From a389dd59bd881cf2cff09a1f67f5c30de61123e6 Mon Sep 17 00:00:00 2001 From: Mary Date: Tue, 18 Aug 2020 03:49:37 +0200 Subject: [PATCH] Amadeus: Final Act (#1481) * Amadeus: Final Act This is my requiem, I present to you Amadeus, a complete reimplementation of the Audio Renderer! This reimplementation is based on my reversing of every version of the audio system module that I carried for the past 10 months. This supports every revision (at the time of writing REV1 to REV8 included) and all features proposed by the Audio Renderer on real hardware. Because this component could be used outside an emulation context, and to avoid possible "inspirations" not crediting the project, I decided to license the Ryujinx.Audio.Renderer project under LGPLv3. - FE3H voices in videos and chapter intro are not present. - Games that use two audio renderer **at the same time** are probably going to have issues right now **until we rewrite the audio output interface** (Crash Team Racing is the only known game to use two renderer at the same time). - Persona 5 Scrambler now goes ingame but audio is garbage. This is caused by the fact that the game engine is syncing audio and video in a really aggressive way. This will disappears the day this game run at full speed. * Make timing more precise when sleeping on Windows Improve precision to a 1ms resolution on Windows NT based OS. This is used to avoid having totally erratic timings and unify all Windows users to the same resolution. NOTE: This is only active when emulation is running. --- ARMeilleure/State/ExecutionContext.cs | 2 + README.md | 1 + .../Common/AuxiliaryBufferAddresses.cs | 30 + .../Common/BehaviourParameter.cs | 67 ++ Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs | 167 ++++ Ryujinx.Audio.Renderer/Common/EffectType.cs | 60 ++ .../Common/MemoryPoolUserState.cs | 60 ++ Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs | 45 + Ryujinx.Audio.Renderer/Common/NodeIdType.cs | 50 + Ryujinx.Audio.Renderer/Common/NodeStates.cs | 246 +++++ .../Common/PerformanceDetailType.cs | 34 + .../Common/PerformanceEntryType.cs | 28 + Ryujinx.Audio.Renderer/Common/PlayState.cs | 40 + .../Common/ReverbEarlyMode.cs | 50 + .../Common/ReverbLateMode.cs | 55 + Ryujinx.Audio.Renderer/Common/SampleFormat.cs | 60 ++ Ryujinx.Audio.Renderer/Common/SinkType.cs | 40 + .../Common/UpdateDataHeader.cs | 50 + .../Common/VoiceUpdateState.cs | 121 +++ Ryujinx.Audio.Renderer/Common/WaveBuffer.cs | 99 ++ .../Common/WorkBufferAllocator.cs | 78 ++ .../Device/VirtualDevice.cs | 84 ++ .../Device/VirtualDeviceSession.cs | 44 + .../Device/VirtualDeviceSessionRegistry.cs | 79 ++ Ryujinx.Audio.Renderer/Dsp/AdpcmHelper.cs | 219 ++++ Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs | 221 +++++ .../Command/AdpcmDataSourceCommandVersion1.cs | 93 ++ .../Dsp/Command/AuxiliaryBufferCommand.cs | 188 ++++ .../Dsp/Command/BiquadFilterCommand.cs | 89 ++ .../Dsp/Command/CircularBufferSinkCommand.cs | 91 ++ .../Dsp/Command/ClearMixBufferCommand.cs | 41 + .../Dsp/Command/CommandList.cs | 124 +++ .../Dsp/Command/CommandType.cs | 49 + .../Dsp/Command/CopyMixBufferCommand.cs | 52 + .../Dsp/Command/DataSourceVersion2Command.cs | 126 +++ .../Dsp/Command/DelayCommand.cs | 272 +++++ .../Dsp/Command/DepopForMixBuffersCommand.cs | 103 ++ .../Dsp/Command/DepopPrepareCommand.cs | 72 ++ .../Dsp/Command/DeviceSinkCommand.cs | 108 ++ .../Command/DownMixSurroundToStereoCommand.cs | 89 ++ .../Dsp/Command/ICommand.cs | 37 + .../Dsp/Command/MixCommand.cs | 125 +++ .../Dsp/Command/MixRampCommand.cs | 83 ++ .../Dsp/Command/MixRampGroupedCommand.cs | 106 ++ .../PcmFloatDataSourceCommandVersion1.cs | 92 ++ .../PcmInt16DataSourceCommandVersion1.cs | 92 ++ .../Dsp/Command/PerformanceCommand.cs | 64 ++ .../Dsp/Command/Reverb3dCommand.cs | 269 +++++ .../Dsp/Command/ReverbCommand.cs | 284 ++++++ .../Dsp/Command/UpsampleCommand.cs | 87 ++ .../Dsp/Command/VolumeCommand.cs | 125 +++ .../Dsp/Command/VolumeRampCommand.cs | 71 ++ .../Dsp/DataSourceHelper.cs | 408 ++++++++ .../Dsp/Effect/DecayDelay.cs | 69 ++ .../Dsp/Effect/DelayLine.cs | 87 ++ .../Dsp/Effect/DelayLineReverb3d.cs | 93 ++ .../Dsp/Effect/IDelayLine.cs | 53 + .../Dsp/FixedPointHelper.cs | 50 + .../Dsp/FloatingPointHelper.cs | 84 ++ Ryujinx.Audio.Renderer/Dsp/PcmHelper.cs | 95 ++ Ryujinx.Audio.Renderer/Dsp/ResamplerHelper.cs | 624 ++++++++++++ .../Dsp/State/AdpcmLoopContext.cs | 29 + .../Dsp/State/AuxiliaryBufferHeader.cs | 60 ++ .../Dsp/State/BiquadFilterState.cs | 28 + .../Dsp/State/DelayState.cs | 73 ++ .../Dsp/State/Reverb3dState.cs | 136 +++ .../Dsp/State/ReverbState.cs | 221 +++++ .../Integration/HardwareDevice.cs | 60 ++ .../Integration/IWritableEvent.cs | 35 + Ryujinx.Audio.Renderer/LICENSE.txt | 165 +++ .../Parameter/AudioRendererConfiguration.cs | 116 +++ .../Parameter/BehaviourErrorInfoOutStatus.cs | 47 + .../Parameter/BiquadFilterParameter.cs | 51 + .../Effect/AuxiliaryBufferParameter.cs | 100 ++ .../Effect/BiquadFilterEffectParameter.cs | 61 ++ .../Parameter/Effect/BufferMixerParameter.cs | 49 + .../Parameter/Effect/DelayParameter.cs | 118 +++ .../Parameter/Effect/Reverb3dParameter.cs | 144 +++ .../Parameter/Effect/ReverbParameter.cs | 136 +++ .../Parameter/EffectInParameter.cs | 103 ++ .../Parameter/EffectOutStatus.cs | 55 + .../Parameter/MemoryPoolInParameter.cs | 50 + .../Parameter/MemoryPoolOutStatus.cs | 39 + .../MixInParameterDirtyOnlyUpdate.cs | 44 + .../Parameter/MixParameter.cs | 112 +++ .../Performance/PerformanceInParameter.cs | 38 + .../Performance/PerformanceOutStatus.cs | 38 + .../Parameter/RendererInfoOutStatus.cs | 38 + .../Parameter/Sink/CircularBufferParameter.cs | 78 ++ .../Parameter/Sink/DeviceParameter.cs | 75 ++ .../Parameter/SinkInParameter.cs | 70 ++ .../Parameter/SinkOutStatus.cs | 43 + .../SplitterDestinationInParameter.cs | 84 ++ .../Parameter/SplitterInParameter.cs | 63 ++ .../Parameter/SplitterInParameterHeader.cs | 62 ++ .../VoiceChannelResourceInParameter.cs | 45 + .../Parameter/VoiceInParameter.cs | 360 +++++++ .../Parameter/VoiceOutStatus.cs | 52 + Ryujinx.Audio.Renderer/RendererConstants.cs | 167 ++++ Ryujinx.Audio.Renderer/ResultCode.cs | 34 + .../Ryujinx.Audio.Renderer.csproj | 38 + .../Server/AudioRenderSystem.cs | 838 ++++++++++++++++ .../Server/AudioRendererManager.cs | 333 +++++++ .../Server/BehaviourContext.cs | 405 ++++++++ .../Server/CommandBuffer.cs | 484 +++++++++ .../Server/CommandGenerator.cs | 938 ++++++++++++++++++ .../CommandProcessingTimeEstimatorVersion1.cs | 180 ++++ .../CommandProcessingTimeEstimatorVersion2.cs | 544 ++++++++++ .../CommandProcessingTimeEstimatorVersion3.cs | 635 ++++++++++++ .../Server/Effect/AuxiliaryBufferEffect.cs | 92 ++ .../Server/Effect/BaseEffect.cs | 257 +++++ .../Server/Effect/BiquadFilterEffect.cs | 74 ++ .../Server/Effect/BufferMixEffect.cs | 56 ++ .../Server/Effect/DelayEffect.cs | 100 ++ .../Server/Effect/EffectContext.cs | 82 ++ .../Server/Effect/Reverb3dEffect.cs | 99 ++ .../Server/Effect/ReverbEffect.cs | 102 ++ .../Server/Effect/UsageState.cs | 45 + .../Server/ICommandProcessingTimeEstimator.cs | 52 + .../Server/MemoryPool/AddressInfo.cs | 151 +++ .../Server/MemoryPool/MemoryPoolState.cs | 148 +++ .../Server/MemoryPool/PoolMapper.cs | 383 +++++++ .../Server/Mix/MixContext.cs | 276 ++++++ Ryujinx.Audio.Renderer/Server/Mix/MixState.cs | 330 ++++++ .../Performance/IPerformanceDetailEntry.cs | 69 ++ .../Server/Performance/IPerformanceEntry.cs | 63 ++ .../Server/Performance/IPerformanceHeader.cs | 97 ++ .../Performance/PerformanceDetailVersion1.cs | 89 ++ .../Performance/PerformanceDetailVersion2.cs | 89 ++ .../Performance/PerformanceEntryAddresses.cs | 73 ++ .../Performance/PerformanceEntryVersion1.cs | 79 ++ .../Performance/PerformanceEntryVersion2.cs | 79 ++ .../PerformanceFrameHeaderVersion1.cs | 118 +++ .../PerformanceFrameHeaderVersion2.cs | 134 +++ .../Server/Performance/PerformanceManager.cs | 124 +++ .../Performance/PerformanceManagerGeneric.cs | 311 ++++++ .../Server/RendererSystemContext.cs | 65 ++ .../Server/Sink/BaseSink.cs | 119 +++ .../Server/Sink/CircularBufferSink.cs | 126 +++ .../Server/Sink/DeviceSink.cs | 92 ++ .../Server/Sink/SinkContext.cs | 73 ++ .../Server/Splitter/SplitterContext.cs | 320 ++++++ .../Server/Splitter/SplitterDestination.cs | 210 ++++ .../Server/Splitter/SplitterState.cs | 237 +++++ Ryujinx.Audio.Renderer/Server/StateUpdater.cs | 575 +++++++++++ .../Types/AudioRendererExecutionMode.cs | 36 + .../Types/AudioRendererRenderingDevice.cs | 41 + .../Server/Types/PlayState.cs | 56 ++ .../Server/Upsampler/UpsamplerManager.cs | 101 ++ .../Server/Upsampler/UpsamplerState.cs | 80 ++ .../Server/Voice/VoiceChannelResource.cs | 57 ++ .../Server/Voice/VoiceContext.cs | 166 ++++ .../Server/Voice/VoiceState.cs | 715 +++++++++++++ .../Server/Voice/WaveBuffer.cs | 121 +++ .../Utils/AudioProcessorMemoryManager.cs | 75 ++ Ryujinx.Audio.Renderer/Utils/BitArray.cs | 120 +++ .../Utils/FileHardwareDevice.cs | 105 ++ Ryujinx.Audio.Renderer/Utils/Mailbox.cs | 72 ++ Ryujinx.Audio.Renderer/Utils/SpanIOHelper.cs | 188 ++++ .../Utils/SpanMemoryManager.cs | 60 ++ .../Utils/SplitterHardwareDevice.cs | 64 ++ Ryujinx.Common/Logging/LogClass.cs | 1 + .../WindowsMultimediaTimerResolution.cs | 111 +++ Ryujinx.HLE/HOS/Horizon.cs | 37 + .../Audio/AudioRenderer/AalHardwareDevice.cs | 98 ++ .../Audio/AudioRenderer/AudioDevice.cs | 158 +++ .../AudioDeviceServer.cs} | 156 ++- .../Audio/AudioRenderer/AudioKernelEvent.cs | 25 + .../Audio/AudioRenderer/AudioRenderer.cs | 112 +++ .../AudioRenderer/AudioRendererServer.cs | 188 ++++ .../Audio/AudioRenderer/IAudioDevice.cs | 17 + .../Audio/AudioRenderer/IAudioRenderer.cs | 20 + .../Services/Audio/AudioRendererManager.cs | 50 + .../AudioRendererCommon.cs | 9 - .../AudioRendererManager/BehaviorInfo.cs | 30 - .../AudioRendererManager/CommandGenerator.cs | 16 - .../Audio/AudioRendererManager/EdgeMatrix.cs | 19 - .../AudioRendererManager/EffectContext.cs | 7 - .../AudioRendererManager/IAudioRenderer.cs | 452 --------- .../AudioRendererManager/MemoryPoolContext.cs | 12 - .../Audio/AudioRendererManager/NodeStates.cs | 19 - .../PerformanceManager.cs | 30 - .../Audio/AudioRendererManager/Resampler.cs | 191 ---- .../AudioRendererManager/SplitterContext.cs | 25 - .../Types/AudioRendererConsts.cs | 17 - .../Types/AudioRendererParameter.cs | 22 - .../AudioRendererManager/Types/BehaviorIn.cs | 11 - .../Types/BiquadFilter.cs | 16 - .../AudioRendererManager/Types/EffectIn.cs | 12 - .../AudioRendererManager/Types/EffectOut.cs | 11 - .../AudioRendererManager/Types/EffectState.cs | 8 - .../Types/MemoryPoolIn.cs | 14 - .../Types/MemoryPoolOut.cs | 12 - .../Types/MemoryPoolState.cs | 13 - .../AudioRendererManager/Types/PlayState.cs | 9 - .../Types/RendererInfoOut.cs | 11 - .../AudioRendererManager/Types/SupportTags.cs | 11 - .../Types/UpdateDataHeader.cs | 24 - .../Types/VoiceChannelResourceIn.cs | 10 - .../AudioRendererManager/Types/VoiceIn.cs | 49 - .../AudioRendererManager/Types/VoiceOut.cs | 12 - .../AudioRendererManager/Types/WaveBuffer.cs | 20 - .../AudioRendererManager/VoiceContext.cs | 201 ---- .../Audio/AudioRendererManagerServer.cs | 105 ++ .../Services/Audio/IAudioRendererManager.cs | 151 +-- Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs | 24 - Ryujinx.HLE/Ryujinx.HLE.csproj | 1 + .../AudioRendererConfigurationTests.cs | 15 + .../Audio/Renderer/BehaviourParameterTests.cs | 16 + .../Renderer/BiquadFilterParameterTests.cs | 15 + .../Renderer/Common/UpdateDataHeaderTests.cs | 15 + .../Renderer/Common/VoiceUpdateStateTests.cs | 15 + .../Audio/Renderer/Common/WaveBufferTests.cs | 15 + .../Renderer/EffectInfoParameterTests.cs | 15 + .../Audio/Renderer/EffectOutStatusTests.cs | 15 + .../Renderer/MemoryPoolParameterTests.cs | 16 + .../BehaviourErrorInfoOutStatusTests.cs | 15 + .../Parameter/Effect/AuxParameterTests.cs | 15 + .../BiquadFilterEffectParameterTests.cs | 15 + .../Effect/BufferMixerParameterTests.cs | 15 + .../Parameter/Effect/DelayParameterTests.cs | 15 + .../Effect/Reverb3dParameterTests.cs | 15 + .../Parameter/Effect/ReverbParameterTests.cs | 15 + .../MixInParameterDirtyOnlyUpdateTests.cs | 15 + .../Renderer/Parameter/MixParameterTests.cs | 15 + .../Parameter/PerformanceInParameterTests.cs | 15 + .../Parameter/PerformanceOutStatusTests.cs | 15 + .../Parameter/RendererInfoOutStatusTests.cs | 15 + .../Sink/CircularBufferParameterTests.cs | 15 + .../Parameter/Sink/DeviceParameterTests.cs | 15 + .../Parameter/SinkInParameterTests.cs | 15 + .../Renderer/Parameter/SinkOutStatusTests.cs | 15 + .../Parameter/SplitterInParamHeaderTests.cs | 15 + .../Audio/Renderer/Server/AddressInfoTests.cs | 35 + .../Renderer/Server/BehaviourContextTests.cs | 228 +++++ .../Renderer/Server/MemoryPoolStateTests.cs | 62 ++ .../Audio/Renderer/Server/MixStateTests.cs | 15 + .../Audio/Renderer/Server/PoolMapperTests.cs | 135 +++ .../Server/SplitterDestinationTests.cs | 15 + .../Renderer/Server/SplitterStateTests.cs | 15 + .../Server/VoiceChannelResourceTests.cs | 15 + .../Audio/Renderer/Server/VoiceStateTests.cs | 15 + .../Audio/Renderer/Server/WaveBufferTests.cs | 15 + .../VoiceChannelResourceInParameterTests.cs | 15 + .../Audio/Renderer/VoiceInParameterTests.cs | 15 + .../Audio/Renderer/VoiceOutStatusTests.cs | 15 + Ryujinx.Tests/Ryujinx.Tests.csproj | 1 + Ryujinx.sln | 10 + Ryujinx/Ryujinx.csproj | 4 + Ryujinx/Ui/MainWindow.cs | 9 + 250 files changed, 23691 insertions(+), 1512 deletions(-) create mode 100644 Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs create mode 100644 Ryujinx.Audio.Renderer/Common/BehaviourParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs create mode 100644 Ryujinx.Audio.Renderer/Common/EffectType.cs create mode 100644 Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs create mode 100644 Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs create mode 100644 Ryujinx.Audio.Renderer/Common/NodeIdType.cs create mode 100644 Ryujinx.Audio.Renderer/Common/NodeStates.cs create mode 100644 Ryujinx.Audio.Renderer/Common/PerformanceDetailType.cs create mode 100644 Ryujinx.Audio.Renderer/Common/PerformanceEntryType.cs create mode 100644 Ryujinx.Audio.Renderer/Common/PlayState.cs create mode 100644 Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs create mode 100644 Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs create mode 100644 Ryujinx.Audio.Renderer/Common/SampleFormat.cs create mode 100644 Ryujinx.Audio.Renderer/Common/SinkType.cs create mode 100644 Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs create mode 100644 Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs create mode 100644 Ryujinx.Audio.Renderer/Common/WaveBuffer.cs create mode 100644 Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs create mode 100644 Ryujinx.Audio.Renderer/Device/VirtualDevice.cs create mode 100644 Ryujinx.Audio.Renderer/Device/VirtualDeviceSession.cs create mode 100644 Ryujinx.Audio.Renderer/Device/VirtualDeviceSessionRegistry.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/AdpcmHelper.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/AuxiliaryBufferCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/CircularBufferSinkCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/ClearMixBufferCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/CommandList.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/CommandType.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/CopyMixBufferCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/DataSourceVersion2Command.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/DelayCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/DepopForMixBuffersCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/DepopPrepareCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/DeviceSinkCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/ICommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/MixCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/MixRampCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/MixRampGroupedCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/PerformanceCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/Reverb3dCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/ReverbCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/UpsampleCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/VolumeCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Command/VolumeRampCommand.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/DataSourceHelper.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Effect/DecayDelay.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Effect/DelayLine.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Effect/DelayLineReverb3d.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/Effect/IDelayLine.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/FixedPointHelper.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/FloatingPointHelper.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/PcmHelper.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/ResamplerHelper.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/State/AdpcmLoopContext.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/State/AuxiliaryBufferHeader.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/State/BiquadFilterState.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/State/DelayState.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/State/Reverb3dState.cs create mode 100644 Ryujinx.Audio.Renderer/Dsp/State/ReverbState.cs create mode 100644 Ryujinx.Audio.Renderer/Integration/HardwareDevice.cs create mode 100644 Ryujinx.Audio.Renderer/Integration/IWritableEvent.cs create mode 100644 Ryujinx.Audio.Renderer/LICENSE.txt create mode 100644 Ryujinx.Audio.Renderer/Parameter/AudioRendererConfiguration.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/BehaviourErrorInfoOutStatus.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/BiquadFilterParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/Effect/BufferMixerParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/Effect/DelayParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/Effect/Reverb3dParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/Effect/ReverbParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/EffectInParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/EffectOutStatus.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/MemoryPoolInParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/MemoryPoolOutStatus.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/MixParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceInParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceOutStatus.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/RendererInfoOutStatus.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/Sink/CircularBufferParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/Sink/DeviceParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/SinkInParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/SinkOutStatus.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/SplitterDestinationInParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/SplitterInParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/SplitterInParameterHeader.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/VoiceChannelResourceInParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/VoiceInParameter.cs create mode 100644 Ryujinx.Audio.Renderer/Parameter/VoiceOutStatus.cs create mode 100644 Ryujinx.Audio.Renderer/RendererConstants.cs create mode 100644 Ryujinx.Audio.Renderer/ResultCode.cs create mode 100644 Ryujinx.Audio.Renderer/Ryujinx.Audio.Renderer.csproj create mode 100644 Ryujinx.Audio.Renderer/Server/AudioRenderSystem.cs create mode 100644 Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs create mode 100644 Ryujinx.Audio.Renderer/Server/BehaviourContext.cs create mode 100644 Ryujinx.Audio.Renderer/Server/CommandBuffer.cs create mode 100644 Ryujinx.Audio.Renderer/Server/CommandGenerator.cs create mode 100644 Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs create mode 100644 Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs create mode 100644 Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Effect/AuxiliaryBufferEffect.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Effect/BaseEffect.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Effect/BiquadFilterEffect.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Effect/BufferMixEffect.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Effect/DelayEffect.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Effect/EffectContext.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Effect/Reverb3dEffect.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Effect/ReverbEffect.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Effect/UsageState.cs create mode 100644 Ryujinx.Audio.Renderer/Server/ICommandProcessingTimeEstimator.cs create mode 100644 Ryujinx.Audio.Renderer/Server/MemoryPool/AddressInfo.cs create mode 100644 Ryujinx.Audio.Renderer/Server/MemoryPool/MemoryPoolState.cs create mode 100644 Ryujinx.Audio.Renderer/Server/MemoryPool/PoolMapper.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Mix/MixState.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/IPerformanceDetailEntry.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/IPerformanceEntry.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/IPerformanceHeader.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion1.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion2.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryAddresses.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion1.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion2.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/PerformanceManager.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Performance/PerformanceManagerGeneric.cs create mode 100644 Ryujinx.Audio.Renderer/Server/RendererSystemContext.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Sink/BaseSink.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Sink/CircularBufferSink.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Sink/DeviceSink.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Sink/SinkContext.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Splitter/SplitterContext.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Splitter/SplitterDestination.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Splitter/SplitterState.cs create mode 100644 Ryujinx.Audio.Renderer/Server/StateUpdater.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Types/AudioRendererExecutionMode.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Types/AudioRendererRenderingDevice.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Types/PlayState.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerManager.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerState.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Voice/VoiceChannelResource.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Voice/VoiceContext.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs create mode 100644 Ryujinx.Audio.Renderer/Server/Voice/WaveBuffer.cs create mode 100644 Ryujinx.Audio.Renderer/Utils/AudioProcessorMemoryManager.cs create mode 100644 Ryujinx.Audio.Renderer/Utils/BitArray.cs create mode 100644 Ryujinx.Audio.Renderer/Utils/FileHardwareDevice.cs create mode 100644 Ryujinx.Audio.Renderer/Utils/Mailbox.cs create mode 100644 Ryujinx.Audio.Renderer/Utils/SpanIOHelper.cs create mode 100644 Ryujinx.Audio.Renderer/Utils/SpanMemoryManager.cs create mode 100644 Ryujinx.Audio.Renderer/Utils/SplitterHardwareDevice.cs create mode 100644 Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs create mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs create mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs rename Ryujinx.HLE/HOS/Services/Audio/{AudioRendererManager/IAudioDevice.cs => AudioRenderer/AudioDeviceServer.cs} (52%) create mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs create mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs create mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs create mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs create mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs create mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/AudioRendererCommon.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/BehaviorInfo.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/CommandGenerator.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EdgeMatrix.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EffectContext.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/NodeStates.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/PerformanceManager.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/SplitterContext.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererConsts.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectIn.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectOut.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectState.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/RendererInfoOut.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/SupportTags.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs create mode 100644 Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/AudioRendererConfigurationTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/BehaviourParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/BiquadFilterParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Common/UpdateDataHeaderTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Common/VoiceUpdateStateTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Common/WaveBufferTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/MemoryPoolParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/BehaviourErrorInfoOutStatusTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Effect/AuxParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BufferMixerParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Effect/DelayParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Effect/Reverb3dParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Effect/ReverbParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdateTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/MixParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceInParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceOutStatusTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/RendererInfoOutStatusTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Sink/CircularBufferParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/Sink/DeviceParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/SinkInParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/SinkOutStatusTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Parameter/SplitterInParamHeaderTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Server/AddressInfoTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Server/MemoryPoolStateTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Server/MixStateTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Server/SplitterStateTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Server/VoiceChannelResourceTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Server/VoiceStateTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/Server/WaveBufferTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/VoiceChannelResourceInParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/VoiceInParameterTests.cs create mode 100644 Ryujinx.Tests/Audio/Renderer/VoiceOutStatusTests.cs diff --git a/ARMeilleure/State/ExecutionContext.cs b/ARMeilleure/State/ExecutionContext.cs index 2755122f..9f89bff4 100644 --- a/ARMeilleure/State/ExecutionContext.cs +++ b/ARMeilleure/State/ExecutionContext.cs @@ -37,6 +37,8 @@ namespace ARMeilleure.State public ulong CntvctEl0 => CntpctEl0; public static TimeSpan ElapsedTime => _tickCounter.Elapsed; + public static long ElapsedTicks => _tickCounter.ElapsedTicks; + public static double TickFrequency => _hostTickFreq; public long TpidrEl0 { get; set; } public long Tpidr { get; set; } diff --git a/README.md b/README.md index e6d4f23d..b2e3b8e8 100644 --- a/README.md +++ b/README.md @@ -120,5 +120,6 @@ If you'd like to donate, please take a look at our [Patreon](https://www.patreon ## License This software is licensed under the terms of the MIT license. +The Ryujinx.Audio.Renderer project is licensed under the terms of the LGPLv3 license. This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3. See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](Ryujinx/THIRDPARTY.md) for more details. diff --git a/Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs b/Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs new file mode 100644 index 00000000..d8e345d8 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using 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; + } +} diff --git a/Ryujinx.Audio.Renderer/Common/BehaviourParameter.cs b/Ryujinx.Audio.Renderer/Common/BehaviourParameter.cs new file mode 100644 index 00000000..8881a92c --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/BehaviourParameter.cs @@ -0,0 +1,67 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using 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; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs b/Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs new file mode 100644 index 00000000..11528174 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs @@ -0,0 +1,167 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.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, RendererConstants.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; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Common/EffectType.cs b/Ryujinx.Audio.Renderer/Common/EffectType.cs new file mode 100644 index 00000000..8349ecb5 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/EffectType.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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 + } +} diff --git a/Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs b/Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs new file mode 100644 index 00000000..f1ea8d9a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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 + } +} diff --git a/Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs b/Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs new file mode 100644 index 00000000..6bb66058 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Common/NodeIdType.cs b/Ryujinx.Audio.Renderer/Common/NodeIdType.cs new file mode 100644 index 00000000..9ee760e9 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/NodeIdType.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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 + } +} diff --git a/Ryujinx.Audio.Renderer/Common/NodeStates.cs b/Ryujinx.Audio.Renderer/Common/NodeStates.cs new file mode 100644 index 00000000..6598619a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/NodeStates.cs @@ -0,0 +1,246 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.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; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Common/PerformanceDetailType.cs b/Ryujinx.Audio.Renderer/Common/PerformanceDetailType.cs new file mode 100644 index 00000000..ebf5b469 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/PerformanceDetailType.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Common +{ + public enum PerformanceDetailType : byte + { + Unknown, + PcmInt16, + Adpcm, + VolumeRamp, + BiquadFilter, + Mix, + Delay, + Aux, + Reverb, + Reverb3d, + PcmFloat + } +} diff --git a/Ryujinx.Audio.Renderer/Common/PerformanceEntryType.cs b/Ryujinx.Audio.Renderer/Common/PerformanceEntryType.cs new file mode 100644 index 00000000..ba27633f --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/PerformanceEntryType.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Common +{ + public enum PerformanceEntryType : byte + { + Invalid, + Voice, + SubMix, + FinalMix, + Sink + } +} diff --git a/Ryujinx.Audio.Renderer/Common/PlayState.cs b/Ryujinx.Audio.Renderer/Common/PlayState.cs new file mode 100644 index 00000000..a96b86dd --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/PlayState.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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 + } +} diff --git a/Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs b/Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs new file mode 100644 index 00000000..2b77336e --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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 + } +} diff --git a/Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs b/Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs new file mode 100644 index 00000000..dab78c45 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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 + } +} diff --git a/Ryujinx.Audio.Renderer/Common/SampleFormat.cs b/Ryujinx.Audio.Renderer/Common/SampleFormat.cs new file mode 100644 index 00000000..24da48bf --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/SampleFormat.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Sample format definition. + /// + public enum SampleFormat : byte + { + /// + /// Invalid sample format. + /// + Invalid = 0, + + /// + /// PCM8 sample format. (unsupported) + /// + PcmInt8 = 1, + + /// + /// PCM16 sample format. + /// + PcmInt16 = 2, + + /// + /// PCM24 sample format. (unsupported) + /// + PcmInt24 = 3, + + /// + /// PCM32 sample format. + /// + PcmInt32 = 4, + + /// + /// PCM Float sample format. + /// + PcmFloat = 5, + + /// + /// ADPCM sample format. (Also known as GC-ADPCM) + /// + Adpcm = 6 + } +} diff --git a/Ryujinx.Audio.Renderer/Common/SinkType.cs b/Ryujinx.Audio.Renderer/Common/SinkType.cs new file mode 100644 index 00000000..22cab23c --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/SinkType.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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 + } +} diff --git a/Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs b/Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs new file mode 100644 index 00000000..40c29f55 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using 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(); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs b/Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs new file mode 100644 index 00000000..490bb871 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs @@ -0,0 +1,121 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.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 * RendererConstants.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 >= RendererConstants.VoiceWaveBufferCount) + { + waveBufferIndex = 0; + } + + if (waveBuffer.IsEndOfStream) + { + playedSampleCount = 0; + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Common/WaveBuffer.cs b/Ryujinx.Audio.Renderer/Common/WaveBuffer.cs new file mode 100644 index 00000000..c2dd1d6b --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/WaveBuffer.cs @@ -0,0 +1,99 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using 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; + } +} diff --git a/Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs b/Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs new file mode 100644 index 00000000..5e5fdff0 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.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, 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, align) + (ulong)Unsafe.SizeOf() * count; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Device/VirtualDevice.cs b/Ryujinx.Audio.Renderer/Device/VirtualDevice.cs new file mode 100644 index 00000000..ca9011e8 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Device/VirtualDevice.cs @@ -0,0 +1,84 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using 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[4] + { + new VirtualDevice("AudioStereoJackOutput", 2), + new VirtualDevice("AudioBuiltInSpeakerOutput", 2), + new VirtualDevice("AudioTvOutput", 6), + new VirtualDevice("AudioUsbDeviceOutput", 2), + }; + + /// + /// 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; } + + /// + /// Create a new instance. + /// + /// The name of the . + /// The count of channels supported by the . + private VirtualDevice(string name, uint channelCount) + { + Name = name; + ChannelCount = channelCount; + } + + /// + /// 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"); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Device/VirtualDeviceSession.cs b/Ryujinx.Audio.Renderer/Device/VirtualDeviceSession.cs new file mode 100644 index 00000000..09184238 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Device/VirtualDeviceSession.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Device/VirtualDeviceSessionRegistry.cs b/Ryujinx.Audio.Renderer/Device/VirtualDeviceSessionRegistry.cs new file mode 100644 index 00000000..569cb7c4 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Device/VirtualDeviceSessionRegistry.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using 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[1]; + + /// + /// 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; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/AdpcmHelper.cs b/Ryujinx.Audio.Renderer/Dsp/AdpcmHelper.cs new file mode 100644 index 00000000..e49c74d3 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/AdpcmHelper.cs @@ -0,0 +1,219 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.State; +using 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)] + 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 = coefficients[coefficientIndex * 2 + 0]; + short coefficient1 = 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 = coefficients[coefficientIndex * 2 + 0]; + coefficient1 = 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; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs b/Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs new file mode 100644 index 00000000..90f6cd51 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs @@ -0,0 +1,221 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Integration; +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; + private HardwareDevice[] _outputDevices; + + private long _lastTime; + private long _playbackEnds; + private ManualResetEvent _event; + + public void SetOutputDevices(HardwareDevice[] outputDevices) + { + _outputDevices = outputDevices; + } + + public void Start() + { + _mailbox = new Mailbox(); + _sessionCommandList = new RendererSession[RendererConstants.AudioRendererSessionCountMax]; + _event = new ManualResetEvent(false); + _lastTime = PerformanceCounter.ElapsedNanoseconds; + + 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!"); + } + } + + public void Send(int sessionId, CommandList commands, int renderingLimit, ulong appletResourceId) + { + _sessionCommandList[sessionId] = new RendererSession + { + CommandList = commands, + RenderingLimit = renderingLimit, + AppletResourceId = appletResourceId + }; + } + + 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 = RendererConstants.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) + { + 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] = null; + } + } + + long endTicks = PerformanceCounter.ElapsedNanoseconds; + + long elapsedTime = endTicks - startTicks; + + if (RendererConstants.AudioProcessorMaxUpdateTime < elapsedTime) + { + Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - RendererConstants.AudioProcessorMaxUpdateTime}ns)"); + } + + _mailbox.SendResponse(MailboxMessage.RenderEnd); + } + } + + Logger.Info?.Print(LogClass.AudioRenderer, "Stopping audio processor"); + _mailbox.SendResponse(MailboxMessage.Stop); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _event.Dispose(); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs b/Ryujinx.Audio.Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs new file mode 100644 index 00000000..df9ef086 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using 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 ulong 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[RendererConstants.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() + { + State = State, + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.Adpcm, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + WaveBuffers = WaveBuffers, + ExtraParameter = AdpcmParameter, + ExtraParameterSize = AdpcmParameterSize, + ChannelIndex = 0, + ChannelCount = 1, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/AuxiliaryBufferCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/AuxiliaryBufferCommand.cs new file mode 100644 index 00000000..f63722c1 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/AuxiliaryBufferCommand.cs @@ -0,0 +1,188 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Cpu; +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 ulong 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; + } + + private uint Read(MemoryManager 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; + } + + private uint Write(MemoryManager 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, outputBuffer.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 + { + MemoryHelper.FillWithZeros(context.MemoryManager, (long)BufferInfo.SendBufferInfo, Unsafe.SizeOf()); + MemoryHelper.FillWithZeros(context.MemoryManager, (long)BufferInfo.ReturnBufferInfo, Unsafe.SizeOf()); + + if (InputBufferIndex != OutputBufferIndex) + { + inputBuffer.CopyTo(outputBuffer); + } + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs new file mode 100644 index 00000000..d00d3371 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class BiquadFilterCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.BiquadFilter; + + public ulong EstimatedProcessingTime { get; set; } + + public BiquadFilterParameter Parameter { get; } + public Memory BiquadFilterState { get; } + public int InputBufferIndex { get; } + public int OutputBufferIndex { get; } + public bool NeedInitialization { get; } + + 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; + } + + private void ProcessBiquadFilter(Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount) + { + const int fixedPointPrecisionForParameter = 14; + + float a0 = FixedPointHelper.ToFloat(Parameter.Numerator[0], fixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(Parameter.Numerator[1], fixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(Parameter.Numerator[2], fixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(Parameter.Denominator[0], fixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(Parameter.Denominator[1], fixedPointPrecisionForParameter); + + ref BiquadFilterState state = ref BiquadFilterState.Span[0]; + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a0 + state.Z1; + + state.Z1 = input * a1 + output * b1 + state.Z2; + state.Z2 = input * a2 + output * b2; + + outputBuffer[i] = output; + } + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(InputBufferIndex); + + if (NeedInitialization) + { + BiquadFilterState.Span[0] = new BiquadFilterState(); + } + + ProcessBiquadFilter(outputBuffer, outputBuffer, context.SampleCount); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/CircularBufferSinkCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/CircularBufferSinkCommand.cs new file mode 100644 index 00000000..63719a75 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/CircularBufferSinkCommand.cs @@ -0,0 +1,91 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +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 ulong 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[RendererConstants.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++) + { + ReadOnlySpan inputBuffer = context.GetBuffer(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; + } + } + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/ClearMixBufferCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/ClearMixBufferCommand.cs new file mode 100644 index 00000000..3ec725d1 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/ClearMixBufferCommand.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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 ulong EstimatedProcessingTime { get; set; } + + public ClearMixBufferCommand(int nodeId) + { + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + context.Buffers.Span.Fill(0); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/CommandList.cs b/Ryujinx.Audio.Renderer/Dsp/Command/CommandList.cs new file mode 100644 index 00000000..d65f6ced --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/CommandList.cs @@ -0,0 +1,124 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CommandList + { + 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 MemoryManager MemoryManager { get; } + + public HardwareDevice OutputDevice { get; private set; } + + public CommandList(AudioRenderSystem renderSystem) : this(renderSystem.MemoryManager, + renderSystem.GetMixBuffer(), + renderSystem.GetSampleCount(), + renderSystem.GetSampleRate(), + renderSystem.GetMixBufferCount(), + renderSystem.GetVoiceChannelCountMax()) + { + } + + public CommandList(MemoryManager memoryManager, Memory mixBuffer, uint sampleCount, uint sampleRate, uint mixBufferCount, uint voiceChannelCountMax) + { + SampleCount = sampleCount; + SampleRate = sampleRate; + BufferCount = mixBufferCount + voiceChannelCountMax; + Buffers = mixBuffer; + Commands = new List(); + MemoryManager = memoryManager; + } + + public void AddCommand(ICommand command) + { + Commands.Add(command); + } + + public void AddCommand(T command) where T : unmanaged, ICommand + { + throw new NotImplementedException(); + } + + public Memory GetBufferMemory(int index) + { + return Buffers.Slice(index * (int)SampleCount, (int)SampleCount); + } + + public Span GetBuffer(int index) + { + return Buffers.Span.Slice(index * (int)SampleCount, (int)SampleCount); + } + + public ulong GetTimeElapsedSinceDspStartedProcessing() + { + return (ulong)PerformanceCounter.ElapsedNanoseconds - StartTime; + } + + public void Process(HardwareDevice 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; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/CommandType.cs b/Ryujinx.Audio.Renderer/Dsp/Command/CommandType.cs new file mode 100644 index 00000000..6532cd15 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/CommandType.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +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 + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/CopyMixBufferCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/CopyMixBufferCommand.cs new file mode 100644 index 00000000..62b95215 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/CopyMixBufferCommand.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; + +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 ulong 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) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + inputBuffer.CopyTo(outputBuffer); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DataSourceVersion2Command.cs b/Ryujinx.Audio.Renderer/Dsp/Command/DataSourceVersion2Command.cs new file mode 100644 index 00000000..7b68ff5d --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/DataSourceVersion2Command.cs @@ -0,0 +1,126 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using 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 ulong 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 = outputBufferIndex; + SampleRate = serverState.SampleRate; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[RendererConstants.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() + { + State = State, + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + WaveBuffers = WaveBuffers, + ExtraParameter = ExtraParameter, + ExtraParameterSize = ExtraParameterSize, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + SrcQuality = SrcQuality + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DelayCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/DelayCommand.cs new file mode 100644 index 00000000..1bf844d8 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/DelayCommand.cs @@ -0,0 +1,272 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; + +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 ulong 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) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + } + + private void ProcessDelayMono(Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount) + { + ref DelayState state = ref State.Span[0]; + + 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 lowPassResult = input * inGain + delayLineValue * feedbackGain * state.LowPassBaseGain + state.LowPassZ[0] * state.LowPassFeedbackGain; + + state.LowPassZ[0] = lowPassResult; + + state.DelayLines[0].Update(lowPassResult); + + outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64; + } + } + + private void ProcessDelayStereo(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + ref DelayState state = ref State.Span[0]; + + float[] channelInput = new float[Parameter.ChannelCount]; + float[] delayLineValues = new float[Parameter.ChannelCount]; + float[] temp = new float[Parameter.ChannelCount]; + + 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); + + for (int i = 0; i < sampleCount; i++) + { + for (int j = 0; j < Parameter.ChannelCount; j++) + { + channelInput[j] = inputBuffers[j].Span[i] * 64; + delayLineValues[j] = state.DelayLines[j].Read(); + } + + temp[0] = channelInput[0] * inGain + delayLineValues[1] * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain; + temp[1] = channelInput[1] * inGain + delayLineValues[0] * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain; + + for (int j = 0; j < Parameter.ChannelCount; j++) + { + float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain; + + state.LowPassZ[j] = lowPassResult; + state.DelayLines[j].Update(lowPassResult); + + outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64; + } + } + } + + private void ProcessDelayQuadraphonic(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + ref DelayState state = ref State.Span[0]; + + float[] channelInput = new float[Parameter.ChannelCount]; + float[] delayLineValues = new float[Parameter.ChannelCount]; + float[] temp = new float[Parameter.ChannelCount]; + + 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); + + for (int i = 0; i < sampleCount; i++) + { + for (int j = 0; j < Parameter.ChannelCount; j++) + { + channelInput[j] = inputBuffers[j].Span[i] * 64; + delayLineValues[j] = state.DelayLines[j].Read(); + } + + temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain; + temp[1] = channelInput[1] * inGain + (delayLineValues[0] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain; + temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain; + temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain; + + for (int j = 0; j < Parameter.ChannelCount; j++) + { + float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain; + + state.LowPassZ[j] = lowPassResult; + state.DelayLines[j].Update(lowPassResult); + + outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64; + } + } + } + + private void ProcessDelaySurround(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + ref DelayState state = ref State.Span[0]; + + float[] channelInput = new float[Parameter.ChannelCount]; + float[] delayLineValues = new float[Parameter.ChannelCount]; + float[] temp = new float[Parameter.ChannelCount]; + + 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); + + for (int i = 0; i < sampleCount; i++) + { + for (int j = 0; j < Parameter.ChannelCount; j++) + { + channelInput[j] = inputBuffers[j].Span[i] * 64; + delayLineValues[j] = state.DelayLines[j].Read(); + } + + temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[4]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain; + temp[1] = channelInput[1] * inGain + (delayLineValues[4] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain; + temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain; + temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain; + temp[4] = channelInput[4] * inGain + (delayLineValues[0] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[4] * delayFeedbackBaseGain; + temp[5] = channelInput[5] * inGain + delayLineValues[5] * delayFeedbackBaseGain; + + for (int j = 0; j < Parameter.ChannelCount; j++) + { + float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain; + + state.LowPassZ[j] = lowPassResult; + state.DelayLines[j].Update(lowPassResult); + + outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64; + } + } + } + + private void ProcessDelay(CommandList context) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + ReadOnlyMemory[] inputBuffers = new ReadOnlyMemory[Parameter.ChannelCount]; + Memory[] outputBuffers = new Memory[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]); + } + + switch (Parameter.ChannelCount) + { + case 1: + ProcessDelayMono(outputBuffers[0].Span, inputBuffers[0].Span, context.SampleCount); + break; + case 2: + ProcessDelayStereo(outputBuffers, inputBuffers, context.SampleCount); + break; + case 4: + ProcessDelayQuadraphonic(outputBuffers, inputBuffers, context.SampleCount); + break; + case 6: + ProcessDelaySurround(outputBuffers, inputBuffers, context.SampleCount); + break; + default: + throw new NotImplementedException($"{Parameter.ChannelCount}"); + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[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); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DepopForMixBuffersCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/DepopForMixBuffersCommand.cs new file mode 100644 index 00000000..5a9806b6 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/DepopForMixBuffersCommand.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; + +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 ulong EstimatedProcessingTime { get; set; } + + public uint MixBufferOffset { get; } + + public uint MixBufferCount { get; } + + public float Decay { get; } + + public Memory DepopBuffer { get; } + + private const int FixedPointPrecisionForDecay = 15; + + 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; + } + } + + private float ProcessDepopMix(Span buffer, float depopValue, uint sampleCount) + { + if (depopValue <= 0) + { + 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) + { + uint bufferCount = Math.Min(MixBufferOffset + MixBufferCount, context.BufferCount); + + for (int i = (int)MixBufferOffset; i < bufferCount; i++) + { + float depopValue = DepopBuffer.Span[i]; + if (depopValue != 0) + { + Span buffer = context.GetBuffer(i); + + DepopBuffer.Span[i] = ProcessDepopMix(buffer, depopValue, context.SampleCount); + } + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DepopPrepareCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/DepopPrepareCommand.cs new file mode 100644 index 00000000..21ded53a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/DepopPrepareCommand.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using 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 ulong 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[RendererConstants.MixBufferCountMax]; + + for (int i = 0; i < RendererConstants.MixBufferCountMax; i++) + { + OutputBufferIndices[i] = (ushort)(bufferOffset + i); + } + + State = state; + DepopBuffer = depopBuffer; + } + + public void Process(CommandList context) + { + ref VoiceUpdateState state = ref State.Span[0]; + + for (int i = 0; i < MixBufferCount; i++) + { + if (state.LastSamples[i] != 0) + { + DepopBuffer.Span[OutputBufferIndices[i]] += state.LastSamples[i]; + + state.LastSamples[i] = 0; + } + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DeviceSinkCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/DeviceSinkCommand.cs new file mode 100644 index 00000000..111d2a4c --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/DeviceSinkCommand.cs @@ -0,0 +1,108 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.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 ulong 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 < InputCount; 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) + { + HardwareDevice device = context.OutputDevice; + + if (device.GetSampleRate() == RendererConstants.TargetSampleRate) + { + int channelCount = (int)device.GetChannelCount(); + uint bufferCount = Math.Min(device.GetChannelCount(), InputCount); + + const int sampleCount = RendererConstants.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(); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs new file mode 100644 index 00000000..76f5c576 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using 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 ulong EstimatedProcessingTime { get; set; } + + public ushort[] InputBufferIndices { get; } + public ushort[] OutputBufferIndices { get; } + + public float[] Coefficients { get; } + + public DownMixSurroundToStereoCommand(uint bufferOffset, Span inputBufferOffset, Span outputBufferOffset, ReadOnlySpan downMixParameter, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + + for (int i = 0; i < RendererConstants.VoiceChannelCountMax; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + outputBufferOffset[i]); + } + + Coefficients = downMixParameter.ToArray(); + } + + [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 lowFrequency = context.GetBuffer(InputBufferIndices[2]); + ReadOnlySpan frontCenter = 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]); + Span unused2 = context.GetBuffer(OutputBufferIndices[2]); + Span unused3 = context.GetBuffer(OutputBufferIndices[3]); + Span unused4 = context.GetBuffer(OutputBufferIndices[4]); + Span unused5 = context.GetBuffer(OutputBufferIndices[5]); + + 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]); + } + + unused2.Fill(0); + unused3.Fill(0); + unused4.Fill(0); + unused5.Fill(0); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/ICommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/ICommand.cs new file mode 100644 index 00000000..85a01123 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/ICommand.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public interface ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType { get; } + + public ulong EstimatedProcessingTime { get; } + + public void Process(CommandList context); + + public bool ShouldMeter() + { + return false; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/MixCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/MixCommand.cs new file mode 100644 index 00000000..87b296f6 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/MixCommand.cs @@ -0,0 +1,125 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +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 ulong 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; + } + + 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); + } + } + + 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 ProcessMixSlowPath(Span outputMix, ReadOnlySpan inputMix) + { + for (int i = 0; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + private void ProcessMix(Span outputMix, ReadOnlySpan inputMix) + { + if (Avx.IsSupported) + { + ProcessMixAvx(outputMix, inputMix); + } + else if (Sse41.IsSupported) + { + ProcessMixSse41(outputMix, inputMix); + } + else + { + ProcessMixSlowPath(outputMix, inputMix); + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessMix(outputBuffer, inputBuffer); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/MixRampCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/MixRampCommand.cs new file mode 100644 index 00000000..0397dee2 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/MixRampCommand.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using System; + +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 ulong 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; + } + + 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); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/MixRampGroupedCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/MixRampGroupedCommand.cs new file mode 100644 index 00000000..700b906c --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/MixRampGroupedCommand.cs @@ -0,0 +1,106 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using System; + +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 ulong 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[RendererConstants.MixBufferCountMax]; + OutputBufferIndices = new ushort[RendererConstants.MixBufferCountMax]; + Volume0 = new float[RendererConstants.MixBufferCountMax]; + Volume1 = new float[RendererConstants.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; + } + + 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; + } + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs b/Ryujinx.Audio.Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs new file mode 100644 index 00000000..49e6253d --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using 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 ulong 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[RendererConstants.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() + { + State = State, + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.PcmInt16, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + WaveBuffers = WaveBuffers, + ExtraParameter = 0, + ExtraParameterSize = 0, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs b/Ryujinx.Audio.Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs new file mode 100644 index 00000000..56f85a90 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using 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 ulong 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[RendererConstants.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() + { + State = State, + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.PcmInt16, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + WaveBuffers = WaveBuffers, + ExtraParameter = 0, + ExtraParameterSize = 0, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/PerformanceCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/PerformanceCommand.cs new file mode 100644 index 00000000..199667e0 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/PerformanceCommand.cs @@ -0,0 +1,64 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.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 ulong 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(); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/Reverb3dCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/Reverb3dCommand.cs new file mode 100644 index 00000000..eeb7322a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/Reverb3dCommand.cs @@ -0,0 +1,269 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; + +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 ulong 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) + { + Enabled = true; + IsEffectEnabled = isEnabled; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + } + + private void ProcessReverb3dMono(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + const int delayLineSampleIndexOffset = -1; + + ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableMono, TargetEarlyDelayLineIndicesTableMono, TargetOutputFeedbackIndicesTableMono, delayLineSampleIndexOffset); + } + + private void ProcessReverb3dStereo(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + const int delayLineSampleIndexOffset = 1; + + ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableStereo, TargetEarlyDelayLineIndicesTableStereo, TargetOutputFeedbackIndicesTableStereo, delayLineSampleIndexOffset); + } + + private void ProcessReverb3dQuadraphonic(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + const int delayLineSampleIndexOffset = 1; + + ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableQuadraphonic, TargetEarlyDelayLineIndicesTableQuadraphonic, TargetOutputFeedbackIndicesTableQuadraphonic, delayLineSampleIndexOffset); + } + + private void ProcessReverb3dSurround(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + const int delayLineSampleIndexOffset = 1; + + ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableSurround, TargetEarlyDelayLineIndicesTableSurround, TargetOutputFeedbackIndicesTableSurround, delayLineSampleIndexOffset); + } + + private void ProcessReverb3dGeneric(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable, int delayLineSampleIndexOffset) + { + ref Reverb3dState state = ref State.Span[0]; + + bool isMono = Parameter.ChannelCount == 1; + bool isSurround = Parameter.ChannelCount == 6; + + float[] outputValues = new float[RendererConstants.ChannelCountMax]; + float[] channelInput = new float[Parameter.ChannelCount]; + float[] feedbackValues = new float[4]; + float[] feedbackOutputValues = new float[4]; + float[] values = new float[4]; + + for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + outputValues.AsSpan().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] = inputBuffers[channelIndex].Span[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) + { + outputBuffers[channelIndex].Span[sampleIndex] = (outputValues[channelIndex] + values[targetOutputFeedbackIndex] + channelInput[channelIndex] * state.DryGain); + } + } + + if (isMono) + { + outputBuffers[0].Span[sampleIndex] += values[1]; + } + + if (isSurround) + { + outputBuffers[4].Span[sampleIndex] += (outputValues[4] + state.BackLeftDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain); + } + } + } + + public void ProcessReverb3d(CommandList context) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + ReadOnlyMemory[] inputBuffers = new ReadOnlyMemory[Parameter.ChannelCount]; + Memory[] outputBuffers = new Memory[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]); + } + + switch (Parameter.ChannelCount) + { + case 1: + ProcessReverb3dMono(outputBuffers, inputBuffers, context.SampleCount); + break; + case 2: + ProcessReverb3dStereo(outputBuffers, inputBuffers, context.SampleCount); + break; + case 4: + ProcessReverb3dQuadraphonic(outputBuffers, inputBuffers, context.SampleCount); + break; + case 6: + ProcessReverb3dSurround(outputBuffers, inputBuffers, context.SampleCount); + break; + default: + throw new NotImplementedException($"{Parameter.ChannelCount}"); + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[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); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/ReverbCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/ReverbCommand.cs new file mode 100644 index 00000000..3935234e --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/ReverbCommand.cs @@ -0,0 +1,284 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; +using System.Diagnostics; + +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[RendererConstants.ChannelCountMax] { 0, 1, 2, 3, 4, 5 }; + private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[RendererConstants.ChannelCountMax] { 0, 1, 2, 3, -1, 3 }; + + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Reverb; + + public ulong 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) + { + Enabled = true; + IsEffectEnabled = isEnabled; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[RendererConstants.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; + } + + private void ProcessReverbMono(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + ProcessReverbGeneric(outputBuffers, + inputBuffers, + sampleCount, + OutputEarlyIndicesTableMono, + TargetEarlyDelayLineIndicesTableMono, + TargetOutputFeedbackIndicesTableMono, + OutputIndicesTableMono); + } + + private void ProcessReverbStereo(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + ProcessReverbGeneric(outputBuffers, + inputBuffers, + sampleCount, + OutputEarlyIndicesTableStereo, + TargetEarlyDelayLineIndicesTableStereo, + TargetOutputFeedbackIndicesTableStereo, + OutputIndicesTableStereo); + } + + private void ProcessReverbQuadraphonic(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + ProcessReverbGeneric(outputBuffers, + inputBuffers, + sampleCount, + OutputEarlyIndicesTableQuadraphonic, + TargetEarlyDelayLineIndicesTableQuadraphonic, + TargetOutputFeedbackIndicesTableQuadraphonic, + OutputIndicesTableQuadraphonic); + } + + private void ProcessReverbSurround(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount) + { + ProcessReverbGeneric(outputBuffers, + inputBuffers, + sampleCount, + OutputEarlyIndicesTableSurround, + TargetEarlyDelayLineIndicesTableSurround, + TargetOutputFeedbackIndicesTableSurround, + OutputIndicesTableSurround); + } + + private void ProcessReverbGeneric(Memory[] outputBuffers, ReadOnlyMemory[] inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable, ReadOnlySpan outputIndicesTable) + { + ref ReverbState state = ref State.Span[0]; + + 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); + + float[] outputValues = new float[RendererConstants.ChannelCountMax]; + float[] feedbackValues = new float[4]; + float[] feedbackOutputValues = new float[4]; + float[] channelInput = new float[Parameter.ChannelCount]; + + for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + outputValues.AsSpan().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] = inputBuffers[channelIndex].Span[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.BackLeftDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + outputBuffers[channelIndex].Span[sampleIndex] = (outputValues[channelIndex] * outGain + channelInput[channelIndex] * dryGain) / 64; + } + } + } + + private void ProcessReverb(CommandList context) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + ReadOnlyMemory[] inputBuffers = new ReadOnlyMemory[Parameter.ChannelCount]; + Memory[] outputBuffers = new Memory[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]); + } + + switch (Parameter.ChannelCount) + { + case 1: + ProcessReverbMono(outputBuffers, inputBuffers, context.SampleCount); + break; + case 2: + ProcessReverbStereo(outputBuffers, inputBuffers, context.SampleCount); + break; + case 4: + ProcessReverbQuadraphonic(outputBuffers, inputBuffers, context.SampleCount); + break; + case 6: + ProcessReverbSurround(outputBuffers, inputBuffers, context.SampleCount); + break; + default: + throw new NotImplementedException($"{Parameter.ChannelCount}"); + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[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); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/UpsampleCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/UpsampleCommand.cs new file mode 100644 index 00000000..0761a8da --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/UpsampleCommand.cs @@ -0,0 +1,87 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; +using System.Runtime.CompilerServices; + +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 ulong 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]); + } + + UpsamplerInfo = info; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetBuffer(int index, int sampleCount) + { + return UpsamplerInfo.OutputBuffer.Span.Slice(index * sampleCount, sampleCount); + } + + public void Process(CommandList context) + { + float ratio = (float)InputSampleRate / RendererConstants.TargetSampleRate; + + 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); + + float fraction = 0.0f; + + ResamplerHelper.ResampleForUpsampler(outputBuffer, inputBuffer, ratio, ref fraction, (int)(InputSampleCount / ratio)); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/VolumeCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/VolumeCommand.cs new file mode 100644 index 00000000..735a0a68 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/VolumeCommand.cs @@ -0,0 +1,125 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +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 ulong 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; + } + + 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); + } + } + + 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); + } + } + + private void ProcessVolume(Span outputBuffer, ReadOnlySpan inputBuffer) + { + if (Avx.IsSupported) + { + ProcessVolumeAvx(outputBuffer, inputBuffer); + } + else if (Sse41.IsSupported) + { + ProcessVolumeSse41(outputBuffer, inputBuffer); + } + else + { + ProcessVolumeSlowPath(outputBuffer, inputBuffer); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeSlowPath(Span outputBuffer, ReadOnlySpan inputBuffer) + { + for (int i = 0; i < outputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessVolume(outputBuffer, inputBuffer); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/VolumeRampCommand.cs b/Ryujinx.Audio.Renderer/Dsp/Command/VolumeRampCommand.cs new file mode 100644 index 00000000..98427a2c --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Command/VolumeRampCommand.cs @@ -0,0 +1,71 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class VolumeRampCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.VolumeRamp; + + public ulong EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public VolumeRampCommand(float volume0, float volume1, uint bufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)bufferIndex; + OutputBufferIndex = (ushort)bufferIndex; + + Volume0 = volume0; + Volume1 = volume1; + } + + private void ProcessVolumeRamp(Span outputBuffer, ReadOnlySpan inputBuffer, int sampleCount) + { + float ramp = (Volume1 - Volume0) / sampleCount; + + float volume = Volume0; + + for (int i = 0; i < sampleCount; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume); + volume += ramp; + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessVolumeRamp(outputBuffer, inputBuffer, (int)context.SampleCount); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/DataSourceHelper.cs b/Ryujinx.Audio.Renderer/Dsp/DataSourceHelper.cs new file mode 100644 index 00000000..ccc97b92 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/DataSourceHelper.cs @@ -0,0 +1,408 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class DataSourceHelper + { + private const int FixedPointPrecision = 15; + + public class WaveBufferInformation + { + public Memory State; + public uint SourceSampleRate; + public SampleFormat SampleFormat; + public float Pitch; + public DecodingBehaviour DecodingBehaviour; + public WaveBuffer[] WaveBuffers; + public ulong ExtraParameter; + public ulong ExtraParameterSize; + public int ChannelIndex; + public int ChannelCount; + public SampleRateConversionQuality SrcQuality; + } + + private static int GetPitchLimitBySrcQuality(SampleRateConversionQuality quality) + { + switch (quality) + { + case SampleRateConversionQuality.Default: + case SampleRateConversionQuality.Low: + return 4; + case SampleRateConversionQuality.High: + return 8; + default: + throw new ArgumentException($"{quality}"); + } + } + + public static void ProcessWaveBuffers(MemoryManager memoryManager, Span outputBuffer, WaveBufferInformation info, uint targetSampleRate, int sampleCount) + { + const int tempBufferSize = 0x3F00; + + ref VoiceUpdateState state = ref info.State.Span[0]; + + short[] tempBuffer = ArrayPool.Shared.Rent(tempBufferSize); + + float sampleRateRatio = ((float)info.SourceSampleRate / targetSampleRate * info.Pitch); + + float fraction = state.Fraction; + int waveBufferIndex = (int)state.WaveBufferIndex; + ulong playedSampleCount = state.PlayedSampleCount; + int offset = state.Offset; + uint waveBufferConsumed = state.WaveBufferConsumed; + + int pitchMaxLength = GetPitchLimitBySrcQuality(info.SrcQuality); + + int totalNeededSize = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCount); + + if (totalNeededSize + pitchMaxLength <= tempBufferSize && totalNeededSize >= 0) + { + int sourceSampleCountToProcess = sampleCount; + + int maxSampleCountPerIteration = Math.Min((int)MathF.Truncate((tempBufferSize - fraction) / sampleRateRatio), sampleCount); + + bool isStarving = false; + + int i = 0; + + while (i < sourceSampleCountToProcess) + { + int tempBufferIndex = 0; + + if (!info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion)) + { + state.Pitch.ToSpan().Slice(0, pitchMaxLength).CopyTo(tempBuffer.AsSpan()); + tempBufferIndex += pitchMaxLength; + } + + int sampleCountToProcess = Math.Min(sourceSampleCountToProcess, maxSampleCountPerIteration); + + int y = 0; + + int sampleCountToDecode = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCountToProcess); + + while (y < sampleCountToDecode) + { + if (waveBufferIndex >= RendererConstants.VoiceWaveBufferCount) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Invalid WaveBuffer index {waveBufferIndex}"); + + waveBufferIndex = 0; + playedSampleCount = 0; + } + + if (!state.IsWaveBufferValid[waveBufferIndex]) + { + isStarving = true; + break; + } + + ref WaveBuffer waveBuffer = ref info.WaveBuffers[waveBufferIndex]; + + if (offset == 0 && info.SampleFormat == SampleFormat.Adpcm && waveBuffer.Context != 0) + { + state.LoopContext = memoryManager.Read(waveBuffer.Context); + } + + Span tempSpan = tempBuffer.AsSpan().Slice(tempBufferIndex + y); + + int decodedSampleCount = -1; + + int targetSampleStartOffset; + int targetSampleEndOffset; + + if (state.LoopCount > 0 && waveBuffer.LoopStartSampleOffset != 0 && waveBuffer.LoopEndSampleOffset != 0 && waveBuffer.LoopStartSampleOffset <= waveBuffer.LoopEndSampleOffset) + { + targetSampleStartOffset = (int)waveBuffer.LoopStartSampleOffset; + targetSampleEndOffset = (int)waveBuffer.LoopEndSampleOffset; + } + else + { + targetSampleStartOffset = (int)waveBuffer.StartSampleOffset; + targetSampleEndOffset = (int)waveBuffer.EndSampleOffset; + } + + int targetWaveBufferSampleCount = targetSampleEndOffset - targetSampleStartOffset; + + switch (info.SampleFormat) + { + case SampleFormat.Adpcm: + ReadOnlySpan waveBufferAdpcm = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + // TODO: we are possibly copying a lot of unneeded data here, we should only take what we need. + waveBufferAdpcm = memoryManager.GetSpan(waveBuffer.Buffer, (int)waveBuffer.BufferSize); + } + + ReadOnlySpan coefficients = MemoryMarshal.Cast(memoryManager.GetSpan(info.ExtraParameter, (int)info.ExtraParameterSize)); + decodedSampleCount = AdpcmHelper.Decode(tempSpan, waveBufferAdpcm, targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y, coefficients, ref state.LoopContext); + break; + case SampleFormat.PcmInt16: + ReadOnlySpan waveBufferPcm16 = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset(targetSampleStartOffset, offset, info.ChannelCount); + int bufferSize = PcmHelper.GetBufferSize(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount; + + waveBufferPcm16 = MemoryMarshal.Cast(memoryManager.GetSpan(bufferOffset, bufferSize)); + } + + decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcm16, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount); + break; + case SampleFormat.PcmFloat: + ReadOnlySpan waveBufferPcmFloat = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset(targetSampleStartOffset, offset, info.ChannelCount); + int bufferSize = PcmHelper.GetBufferSize(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount; + + waveBufferPcmFloat = MemoryMarshal.Cast(memoryManager.GetSpan(bufferOffset, bufferSize)); + } + + decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcmFloat, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount); + break; + default: + Logger.Warning?.Print(LogClass.AudioRenderer, $"Unsupported sample format {info.SampleFormat}"); + break; + } + + Debug.Assert(decodedSampleCount <= sampleCountToDecode); + + if (decodedSampleCount < 0) + { + Logger.Warning?.Print(LogClass.AudioRenderer, $"Decoding failed, skipping WaveBuffer"); + + state.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount); + decodedSampleCount = 0; + } + + y += decodedSampleCount; + offset += decodedSampleCount; + playedSampleCount += (uint)decodedSampleCount; + + if (offset >= targetWaveBufferSampleCount || decodedSampleCount == 0) + { + offset = 0; + + if (waveBuffer.Looping) + { + state.LoopCount++; + + if (waveBuffer.LoopCount >= 0) + { + if (decodedSampleCount == 0 || state.LoopCount > waveBuffer.LoopCount) + { + state.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount); + } + } + + if (decodedSampleCount == 0) + { + isStarving = true; + break; + } + + if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.PlayedSampleCountResetWhenLooping)) + { + playedSampleCount = 0; + } + } + else + { + state.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount); + } + } + } + + Span outputSpan = outputBuffer.Slice(i); + Span outputSpanInt = MemoryMarshal.Cast(outputSpan); + + if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion)) + { + for (int j = 0; j < y; j++) + { + outputBuffer[j] = tempBuffer[j]; + } + } + else + { + Span tempSpan = tempBuffer.AsSpan().Slice(tempBufferIndex + y); + + tempSpan.Slice(0, sampleCountToDecode - y).Fill(0); + + ToFloat(outputBuffer, outputSpanInt, sampleCountToProcess); + + ResamplerHelper.Resample(outputBuffer, tempBuffer, sampleRateRatio, ref fraction, sampleCountToProcess, info.SrcQuality, y != sourceSampleCountToProcess || info.Pitch != 1.0f); + + tempBuffer.AsSpan().Slice(sampleCountToDecode, pitchMaxLength).CopyTo(state.Pitch.ToSpan()); + } + + i += sampleCountToProcess; + } + + Debug.Assert(sourceSampleCountToProcess == i || !isStarving); + + state.WaveBufferConsumed = waveBufferConsumed; + state.Offset = offset; + state.PlayedSampleCount = playedSampleCount; + state.WaveBufferIndex = (uint)waveBufferIndex; + state.Fraction = fraction; + } + + ArrayPool.Shared.Return(tempBuffer); + } + + private static void ToFloatAvx(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.ConvertToVector256Single(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + private static void ToFloatSse2(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse2.ConvertToVector128Single(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToFloatSlow(Span output, ReadOnlySpan input, int sampleCount) + { + for (int i = 0; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + public static void ToFloat(Span output, ReadOnlySpan input, int sampleCount) + { + if (Avx.IsSupported) + { + ToFloatAvx(output, input, sampleCount); + } + else if (Sse2.IsSupported) + { + ToFloatSse2(output, input, sampleCount); + } + else + { + ToFloatSlow(output, input, sampleCount); + } + } + + public static void ToIntAvx(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.ConvertToVector256Int32(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + public static void ToIntSse2(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.ConvertToVector128Int32(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToIntSlow(Span output, ReadOnlySpan input, int sampleCount) + { + for (int i = 0; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + public static void ToInt(Span output, ReadOnlySpan input, int sampleCount) + { + if (Avx.IsSupported) + { + ToIntAvx(output, input, sampleCount); + } + else if (Sse2.IsSupported) + { + ToIntSse2(output, input, sampleCount); + } + else + { + ToIntSlow(output, input, sampleCount); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Effect/DecayDelay.cs b/Ryujinx.Audio.Renderer/Dsp/Effect/DecayDelay.cs new file mode 100644 index 00000000..6d885dbf --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Effect/DecayDelay.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DecayDelay : IDelayLine + { + private readonly IDelayLine _delayLine; + + public uint CurrentSampleCount => _delayLine.CurrentSampleCount; + + public uint SampleCountMax => _delayLine.SampleCountMax; + + private float _decayRate; + + public DecayDelay(IDelayLine delayLine) + { + _decayRate = 0.0f; + _delayLine = delayLine; + } + + public void SetDecayRate(float decayRate) + { + _decayRate = decayRate; + } + + public float Update(float value) + { + float delayLineValue = _delayLine.Read(); + float processedValue = value - (_decayRate * delayLineValue); + + return _delayLine.Update(processedValue) + processedValue * _decayRate; + } + + public void SetDelay(float delayTime) + { + _delayLine.SetDelay(delayTime); + } + + public float Read() + { + return _delayLine.Read(); + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return _delayLine.TapUnsafe(sampleIndex, offset); + } + + public float Tap(uint sampleIndex) + { + return _delayLine.Tap(sampleIndex); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Effect/DelayLine.cs b/Ryujinx.Audio.Renderer/Dsp/Effect/DelayLine.cs new file mode 100644 index 00000000..b443cd15 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Effect/DelayLine.cs @@ -0,0 +1,87 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DelayLine : IDelayLine + { + private float[] _workBuffer; + private uint _sampleRate; + private uint _currentSampleIndex; + private uint _lastSampleIndex; + + public uint CurrentSampleCount { get; private set; } + public uint SampleCountMax { get; private set; } + + public DelayLine(uint sampleRate, float delayTimeMax) + { + _sampleRate = sampleRate; + SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax); + _workBuffer = new float[SampleCountMax]; + + SetDelay(delayTimeMax); + } + + private void ConfigureDelay(uint targetSampleCount) + { + CurrentSampleCount = Math.Min(SampleCountMax, targetSampleCount); + _currentSampleIndex = 0; + _lastSampleIndex = CurrentSampleCount - 1; + } + + public void SetDelay(float delayTime) + { + ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime)); + } + + public float Read() + { + return _workBuffer[_currentSampleIndex]; + } + + public float Update(float value) + { + float output = Read(); + + _workBuffer[_currentSampleIndex++] = value; + + if (_currentSampleIndex >= _lastSampleIndex) + { + _currentSampleIndex = 0; + } + + return output; + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return IDelayLine.Tap(_workBuffer, (int)_currentSampleIndex, (int)sampleIndex + offset, (int)CurrentSampleCount); + } + + public float Tap(uint sampleIndex) + { + if (sampleIndex >= CurrentSampleCount) + { + sampleIndex = CurrentSampleCount - 1; + } + + return TapUnsafe(sampleIndex, -1); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Effect/DelayLineReverb3d.cs b/Ryujinx.Audio.Renderer/Dsp/Effect/DelayLineReverb3d.cs new file mode 100644 index 00000000..acdfdb4f --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Effect/DelayLineReverb3d.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DelayLine3d : IDelayLine + { + private float[] _workBuffer; + private uint _sampleRate; + private uint _currentSampleIndex; + private uint _lastSampleIndex; + + public uint CurrentSampleCount { get; private set; } + public uint SampleCountMax { get; private set; } + + public DelayLine3d(uint sampleRate, float delayTimeMax) + { + _sampleRate = sampleRate; + SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax); + _workBuffer = new float[SampleCountMax + 1]; + + SetDelay(delayTimeMax); + } + + private void ConfigureDelay(uint targetSampleCount) + { + if (SampleCountMax >= targetSampleCount) + { + CurrentSampleCount = targetSampleCount; + _lastSampleIndex = (_currentSampleIndex + targetSampleCount) % (SampleCountMax + 1); + } + } + + public void SetDelay(float delayTime) + { + ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime)); + } + + public float Read() + { + return _workBuffer[_currentSampleIndex]; + } + + public float Update(float value) + { + Debug.Assert(!float.IsNaN(value) && !float.IsInfinity(value)); + + _workBuffer[_lastSampleIndex++] = value; + + float output = Read(); + + _currentSampleIndex++; + + if (_currentSampleIndex >= SampleCountMax) + { + _currentSampleIndex = 0; + } + + if (_lastSampleIndex >= SampleCountMax) + { + _lastSampleIndex = 0; + } + + return output; + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return IDelayLine.Tap(_workBuffer, (int)_lastSampleIndex, (int)sampleIndex + offset, (int)SampleCountMax + 1); + } + + public float Tap(uint sampleIndex) + { + return TapUnsafe(sampleIndex, -1); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/Effect/IDelayLine.cs b/Ryujinx.Audio.Renderer/Dsp/Effect/IDelayLine.cs new file mode 100644 index 00000000..d44f5596 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/Effect/IDelayLine.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public interface IDelayLine + { + uint CurrentSampleCount { get; } + uint SampleCountMax { get; } + + void SetDelay(float delayTime); + float Read(); + float Update(float value); + + float TapUnsafe(uint sampleIndex, int offset); + float Tap(uint sampleIndex); + + public static float Tap(Span workBuffer, int baseIndex, int sampleIndex, int delaySampleCount) + { + int targetIndex = baseIndex - sampleIndex; + + if (targetIndex < 0) + { + targetIndex += delaySampleCount; + } + + return workBuffer[targetIndex]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint GetSampleCount(uint sampleRate, float delayTime) + { + return (uint)MathF.Round(sampleRate * delayTime); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/FixedPointHelper.cs b/Ryujinx.Audio.Renderer/Dsp/FixedPointHelper.cs new file mode 100644 index 00000000..2983c4dc --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/FixedPointHelper.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class FixedPointHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ToInt(long value, int qBits) + { + return (int)(value >> qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ToFloat(long value, int qBits) + { + return (float)value / (1 << qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ToFixed(float value, int qBits) + { + return (int)(value * (1 << qBits)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int RoundUpAndToInt(long value, int qBits) + { + int half = 1 << (qBits - 1); + + return ToInt(value + half, qBits); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/FloatingPointHelper.cs b/Ryujinx.Audio.Renderer/Dsp/FloatingPointHelper.cs new file mode 100644 index 00000000..c3da9d4d --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/FloatingPointHelper.cs @@ -0,0 +1,84 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class FloatingPointHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MultiplyRoundDown(float a, float b) + { + return RoundDown(a * b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RoundDown(float a) + { + return MathF.Round(a, 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RoundUp(float a) + { + return MathF.Round(a); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MultiplyRoundUp(float a, float b) + { + return RoundUp(a * b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Pow10(float x) + { + // NOTE: Nintendo implementation uses Q15 and a LUT for this, we don't. + // As such, we support the same ranges as Nintendo to avoid unexpected behaviours. + if (x >= 0.0f) + { + return 1.0f; + } + else if (x <= -5.3f) + { + return 0.0f; + } + + return MathF.Pow(10, x); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DegreesToRadians(float degrees) + { + return degrees * MathF.PI / 180.0f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Cos(float value) + { + return MathF.Cos(DegreesToRadians(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Sin(float value) + { + return MathF.Sin(DegreesToRadians(value)); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/PcmHelper.cs b/Ryujinx.Audio.Renderer/Dsp/PcmHelper.cs new file mode 100644 index 00000000..395a2c1a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/PcmHelper.cs @@ -0,0 +1,95 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class PcmHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetCountToDecode(int startSampleOffset, int endSampleOffset, int offset, int count) + { + return Math.Min(count, endSampleOffset - startSampleOffset - offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong GetBufferOffset(int startSampleOffset, int offset, int channelCount) where T : unmanaged + { + return (ulong)(Unsafe.SizeOf() * channelCount * (startSampleOffset + offset)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBufferSize(int startSampleOffset, int endSampleOffset, int offset, int count) where T : unmanaged + { + return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Decode(Span output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount) + { + if (input.IsEmpty || endSampleOffset < startSampleOffset) + { + return 0; + } + + int decodedCount = input.Length / channelCount; + + for (int i = 0; i < decodedCount; i++) + { + output[i] = input[i * channelCount + channelIndex]; + } + + return decodedCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Decode(Span output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount) + { + if (input.IsEmpty || endSampleOffset < startSampleOffset) + { + return 0; + } + + int decodedCount = input.Length / channelCount; + + for (int i = 0; i < decodedCount; i++) + { + output[i] = (short)(input[i * channelCount + channelIndex] * short.MaxValue); + } + + return decodedCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short Saturate(float value) + { + if (value > short.MaxValue) + { + return short.MaxValue; + } + + if (value < short.MinValue) + { + return short.MinValue; + } + + return (short)value; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/ResamplerHelper.cs b/Ryujinx.Audio.Renderer/Dsp/ResamplerHelper.cs new file mode 100644 index 00000000..0d40bbe9 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/ResamplerHelper.cs @@ -0,0 +1,624 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class ResamplerHelper + { + #region "Default Quality Lookup Tables" + private static short[] _normalCurveLut0 = new short[] + { + 6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22, + 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48, + 5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77, + 5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109, + 4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147, + 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190, + 3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241, + 3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300, + 3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369, + 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449, + 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541, + 2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647, + 2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766, + 1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901, + 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052, + 1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221, + 1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407, + 1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613, + 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838, + 766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083, + 647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348, + 541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635, + 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942, + 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269, + 300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618, + 241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987, + 190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377, + 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785, + 109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213, + 77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659, + 48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121, + 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600 + }; + + private static short[] _normalCurveLut1 = new short[] + { + -68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36, + -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80, + -990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128, + -1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180, + -1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240, + -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307, + -1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382, + -2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468, + -2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563, + -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668, + -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783, + -2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907, + -1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039, + -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176, + -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317, + -1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459, + -1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599, + -1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732, + -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855, + -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962, + -907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049, + -783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111, + -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141, + -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133, + -468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083, + -382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984, + -307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830, + -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617, + -180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338, + -128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990, + -80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568, + -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68 + }; + + private static short[] _normalCurveLut2 = new short[] + { + 3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42, + 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58, + 2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76, + 1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98, + 1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121, + 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146, + 829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174, + 583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202, + 371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230, + 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258, + 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283, + -71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306, + -163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325, + -234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337, + -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341, + -317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336, + -336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317, + -341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284, + -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234, + -325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163, + -306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71, + -283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47, + -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194, + -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371, + -202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583, + -174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829, + -146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115, + -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442, + -98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813, + -76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227, + -58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688, + -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195 + }; + #endregion + + #region "High Quality Lookup Tables" + private static short[] _highCurveLut0 = new short[] + { + -582, -23, 8740, 16386, 8833, 8, -590, 0, -573, -54, 8647, 16385, 8925, 40, -598, -1, + -565, -84, 8555, 16383, 9018, 72, -606, -1, -557, -113, 8462, 16379, 9110, 105, -614, -2, + -549, -142, 8370, 16375, 9203, 139, -622, -2, -541, -170, 8277, 16369, 9295, 173, -630, -3, + -533, -198, 8185, 16362, 9387, 208, -638, -4, -525, -225, 8093, 16354, 9480, 244, -646, -5, + -516, -251, 8000, 16344, 9572, 280, -654, -5, -508, -277, 7908, 16334, 9664, 317, -662, -6, + -500, -302, 7816, 16322, 9756, 355, -670, -7, -492, -327, 7724, 16310, 9847, 393, -678, -8, + -484, -351, 7632, 16296, 9939, 432, -686, -9, -476, -374, 7540, 16281, 10030, 471, -694, -10, + -468, -397, 7449, 16265, 10121, 511, -702, -11, -460, -419, 7357, 16247, 10212, 552, -709, -13, + -452, -441, 7266, 16229, 10303, 593, -717, -14, -445, -462, 7175, 16209, 10394, 635, -724, -15, + -437, -483, 7084, 16189, 10484, 678, -732, -16, -429, -503, 6994, 16167, 10574, 722, -739, -18, + -421, -523, 6903, 16144, 10664, 766, -747, -19, -414, -542, 6813, 16120, 10754, 810, -754, -21, + -406, -560, 6723, 16095, 10843, 856, -761, -22, -398, -578, 6633, 16068, 10932, 902, -768, -24, + -391, -596, 6544, 16041, 11021, 949, -775, -26, -383, -612, 6454, 16012, 11109, 996, -782, -27, + -376, -629, 6366, 15983, 11197, 1044, -789, -29, -368, -645, 6277, 15952, 11285, 1093, -796, -31, + -361, -660, 6189, 15920, 11372, 1142, -802, -33, -354, -675, 6100, 15887, 11459, 1192, -809, -35, + -347, -689, 6013, 15853, 11546, 1243, -815, -37, -339, -703, 5925, 15818, 11632, 1294, -821, -39, + -332, -717, 5838, 15782, 11718, 1346, -827, -41, -325, -730, 5751, 15745, 11803, 1399, -833, -43, + -318, -742, 5665, 15707, 11888, 1452, -839, -46, -312, -754, 5579, 15668, 11973, 1506, -845, -48, + -305, -766, 5493, 15627, 12057, 1561, -850, -50, -298, -777, 5408, 15586, 12140, 1616, -855, -53, + -291, -787, 5323, 15544, 12224, 1672, -861, -56, -285, -798, 5239, 15500, 12306, 1729, -866, -58, + -278, -807, 5155, 15456, 12388, 1786, -871, -61, -272, -817, 5071, 15410, 12470, 1844, -875, -64, + -265, -826, 4988, 15364, 12551, 1902, -880, -67, -259, -834, 4905, 15317, 12631, 1962, -884, -70, + -253, -842, 4823, 15268, 12711, 2022, -888, -73, -247, -850, 4741, 15219, 12790, 2082, -892, -76, + -241, -857, 4659, 15168, 12869, 2143, -896, -79, -235, -864, 4578, 15117, 12947, 2205, -899, -82, + -229, -870, 4498, 15065, 13025, 2267, -903, -85, -223, -876, 4417, 15012, 13102, 2331, -906, -89, + -217, -882, 4338, 14958, 13178, 2394, -909, -92, -211, -887, 4259, 14903, 13254, 2459, -911, -96, + -206, -892, 4180, 14847, 13329, 2523, -914, -100, -200, -896, 4102, 14790, 13403, 2589, -916, -103, + -195, -900, 4024, 14732, 13477, 2655, -918, -107, -190, -904, 3947, 14673, 13550, 2722, -919, -111, + -184, -908, 3871, 14614, 13622, 2789, -921, -115, -179, -911, 3795, 14553, 13693, 2857, -922, -119, + -174, -913, 3719, 14492, 13764, 2926, -923, -123, -169, -916, 3644, 14430, 13834, 2995, -923, -127, + -164, -918, 3570, 14367, 13904, 3065, -924, -132, -159, -920, 3496, 14303, 13972, 3136, -924, -136, + -154, -921, 3423, 14239, 14040, 3207, -924, -140, -150, -922, 3350, 14173, 14107, 3278, -923, -145, + -145, -923, 3278, 14107, 14173, 3350, -922, -150, -140, -924, 3207, 14040, 14239, 3423, -921, -154, + -136, -924, 3136, 13972, 14303, 3496, -920, -159, -132, -924, 3065, 13904, 14367, 3570, -918, -164, + -127, -923, 2995, 13834, 14430, 3644, -916, -169, -123, -923, 2926, 13764, 14492, 3719, -913, -174, + -119, -922, 2857, 13693, 14553, 3795, -911, -179, -115, -921, 2789, 13622, 14614, 3871, -908, -184, + -111, -919, 2722, 13550, 14673, 3947, -904, -190, -107, -918, 2655, 13477, 14732, 4024, -900, -195, + -103, -916, 2589, 13403, 14790, 4102, -896, -200, -100, -914, 2523, 13329, 14847, 4180, -892, -206, + -96, -911, 2459, 13254, 14903, 4259, -887, -211, -92, -909, 2394, 13178, 14958, 4338, -882, -217, + -89, -906, 2331, 13102, 15012, 4417, -876, -223, -85, -903, 2267, 13025, 15065, 4498, -870, -229, + -82, -899, 2205, 12947, 15117, 4578, -864, -235, -79, -896, 2143, 12869, 15168, 4659, -857, -241, + -76, -892, 2082, 12790, 15219, 4741, -850, -247, -73, -888, 2022, 12711, 15268, 4823, -842, -253, + -70, -884, 1962, 12631, 15317, 4905, -834, -259, -67, -880, 1902, 12551, 15364, 4988, -826, -265, + -64, -875, 1844, 12470, 15410, 5071, -817, -272, -61, -871, 1786, 12388, 15456, 5155, -807, -278, + -58, -866, 1729, 12306, 15500, 5239, -798, -285, -56, -861, 1672, 12224, 15544, 5323, -787, -291, + -53, -855, 1616, 12140, 15586, 5408, -777, -298, -50, -850, 1561, 12057, 15627, 5493, -766, -305, + -48, -845, 1506, 11973, 15668, 5579, -754, -312, -46, -839, 1452, 11888, 15707, 5665, -742, -318, + -43, -833, 1399, 11803, 15745, 5751, -730, -325, -41, -827, 1346, 11718, 15782, 5838, -717, -332, + -39, -821, 1294, 11632, 15818, 5925, -703, -339, -37, -815, 1243, 11546, 15853, 6013, -689, -347, + -35, -809, 1192, 11459, 15887, 6100, -675, -354, -33, -802, 1142, 11372, 15920, 6189, -660, -361, + -31, -796, 1093, 11285, 15952, 6277, -645, -368, -29, -789, 1044, 11197, 15983, 6366, -629, -376, + -27, -782, 996, 11109, 16012, 6454, -612, -383, -26, -775, 949, 11021, 16041, 6544, -596, -391, + -24, -768, 902, 10932, 16068, 6633, -578, -398, -22, -761, 856, 10843, 16095, 6723, -560, -406, + -21, -754, 810, 10754, 16120, 6813, -542, -414, -19, -747, 766, 10664, 16144, 6903, -523, -421, + -18, -739, 722, 10574, 16167, 6994, -503, -429, -16, -732, 678, 10484, 16189, 7084, -483, -437, + -15, -724, 635, 10394, 16209, 7175, -462, -445, -14, -717, 593, 10303, 16229, 7266, -441, -452, + -13, -709, 552, 10212, 16247, 7357, -419, -460, -11, -702, 511, 10121, 16265, 7449, -397, -468, + -10, -694, 471, 10030, 16281, 7540, -374, -476, -9, -686, 432, 9939, 16296, 7632, -351, -484, + -8, -678, 393, 9847, 16310, 7724, -327, -492, -7, -670, 355, 9756, 16322, 7816, -302, -500, + -6, -662, 317, 9664, 16334, 7908, -277, -508, -5, -654, 280, 9572, 16344, 8000, -251, -516, + -5, -646, 244, 9480, 16354, 8093, -225, -525, -4, -638, 208, 9387, 16362, 8185, -198, -533, + -3, -630, 173, 9295, 16369, 8277, -170, -541, -2, -622, 139, 9203, 16375, 8370, -142, -549, + -2, -614, 105, 9110, 16379, 8462, -113, -557, -1, -606, 72, 9018, 16383, 8555, -84, -565, + -1, -598, 40, 8925, 16385, 8647, -54, -573, 0, -590, 8, 8833, 16386, 8740, -23, -582, + }; + + private static short[] _highCurveLut1 = new short[] + { + -12, 47, -134, 32767, 81, -16, 2, 0, -26, 108, -345, 32760, 301, -79, 17, -1, + -40, 168, -552, 32745, 526, -144, 32, -2, -53, 226, -753, 32723, 755, -210, 47, -3, + -66, 284, -950, 32694, 989, -277, 63, -5, -78, 340, -1143, 32658, 1226, -346, 79, -6, + -90, 394, -1331, 32615, 1469, -415, 96, -8, -101, 447, -1514, 32564, 1715, -486, 113, -9, + -112, 499, -1692, 32506, 1966, -557, 130, -11, -123, 550, -1865, 32441, 2221, -630, 148, -13, + -133, 599, -2034, 32369, 2480, -703, 166, -14, -143, 646, -2198, 32290, 2743, -778, 185, -16, + -152, 693, -2357, 32204, 3010, -853, 204, -18, -162, 738, -2512, 32110, 3281, -929, 223, -20, + -170, 781, -2662, 32010, 3555, -1007, 242, -23, -178, 823, -2807, 31903, 3834, -1084, 262, -25, + -186, 864, -2947, 31789, 4116, -1163, 282, -27, -194, 903, -3082, 31668, 4403, -1242, 303, -30, + -201, 940, -3213, 31540, 4692, -1322, 323, -32, -207, 977, -3339, 31406, 4985, -1403, 344, -35, + -214, 1011, -3460, 31265, 5282, -1484, 365, -37, -220, 1045, -3577, 31117, 5582, -1566, 387, -40, + -225, 1077, -3688, 30963, 5885, -1648, 409, -43, -230, 1107, -3796, 30802, 6191, -1730, 431, -46, + -235, 1136, -3898, 30635, 6501, -1813, 453, -49, -240, 1164, -3996, 30462, 6813, -1896, 475, -52, + -244, 1190, -4089, 30282, 7128, -1980, 498, -55, -247, 1215, -4178, 30097, 7446, -2064, 520, -58, + -251, 1239, -4262, 29905, 7767, -2148, 543, -62, -254, 1261, -4342, 29707, 8091, -2231, 566, -65, + -257, 1281, -4417, 29503, 8416, -2315, 589, -69, -259, 1301, -4488, 29293, 8745, -2399, 613, -72, + -261, 1319, -4555, 29078, 9075, -2483, 636, -76, -263, 1336, -4617, 28857, 9408, -2567, 659, -80, + -265, 1351, -4674, 28631, 9743, -2651, 683, -83, -266, 1365, -4728, 28399, 10080, -2734, 706, -87, + -267, 1378, -4777, 28161, 10418, -2817, 730, -91, -267, 1389, -4822, 27919, 10759, -2899, 753, -95, + -268, 1400, -4863, 27671, 11100, -2981, 777, -99, -268, 1409, -4900, 27418, 11444, -3063, 800, -103, + -268, 1416, -4933, 27161, 11789, -3144, 824, -107, -267, 1423, -4962, 26898, 12135, -3224, 847, -112, + -267, 1428, -4987, 26631, 12482, -3303, 870, -116, -266, 1433, -5008, 26359, 12830, -3382, 893, -120, + -265, 1436, -5026, 26083, 13179, -3460, 916, -125, -264, 1438, -5039, 25802, 13529, -3537, 939, -129, + -262, 1438, -5049, 25517, 13880, -3613, 962, -133, -260, 1438, -5055, 25228, 14231, -3687, 984, -138, + -258, 1437, -5058, 24935, 14582, -3761, 1006, -142, -256, 1435, -5058, 24639, 14934, -3833, 1028, -147, + -254, 1431, -5053, 24338, 15286, -3904, 1049, -151, -252, 1427, -5046, 24034, 15638, -3974, 1071, -155, + -249, 1422, -5035, 23726, 15989, -4042, 1091, -160, -246, 1416, -5021, 23415, 16341, -4109, 1112, -164, + -243, 1408, -5004, 23101, 16691, -4174, 1132, -169, -240, 1400, -4984, 22783, 17042, -4237, 1152, -173, + -237, 1392, -4960, 22463, 17392, -4299, 1171, -178, -234, 1382, -4934, 22140, 17740, -4358, 1190, -182, + -230, 1371, -4905, 21814, 18088, -4416, 1209, -186, -227, 1360, -4873, 21485, 18435, -4472, 1226, -191, + -223, 1348, -4839, 21154, 18781, -4526, 1244, -195, -219, 1335, -4801, 20821, 19125, -4578, 1260, -199, + -215, 1321, -4761, 20486, 19468, -4627, 1277, -203, -211, 1307, -4719, 20148, 19809, -4674, 1292, -207, + -207, 1292, -4674, 19809, 20148, -4719, 1307, -211, -203, 1277, -4627, 19468, 20486, -4761, 1321, -215, + -199, 1260, -4578, 19125, 20821, -4801, 1335, -219, -195, 1244, -4526, 18781, 21154, -4839, 1348, -223, + -191, 1226, -4472, 18435, 21485, -4873, 1360, -227, -186, 1209, -4416, 18088, 21814, -4905, 1371, -230, + -182, 1190, -4358, 17740, 22140, -4934, 1382, -234, -178, 1171, -4299, 17392, 22463, -4960, 1392, -237, + -173, 1152, -4237, 17042, 22783, -4984, 1400, -240, -169, 1132, -4174, 16691, 23101, -5004, 1408, -243, + -164, 1112, -4109, 16341, 23415, -5021, 1416, -246, -160, 1091, -4042, 15989, 23726, -5035, 1422, -249, + -155, 1071, -3974, 15638, 24034, -5046, 1427, -252, -151, 1049, -3904, 15286, 24338, -5053, 1431, -254, + -147, 1028, -3833, 14934, 24639, -5058, 1435, -256, -142, 1006, -3761, 14582, 24935, -5058, 1437, -258, + -138, 984, -3687, 14231, 25228, -5055, 1438, -260, -133, 962, -3613, 13880, 25517, -5049, 1438, -262, + -129, 939, -3537, 13529, 25802, -5039, 1438, -264, -125, 916, -3460, 13179, 26083, -5026, 1436, -265, + -120, 893, -3382, 12830, 26359, -5008, 1433, -266, -116, 870, -3303, 12482, 26631, -4987, 1428, -267, + -112, 847, -3224, 12135, 26898, -4962, 1423, -267, -107, 824, -3144, 11789, 27161, -4933, 1416, -268, + -103, 800, -3063, 11444, 27418, -4900, 1409, -268, -99, 777, -2981, 11100, 27671, -4863, 1400, -268, + -95, 753, -2899, 10759, 27919, -4822, 1389, -267, -91, 730, -2817, 10418, 28161, -4777, 1378, -267, + -87, 706, -2734, 10080, 28399, -4728, 1365, -266, -83, 683, -2651, 9743, 28631, -4674, 1351, -265, + -80, 659, -2567, 9408, 28857, -4617, 1336, -263, -76, 636, -2483, 9075, 29078, -4555, 1319, -261, + -72, 613, -2399, 8745, 29293, -4488, 1301, -259, -69, 589, -2315, 8416, 29503, -4417, 1281, -257, + -65, 566, -2231, 8091, 29707, -4342, 1261, -254, -62, 543, -2148, 7767, 29905, -4262, 1239, -251, + -58, 520, -2064, 7446, 30097, -4178, 1215, -247, -55, 498, -1980, 7128, 30282, -4089, 1190, -244, + -52, 475, -1896, 6813, 30462, -3996, 1164, -240, -49, 453, -1813, 6501, 30635, -3898, 1136, -235, + -46, 431, -1730, 6191, 30802, -3796, 1107, -230, -43, 409, -1648, 5885, 30963, -3688, 1077, -225, + -40, 387, -1566, 5582, 31117, -3577, 1045, -220, -37, 365, -1484, 5282, 31265, -3460, 1011, -214, + -35, 344, -1403, 4985, 31406, -3339, 977, -207, -32, 323, -1322, 4692, 31540, -3213, 940, -201, + -30, 303, -1242, 4403, 31668, -3082, 903, -194, -27, 282, -1163, 4116, 31789, -2947, 864, -186, + -25, 262, -1084, 3834, 31903, -2807, 823, -178, -23, 242, -1007, 3555, 32010, -2662, 781, -170, + -20, 223, -929, 3281, 32110, -2512, 738, -162, -18, 204, -853, 3010, 32204, -2357, 693, -152, + -16, 185, -778, 2743, 32290, -2198, 646, -143, -14, 166, -703, 2480, 32369, -2034, 599, -133, + -13, 148, -630, 2221, 32441, -1865, 550, -123, -11, 130, -557, 1966, 32506, -1692, 499, -112, + -9, 113, -486, 1715, 32564, -1514, 447, -101, -8, 96, -415, 1469, 32615, -1331, 394, -90, + -6, 79, -346, 1226, 32658, -1143, 340, -78, -5, 63, -277, 989, 32694, -950, 284, -66, + -3, 47, -210, 755, 32723, -753, 226, -53, -2, 32, -144, 526, 32745, -552, 168, -40, + -1, 17, -79, 301, 32760, -345, 108, -26, 0, 2, -16, 81, 32767, -134, 47, -12, + }; + + private static short[] _highCurveLut2 = new short[] + { + 418, -2538, 6118, 24615, 6298, -2563, 417, 0, 420, -2512, 5939, 24611, 6479, -2588, 415, 1, + 421, -2485, 5761, 24605, 6662, -2612, 412, 2, 422, -2458, 5585, 24595, 6846, -2635, 409, 3, + 423, -2430, 5410, 24582, 7030, -2658, 406, 4, 423, -2402, 5236, 24565, 7216, -2680, 403, 5, + 423, -2373, 5064, 24546, 7403, -2701, 399, 6, 423, -2343, 4893, 24523, 7591, -2721, 395, 7, + 423, -2313, 4724, 24496, 7780, -2741, 391, 8, 422, -2283, 4556, 24467, 7970, -2759, 386, 9, + 421, -2252, 4390, 24434, 8161, -2777, 381, 11, 420, -2221, 4225, 24398, 8353, -2794, 376, 12, + 419, -2190, 4062, 24359, 8545, -2810, 370, 14, 418, -2158, 3900, 24316, 8739, -2825, 364, 15, + 416, -2126, 3740, 24271, 8933, -2839, 358, 17, 414, -2093, 3582, 24222, 9127, -2851, 351, 19, + 412, -2060, 3425, 24170, 9323, -2863, 344, 21, 410, -2027, 3270, 24115, 9519, -2874, 336, 22, + 407, -1993, 3117, 24056, 9715, -2884, 328, 24, 404, -1960, 2966, 23995, 9912, -2893, 319, 26, + 402, -1926, 2816, 23930, 10110, -2900, 311, 29, 398, -1892, 2668, 23863, 10308, -2907, 301, 31, + 395, -1858, 2522, 23792, 10506, -2912, 292, 33, 392, -1823, 2378, 23718, 10705, -2916, 282, 35, + 389, -1789, 2235, 23641, 10904, -2919, 271, 38, 385, -1754, 2095, 23561, 11103, -2920, 261, 40, + 381, -1719, 1956, 23478, 11303, -2921, 249, 43, 377, -1684, 1819, 23393, 11502, -2920, 238, 45, + 373, -1649, 1684, 23304, 11702, -2917, 225, 48, 369, -1615, 1551, 23212, 11902, -2914, 213, 51, + 365, -1580, 1420, 23118, 12102, -2909, 200, 54, 361, -1545, 1291, 23020, 12302, -2902, 186, 57, + 356, -1510, 1163, 22920, 12502, -2895, 173, 60, 352, -1475, 1038, 22817, 12702, -2885, 158, 63, + 347, -1440, 915, 22711, 12901, -2875, 143, 66, 342, -1405, 793, 22602, 13101, -2863, 128, 69, + 338, -1370, 674, 22491, 13300, -2849, 113, 73, 333, -1335, 557, 22377, 13499, -2834, 97, 76, + 328, -1301, 441, 22260, 13698, -2817, 80, 80, 323, -1266, 328, 22141, 13896, -2799, 63, 83, + 318, -1232, 217, 22019, 14094, -2779, 46, 87, 313, -1197, 107, 21894, 14291, -2758, 28, 91, + 307, -1163, 0, 21767, 14488, -2735, 9, 95, 302, -1129, -105, 21637, 14684, -2710, -9, 98, + 297, -1096, -208, 21506, 14879, -2684, -29, 102, 292, -1062, -310, 21371, 15074, -2656, -48, 106, + 286, -1029, -409, 21234, 15268, -2626, -69, 111, 281, -996, -506, 21095, 15461, -2595, -89, 115, + 276, -963, -601, 20954, 15654, -2562, -110, 119, 270, -930, -694, 20810, 15846, -2527, -132, 123, + 265, -898, -785, 20664, 16036, -2490, -154, 128, 260, -866, -874, 20516, 16226, -2452, -176, 132, + 254, -834, -961, 20366, 16415, -2411, -199, 137, 249, -803, -1046, 20213, 16602, -2369, -222, 141, + 243, -771, -1129, 20059, 16789, -2326, -246, 146, 238, -740, -1209, 19902, 16974, -2280, -270, 151, + 233, -710, -1288, 19744, 17158, -2232, -294, 156, 227, -680, -1365, 19583, 17341, -2183, -319, 160, + 222, -650, -1440, 19421, 17523, -2132, -345, 165, 217, -620, -1513, 19257, 17703, -2079, -370, 170, + 211, -591, -1583, 19091, 17882, -2023, -396, 175, 206, -562, -1652, 18923, 18059, -1966, -423, 180, + 201, -533, -1719, 18754, 18235, -1907, -450, 185, 196, -505, -1784, 18582, 18410, -1847, -477, 191, + 191, -477, -1847, 18410, 18582, -1784, -505, 196, 185, -450, -1907, 18235, 18754, -1719, -533, 201, + 180, -423, -1966, 18059, 18923, -1652, -562, 206, 175, -396, -2023, 17882, 19091, -1583, -591, 211, + 170, -370, -2079, 17703, 19257, -1513, -620, 217, 165, -345, -2132, 17523, 19421, -1440, -650, 222, + 160, -319, -2183, 17341, 19583, -1365, -680, 227, 156, -294, -2232, 17158, 19744, -1288, -710, 233, + 151, -270, -2280, 16974, 19902, -1209, -740, 238, 146, -246, -2326, 16789, 20059, -1129, -771, 243, + 141, -222, -2369, 16602, 20213, -1046, -803, 249, 137, -199, -2411, 16415, 20366, -961, -834, 254, + 132, -176, -2452, 16226, 20516, -874, -866, 260, 128, -154, -2490, 16036, 20664, -785, -898, 265, + 123, -132, -2527, 15846, 20810, -694, -930, 270, 119, -110, -2562, 15654, 20954, -601, -963, 276, + 115, -89, -2595, 15461, 21095, -506, -996, 281, 111, -69, -2626, 15268, 21234, -409, -1029, 286, + 106, -48, -2656, 15074, 21371, -310, -1062, 292, 102, -29, -2684, 14879, 21506, -208, -1096, 297, + 98, -9, -2710, 14684, 21637, -105, -1129, 302, 95, 9, -2735, 14488, 21767, 0, -1163, 307, + 91, 28, -2758, 14291, 21894, 107, -1197, 313, 87, 46, -2779, 14094, 22019, 217, -1232, 318, + 83, 63, -2799, 13896, 22141, 328, -1266, 323, 80, 80, -2817, 13698, 22260, 441, -1301, 328, + 76, 97, -2834, 13499, 22377, 557, -1335, 333, 73, 113, -2849, 13300, 22491, 674, -1370, 338, + 69, 128, -2863, 13101, 22602, 793, -1405, 342, 66, 143, -2875, 12901, 22711, 915, -1440, 347, + 63, 158, -2885, 12702, 22817, 1038, -1475, 352, 60, 173, -2895, 12502, 22920, 1163, -1510, 356, + 57, 186, -2902, 12302, 23020, 1291, -1545, 361, 54, 200, -2909, 12102, 23118, 1420, -1580, 365, + 51, 213, -2914, 11902, 23212, 1551, -1615, 369, 48, 225, -2917, 11702, 23304, 1684, -1649, 373, + 45, 238, -2920, 11502, 23393, 1819, -1684, 377, 43, 249, -2921, 11303, 23478, 1956, -1719, 381, + 40, 261, -2920, 11103, 23561, 2095, -1754, 385, 38, 271, -2919, 10904, 23641, 2235, -1789, 389, + 35, 282, -2916, 10705, 23718, 2378, -1823, 392, 33, 292, -2912, 10506, 23792, 2522, -1858, 395, + 31, 301, -2907, 10308, 23863, 2668, -1892, 398, 29, 311, -2900, 10110, 23930, 2816, -1926, 402, + 26, 319, -2893, 9912, 23995, 2966, -1960, 404, 24, 328, -2884, 9715, 24056, 3117, -1993, 407, + 22, 336, -2874, 9519, 24115, 3270, -2027, 410, 21, 344, -2863, 9323, 24170, 3425, -2060, 412, + 19, 351, -2851, 9127, 24222, 3582, -2093, 414, 17, 358, -2839, 8933, 24271, 3740, -2126, 416, + 15, 364, -2825, 8739, 24316, 3900, -2158, 418, 14, 370, -2810, 8545, 24359, 4062, -2190, 419, + 12, 376, -2794, 8353, 24398, 4225, -2221, 420, 11, 381, -2777, 8161, 24434, 4390, -2252, 421, + 9, 386, -2759, 7970, 24467, 4556, -2283, 422, 8, 391, -2741, 7780, 24496, 4724, -2313, 423, + 7, 395, -2721, 7591, 24523, 4893, -2343, 423, 6, 399, -2701, 7403, 24546, 5064, -2373, 423, + 5, 403, -2680, 7216, 24565, 5236, -2402, 423, 4, 406, -2658, 7030, 24582, 5410, -2430, 423, + 3, 409, -2635, 6846, 24595, 5585, -2458, 422, 2, 412, -2612, 6662, 24605, 5761, -2485, 421, + 1, 415, -2588, 6479, 24611, 5939, -2512, 420, 0, 417, -2563, 6298, 24615, 6118, -2538, 418, + }; + #endregion + + private static float[] _normalCurveLut0F; + private static float[] _normalCurveLut1F; + private static float[] _normalCurveLut2F; + + private static float[] _highCurveLut0F; + private static float[] _highCurveLut1F; + private static float[] _highCurveLut2F; + + static ResamplerHelper() + { + _normalCurveLut0F = _normalCurveLut0.Select(x => x / 32768f).ToArray(); + _normalCurveLut1F = _normalCurveLut1.Select(x => x / 32768f).ToArray(); + _normalCurveLut2F = _normalCurveLut2.Select(x => x / 32768f).ToArray(); + + _highCurveLut0F = _highCurveLut0.Select(x => x / 32768f).ToArray(); + _highCurveLut1F = _highCurveLut1.Select(x => x / 32768f).ToArray(); + _highCurveLut2F = _highCurveLut2.Select(x => x / 32768f).ToArray(); + } + + private const int FixedPointPrecision = 15; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Resample(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount, SampleRateConversionQuality srcQuality, bool needPitch) + { + switch (srcQuality) + { + case SampleRateConversionQuality.Default: + ResampleDefaultQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount, needPitch); + break; + case SampleRateConversionQuality.Low: + ResampleLowQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount); + break; + case SampleRateConversionQuality.High: + ResampleHighQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount); + break; + default: + throw new NotImplementedException($"{srcQuality}"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetDefaultParameter(float ratio) + { + if (ratio <= 1.0f) + { + return _normalCurveLut1F; + } + else if (ratio > 1.333313f) + { + return _normalCurveLut0F; + } + + return _normalCurveLut2F; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe static void ResampleDefaultQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount, bool needPitch) + { + ReadOnlySpan parameters = GetDefaultParameter(ratio); + + int inputBufferIndex = 0, i = 0; + + // TODO: REV8 fast path (when needPitch == false the input index progression is constant + we need SIMD) + + if (Sse41.IsSupported) + { + if (ratio == 1f) + { + fixed (short* pInput = inputBuffer) + { + fixed (float* pOutput = outputBuffer, pParameters = parameters) + { + Vector128 parameter = Sse.LoadVector128(pParameters); + + for (; i < (sampleCount & ~3); i += 4) + { + Vector128 intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i); + Vector128 intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1); + Vector128 intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2); + Vector128 intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3); + + Vector128 input0 = Sse2.ConvertToVector128Single(intInput0); + Vector128 input1 = Sse2.ConvertToVector128Single(intInput1); + Vector128 input2 = Sse2.ConvertToVector128Single(intInput2); + Vector128 input3 = Sse2.ConvertToVector128Single(intInput3); + + Vector128 mix0 = Sse.Multiply(input0, parameter); + Vector128 mix1 = Sse.Multiply(input1, parameter); + Vector128 mix2 = Sse.Multiply(input2, parameter); + Vector128 mix3 = Sse.Multiply(input3, parameter); + + Vector128 mix01 = Sse3.HorizontalAdd(mix0, mix1); + Vector128 mix23 = Sse3.HorizontalAdd(mix2, mix3); + + Vector128 mix0123 = Sse3.HorizontalAdd(mix01, mix23); + + Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123)); + } + } + } + + inputBufferIndex = i; + } + else + { + fixed (short* pInput = inputBuffer) + { + fixed (float* pOutput = outputBuffer, pParameters = parameters) + { + for (; i < (sampleCount & ~3); i += 4) + { + uint baseIndex0 = (uint)(fraction * 128) * 4; + uint inputIndex0 = (uint)inputBufferIndex; + + fraction += ratio; + + uint baseIndex1 = ((uint)(fraction * 128) & 127) * 4; + uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction; + + fraction += ratio; + + uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4; + uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction; + + fraction += ratio; + + uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4; + uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction; + + fraction += ratio; + inputBufferIndex += (int)fraction; + + // Only keep lower part (safe as fraction isn't supposed to be negative) + fraction -= (int)fraction; + + Vector128 parameter0 = Sse.LoadVector128(pParameters + baseIndex0); + Vector128 parameter1 = Sse.LoadVector128(pParameters + baseIndex1); + Vector128 parameter2 = Sse.LoadVector128(pParameters + baseIndex2); + Vector128 parameter3 = Sse.LoadVector128(pParameters + baseIndex3); + + Vector128 intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0); + Vector128 intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1); + Vector128 intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2); + Vector128 intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3); + + Vector128 input0 = Sse2.ConvertToVector128Single(intInput0); + Vector128 input1 = Sse2.ConvertToVector128Single(intInput1); + Vector128 input2 = Sse2.ConvertToVector128Single(intInput2); + Vector128 input3 = Sse2.ConvertToVector128Single(intInput3); + + Vector128 mix0 = Sse.Multiply(input0, parameter0); + Vector128 mix1 = Sse.Multiply(input1, parameter1); + Vector128 mix2 = Sse.Multiply(input2, parameter2); + Vector128 mix3 = Sse.Multiply(input3, parameter3); + + Vector128 mix01 = Sse3.HorizontalAdd(mix0, mix1); + Vector128 mix23 = Sse3.HorizontalAdd(mix2, mix3); + + Vector128 mix0123 = Sse3.HorizontalAdd(mix01, mix23); + + Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123)); + } + } + } + } + } + + for (; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 4; + ReadOnlySpan parameter = parameters.Slice(baseIndex, 4); + ReadOnlySpan currentInput = inputBuffer.Slice(inputBufferIndex, 4); + + outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] + + currentInput[1] * parameter[1] + + currentInput[2] * parameter[2] + + currentInput[3] * parameter[3]); + + fraction += ratio; + inputBufferIndex += (int)fraction; + + // Only keep lower part (safe as fraction isn't supposed to be negative) + fraction -= (int)fraction; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetHighParameter(float ratio) + { + if (ratio <= 1.0f) + { + return _highCurveLut1F; + } + else if (ratio > 1.333313f) + { + return _highCurveLut0F; + } + + return _highCurveLut2F; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ResampleHighQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount) + { + ReadOnlySpan parameters = GetHighParameter(ratio); + + int inputBufferIndex = 0; + + // TODO: fast path + + for (int i = 0; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 8; + ReadOnlySpan parameter = parameters.Slice(baseIndex, 8); + ReadOnlySpan currentInput = inputBuffer.Slice(inputBufferIndex, 8); + + outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] + + currentInput[1] * parameter[1] + + currentInput[2] * parameter[2] + + currentInput[3] * parameter[3] + + currentInput[4] * parameter[4] + + currentInput[5] * parameter[5] + + currentInput[6] * parameter[6] + + currentInput[7] * parameter[7]); + + fraction += ratio; + inputBufferIndex += (int)MathF.Truncate(fraction); + + fraction -= (int)fraction; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ResampleLowQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount) + { + int inputBufferIndex = 0; + + for (int i = 0; i < sampleCount; i++) + { + int outputData = inputBuffer[inputBufferIndex]; + + if (fraction > 1.0f) + { + outputData = inputBuffer[inputBufferIndex + 1]; + } + + outputBuffer[i] = outputData; + + fraction += ratio; + inputBufferIndex += (int)MathF.Truncate(fraction); + + fraction -= (int)fraction; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ResampleForUpsampler(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount) + { + // TODO: use a bandwidth filter to have better resampling. + int inputBufferIndex = 0; + + for (int i = 0; i < sampleCount; i++) + { + float outputData = inputBuffer[inputBufferIndex]; + + if (fraction > 1.0f) + { + outputData = inputBuffer[inputBufferIndex + 1]; + } + + outputBuffer[i] = outputData; + + fraction += ratio; + inputBufferIndex += (int)MathF.Truncate(fraction); + + fraction -= (int)fraction; + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/State/AdpcmLoopContext.cs b/Ryujinx.Audio.Renderer/Dsp/State/AdpcmLoopContext.cs new file mode 100644 index 00000000..0a218320 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/State/AdpcmLoopContext.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 6)] + public struct AdpcmLoopContext + { + public short PredScale; + public short History0; + public short History1; + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/State/AuxiliaryBufferHeader.cs b/Ryujinx.Audio.Renderer/Dsp/State/AuxiliaryBufferHeader.cs new file mode 100644 index 00000000..eebbac53 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/State/AuxiliaryBufferHeader.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Cpu; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x40)] + public struct AuxiliaryBufferHeader + { + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0xC)] + public struct AuxiliaryBufferInfo + { + private const uint ReadOffsetPosition = 0x0; + private const uint WriteOffsetPosition = 0x4; + + public uint ReadOffset; + public uint WriteOffset; + private uint _reserved; + + public static uint GetReadOffset(MemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + ReadOffsetPosition); + } + + public static uint GetWriteOffset(MemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + WriteOffsetPosition); + } + + public static void SetReadOffset(MemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + ReadOffsetPosition, value); + } + + public static void SetWriteOffset(MemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + WriteOffsetPosition, value); + } + } + + public AuxiliaryBufferInfo BufferInfo; + public unsafe fixed uint Unknown[13]; + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/State/BiquadFilterState.cs b/Ryujinx.Audio.Renderer/Dsp/State/BiquadFilterState.cs new file mode 100644 index 00000000..01886b8f --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/State/BiquadFilterState.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct BiquadFilterState + { + public float Z1; + public float Z2; + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/State/DelayState.cs b/Ryujinx.Audio.Renderer/Dsp/State/DelayState.cs new file mode 100644 index 00000000..c6275bef --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/State/DelayState.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public class DelayState + { + public DelayLine[] DelayLines { get; } + public float[] LowPassZ { get; set; } + public float FeedbackGain { get; private set; } + public float DelayFeedbackBaseGain { get; private set; } + public float DelayFeedbackCrossGain { get; private set; } + public float LowPassFeedbackGain { get; private set; } + public float LowPassBaseGain { get; private set; } + + private const int FixedPointPrecision = 14; + + public DelayState(ref DelayParameter parameter, ulong workBuffer) + { + DelayLines = new DelayLine[parameter.ChannelCount]; + LowPassZ = new float[parameter.ChannelCount]; + + uint sampleRate = (uint)FixedPointHelper.ToInt(parameter.SampleRate, FixedPointPrecision) / 1000; + + for (int i = 0; i < DelayLines.Length; i++) + { + DelayLines[i] = new DelayLine(sampleRate, parameter.DelayTimeMax); + DelayLines[i].SetDelay(parameter.DelayTime); + LowPassZ[0] = 0; + } + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref DelayParameter parameter) + { + FeedbackGain = FixedPointHelper.ToFloat(parameter.FeedbackGain, FixedPointPrecision) * 0.98f; + + float channelSpread = FixedPointHelper.ToFloat(parameter.ChannelSpread, FixedPointPrecision); + + DelayFeedbackBaseGain = (1.0f - channelSpread) * FeedbackGain; + + if (parameter.ChannelCount == 4 || parameter.ChannelCount == 6) + { + DelayFeedbackCrossGain = channelSpread * 0.5f * FeedbackGain; + } + else + { + DelayFeedbackCrossGain = channelSpread * FeedbackGain; + } + + LowPassFeedbackGain = 0.95f * FixedPointHelper.ToFloat(parameter.LowPassAmount, FixedPointPrecision); + LowPassBaseGain = 1.0f - LowPassFeedbackGain; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/State/Reverb3dState.cs b/Ryujinx.Audio.Renderer/Dsp/State/Reverb3dState.cs new file mode 100644 index 00000000..3b119b92 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/State/Reverb3dState.cs @@ -0,0 +1,136 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public class Reverb3dState + { + private readonly float[] FdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f }; + private readonly float[] FdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f }; + private readonly float[] DecayDelayMaxTimes1 = new float[4] { 17.0f, 13.0f, 9.0f, 7.0f }; + private readonly float[] DecayDelayMaxTimes2 = new float[4] { 19.0f, 11.0f, 10.0f, 6.0f }; + private readonly float[] EarlyDelayTimes = new float[20] { 0.017136f, 0.059154f, 0.16173f, 0.39019f, 0.42526f, 0.45541f, 0.68974f, 0.74591f, 0.83384f, 0.8595f, 0.0f, 0.075024f, 0.16879f, 0.2999f, 0.33744f, 0.3719f, 0.59901f, 0.71674f, 0.81786f, 0.85166f }; + public readonly float[] EarlyGain = new float[20] { 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f }; + + public IDelayLine[] FdnDelayLines { get; } + public DecayDelay[] DecayDelays1 { get; } + public DecayDelay[] DecayDelays2 { get; } + public IDelayLine PreDelayLine { get; } + public IDelayLine BackLeftDelayLine { get; } + public float DryGain { get; private set; } + public uint[] EarlyDelayTime { get; private set; } + public float PreviousPreDelayValue { get; set; } + public float PreviousPreDelayGain { get; private set; } + public float TargetPreDelayGain { get; private set; } + public float EarlyReflectionsGain { get; private set; } + public float LateReverbGain { get; private set; } + public uint ReflectionDelayTime { get; private set; } + public float EchoLateReverbDecay { get; private set; } + public float[] DecayDirectFdnGain { get; private set; } + public float[] DecayCurrentFdnGain { get; private set; } + public float[] DecayCurrentOutputGain { get; private set; } + public float[] PreviousFeedbackOutputDecayed { get; private set; } + + public Reverb3dState(ref Reverb3dParameter parameter, ulong workBuffer) + { + FdnDelayLines = new IDelayLine[4]; + DecayDelays1 = new DecayDelay[4]; + DecayDelays2 = new DecayDelay[4]; + DecayDirectFdnGain = new float[4]; + DecayCurrentFdnGain = new float[4]; + DecayCurrentOutputGain = new float[4]; + PreviousFeedbackOutputDecayed = new float[4]; + + uint sampleRate = parameter.SampleRate / 1000; + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i] = new DelayLine3d(sampleRate, FdnDelayMaxTimes[i]); + DecayDelays1[i] = new DecayDelay(new DelayLine3d(sampleRate, DecayDelayMaxTimes1[i])); + DecayDelays2[i] = new DecayDelay(new DelayLine3d(sampleRate, DecayDelayMaxTimes2[i])); + } + + PreDelayLine = new DelayLine3d(sampleRate, 400); + BackLeftDelayLine = new DelayLine3d(sampleRate, 5); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref Reverb3dParameter parameter) + { + uint sampleRate = parameter.SampleRate / 1000; + + EarlyDelayTime = new uint[20]; + DryGain = parameter.DryGain; + PreviousFeedbackOutputDecayed.AsSpan().Fill(0); + PreviousPreDelayValue = 0; + + EarlyReflectionsGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReflectionsGain, 5000.0f) / 2000.0f); + LateReverbGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReverbGain, 5000.0f) / 2000.0f); + + float highFrequencyRoomGain = FloatingPointHelper.Pow10(parameter.RoomHf / 2000.0f); + + if (highFrequencyRoomGain < 1.0f) + { + float tempA = 1.0f - highFrequencyRoomGain; + float tempB = 2.0f - ((2.0f * highFrequencyRoomGain) * FloatingPointHelper.Cos(256.0f * parameter.HfReference / parameter.SampleRate)); + float tempC = MathF.Sqrt(MathF.Pow(tempB, 2) - (4.0f * (1.0f - highFrequencyRoomGain) * (1.0f - highFrequencyRoomGain))); + + PreviousPreDelayGain = (tempB - tempC) / (2.0f * tempA); + TargetPreDelayGain = 1.0f - PreviousPreDelayGain; + } + else + { + PreviousPreDelayGain = 0.0f; + TargetPreDelayGain = 1.0f; + } + + ReflectionDelayTime = IDelayLine.GetSampleCount(sampleRate, 1000.0f * (parameter.ReflectionDelay + parameter.ReverbDelayTime)); + EchoLateReverbDecay = 0.6f * parameter.Diffusion * 0.01f; + + for (int i = 0; i < FdnDelayLines.Length; i++) + { + FdnDelayLines[i].SetDelay(FdnDelayMinTimes[i] + (parameter.Density / 100 * (FdnDelayMaxTimes[i] - FdnDelayMinTimes[i]))); + + uint tempSampleCount = FdnDelayLines[i].CurrentSampleCount + DecayDelays1[i].CurrentSampleCount + DecayDelays2[i].CurrentSampleCount; + + float tempA = (-60.0f * tempSampleCount) / (parameter.DecayTime * parameter.SampleRate); + float tempB = tempA / parameter.HfDecayRatio; + float tempC = FloatingPointHelper.Cos(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate) / FloatingPointHelper.Sin(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate); + float tempD = FloatingPointHelper.Pow10((tempB - tempA) / 40.0f); + float tempE = FloatingPointHelper.Pow10((tempB + tempA) / 40.0f) * 0.7071f; + + DecayDirectFdnGain[i] = tempE * ((tempD * tempC) + 1.0f) / (tempC + tempD); + DecayCurrentFdnGain[i] = tempE * (1.0f - (tempD * tempC)) / (tempC + tempD); + DecayCurrentOutputGain[i] = (tempC - tempD) / (tempC + tempD); + + DecayDelays1[i].SetDecayRate(EchoLateReverbDecay); + DecayDelays2[i].SetDecayRate(EchoLateReverbDecay * -0.9f); + } + + for (int i = 0; i < EarlyDelayTime.Length; i++) + { + uint sampleCount = Math.Min(IDelayLine.GetSampleCount(sampleRate, (parameter.ReflectionDelay * 1000.0f) + (EarlyDelayTimes[i] * 1000.0f * ((parameter.ReverbDelayTime * 0.9998f) + 0.02f))), PreDelayLine.SampleCountMax); + EarlyDelayTime[i] = sampleCount; + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Dsp/State/ReverbState.cs b/Ryujinx.Audio.Renderer/Dsp/State/ReverbState.cs new file mode 100644 index 00000000..31b03dd6 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Dsp/State/ReverbState.cs @@ -0,0 +1,221 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public class ReverbState + { + private static readonly float[] FdnDelayTimes = new float[20] + { + // Room + 53.953247f, 79.192566f, 116.238770f, 130.615295f, + // Hall + 53.953247f, 79.192566f, 116.238770f, 170.615295f, + // Plate + 5f, 10f, 5f, 10f, + // Cathedral + 47.03f, 71f, 103f, 170f, + // Max delay (Hall is the one with the highest values so identical to Hall) + 53.953247f, 79.192566f, 116.238770f, 170.615295f, + }; + + private static readonly float[] DecayDelayTimes = new float[20] + { + // Room + 7f, 9f, 13f, 17f, + // Hall + 7f, 9f, 13f, 17f, + // Plate (no decay) + 1f, 1f, 1f, 1f, + // Cathedral + 7f, 7f, 13f, 9f, + // Max delay (Hall is the one with the highest values so identical to Hall) + 7f, 9f, 13f, 17f, + }; + + private static readonly float[] EarlyDelayTimes = new float[50] + { + // Room + 0.0f, 3.5f, 2.8f, 3.9f, 2.7f, 13.4f, 7.9f, 8.4f, 9.9f, 12.0f, + // Chamber + 0.0f, 11.8f, 5.5f, 11.2f, 10.4f, 38.1f, 22.2f, 29.6f, 21.2f, 24.8f, + // Hall + 0.0f, 41.5f, 20.5f, 41.3f, 0.0f, 29.5f, 33.8f, 45.2f, 46.8f, 0.0f, + // Cathedral + 33.1f, 43.3f, 22.8f, 37.9f, 14.9f, 35.3f, 17.9f, 34.2f, 0.0f, 43.3f, + // Disabled + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f + }; + + private static readonly float[] EarlyGainBase = new float[50] + { + // Room + 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, + // Chamber + 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f, + // Hall + 0.50f, 0.70f, 0.70f, 0.68f, 0.50f, 0.68f, 0.68f, 0.70f, 0.68f, 0.00f, + // Cathedral + 0.93f, 0.92f, 0.87f, 0.86f, 0.94f, 0.81f, 0.80f, 0.77f, 0.76f, 0.65f, + // Disabled + 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f + }; + + private static readonly float[] PreDelayTimes = new float[5] + { + // Room + 12.5f, + // Chamber + 40.0f, + // Hall + 50.0f, + // Cathedral + 50.0f, + // Disabled + 0.0f + }; + + public IDelayLine[] FdnDelayLines { get; } + public DecayDelay[] DecayDelays { get; } + public IDelayLine PreDelayLine { get; } + public IDelayLine BackLeftDelayLine { get; } + public uint[] EarlyDelayTime { get; } + public float[] EarlyGain { get; } + public uint PreDelayLineDelayTime { get; private set; } + + public float[] HighFrequencyDecayDirectGain { get; } + public float[] HighFrequencyDecayPreviousGain { get; } + public float[] PreviousFeedbackOutput { get; } + + public const int EarlyModeCount = 10; + + private const int FixedPointPrecision = 14; + + private ReadOnlySpan GetFdnDelayTimesByLateMode(ReverbLateMode lateMode) + { + return FdnDelayTimes.AsSpan().Slice((int)lateMode * 4, 4); + } + + private ReadOnlySpan GetDecayDelayTimesByLateMode(ReverbLateMode lateMode) + { + return DecayDelayTimes.AsSpan().Slice((int)lateMode * 4, 4); + } + + public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported) + { + FdnDelayLines = new IDelayLine[4]; + DecayDelays = new DecayDelay[4]; + EarlyDelayTime = new uint[EarlyModeCount]; + EarlyGain = new float[EarlyModeCount]; + HighFrequencyDecayDirectGain = new float[4]; + HighFrequencyDecayPreviousGain = new float[4]; + PreviousFeedbackOutput = new float[4]; + + ReadOnlySpan fdnDelayTimes = GetFdnDelayTimesByLateMode(ReverbLateMode.Limit); + ReadOnlySpan decayDelayTimes = GetDecayDelayTimesByLateMode(ReverbLateMode.Limit); + + uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision); + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i] = new DelayLine(sampleRate, fdnDelayTimes[i]); + DecayDelays[i] = new DecayDelay(new DelayLine(sampleRate, decayDelayTimes[i])); + } + + float preDelayTimeMax = 150.0f; + + if (isLongSizePreDelaySupported) + { + preDelayTimeMax = 350.0f; + } + + PreDelayLine = new DelayLine(sampleRate, preDelayTimeMax); + BackLeftDelayLine = new DelayLine(sampleRate, 5.0f); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref ReverbParameter parameter) + { + uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision); + + float preDelayTimeInMilliseconds = FixedPointHelper.ToFloat(parameter.PreDelayTime, FixedPointPrecision); + float earlyGain = FixedPointHelper.ToFloat(parameter.EarlyGain, FixedPointPrecision); + float coloration = FixedPointHelper.ToFloat(parameter.Coloration, FixedPointPrecision); + float decayTime = FixedPointHelper.ToFloat(parameter.DecayTime, FixedPointPrecision); + + for (int i = 0; i < 10; i++) + { + EarlyDelayTime[i] = Math.Min(IDelayLine.GetSampleCount(sampleRate, EarlyDelayTimes[i] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax) + 1; + EarlyGain[i] = EarlyGainBase[i] * earlyGain; + } + + if (parameter.ChannelCount == 2) + { + EarlyGain[4] = EarlyGain[4] * 0.5f; + EarlyGain[5] = EarlyGain[5] * 0.5f; + } + + PreDelayLineDelayTime = Math.Min(IDelayLine.GetSampleCount(sampleRate, PreDelayTimes[(int)parameter.EarlyMode] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax); + + ReadOnlySpan fdnDelayTimes = GetFdnDelayTimesByLateMode(parameter.LateMode); + ReadOnlySpan decayDelayTimes = GetDecayDelayTimesByLateMode(parameter.LateMode); + + float highFrequencyDecayRatio = FixedPointHelper.ToFloat(parameter.HighFrequencyDecayRatio, FixedPointPrecision); + float highFrequencyUnknownValue = FloatingPointHelper.Cos(1280.0f / sampleRate); + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i].SetDelay(fdnDelayTimes[i]); + DecayDelays[i].SetDelay(decayDelayTimes[i]); + + float tempA = -3 * (DecayDelays[i].CurrentSampleCount + FdnDelayLines[i].CurrentSampleCount); + float tempB = tempA / (decayTime * sampleRate); + float tempC; + float tempD; + + if (highFrequencyDecayRatio < 0.995f) + { + float tempE = FloatingPointHelper.Pow10((((1.0f / highFrequencyDecayRatio) - 1.0f) * 2) / 100 * (tempB / 10)); + float tempF = 1.0f - tempE; + float tempG = 2.0f - (tempE * 2 * highFrequencyUnknownValue); + float tempH = MathF.Sqrt((tempG * tempG) - (tempF * tempF * 4)); + + tempC = (tempG - tempH) / (tempF * 2); + tempD = 1.0f - tempC; + } + else + { + // no high frequency decay ratio + tempC = 0.0f; + tempD = 1.0f; + } + + HighFrequencyDecayDirectGain[i] = FloatingPointHelper.Pow10(tempB / 1000) * tempD * 0.7071f; + HighFrequencyDecayPreviousGain[i] = tempC; + PreviousFeedbackOutput[i] = 0.0f; + + DecayDelays[i].SetDecayRate(0.6f * (1.0f - coloration)); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Integration/HardwareDevice.cs b/Ryujinx.Audio.Renderer/Integration/HardwareDevice.cs new file mode 100644 index 00000000..4586a2e3 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Integration/HardwareDevice.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Integration +{ + /// + /// Represent an hardware device used in + /// + public interface HardwareDevice : IDisposable + { + /// + /// Get the supported sample rate of this device. + /// + /// The supported sample rate of this device. + uint GetSampleRate(); + + /// + /// Get the channel count supported by this device. + /// + /// The channel count supported by this device. + uint GetChannelCount(); + + /// + /// Appends new PCM16 samples to the device. + /// + /// The new PCM16 samples. + /// The number of channels. + void AppendBuffer(ReadOnlySpan data, uint channelCount); + + /// + /// Check if the audio renderer needs to perform downmixing. + /// + /// True if downmixing is needed. + public bool NeedDownmixing() + { + uint channelCount = GetChannelCount(); + + Debug.Assert(channelCount > 0 && channelCount <= RendererConstants.ChannelCountMax); + + return channelCount != RendererConstants.ChannelCountMax; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Integration/IWritableEvent.cs b/Ryujinx.Audio.Renderer/Integration/IWritableEvent.cs new file mode 100644 index 00000000..3a899851 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Integration/IWritableEvent.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Integration +{ + /// + /// Represent a writable event with manual clear. + /// + public interface IWritableEvent + { + /// + /// Signal the event. + /// + void Signal(); + + /// + /// Clear the signaled state of the event. + /// + void Clear(); + } +} diff --git a/Ryujinx.Audio.Renderer/LICENSE.txt b/Ryujinx.Audio.Renderer/LICENSE.txt new file mode 100644 index 00000000..0a041280 --- /dev/null +++ b/Ryujinx.Audio.Renderer/LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/Ryujinx.Audio.Renderer/Parameter/AudioRendererConfiguration.cs b/Ryujinx.Audio.Renderer/Parameter/AudioRendererConfiguration.cs new file mode 100644 index 00000000..9ce3fb83 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/AudioRendererConfiguration.cs @@ -0,0 +1,116 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Server.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Audio Renderer user configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioRendererConfiguration + { + /// + /// The target sample rate of the user. + /// + /// Only 32000Hz and 48000Hz are considered valid, other sample rates will cause undefined behaviour. + public uint SampleRate; + + /// + /// The target sample count per updates. + /// + public uint SampleCount; + + /// + /// The maximum mix buffer count. + /// + public uint MixBufferCount; + + /// + /// The maximum amount of sub mixes that could be used by the user. + /// + public uint SubMixBufferCount; + + /// + /// The maximum amount of voices that could be used by the user. + /// + public uint VoiceCount; + + /// + /// The maximum amount of sinks that could be used by the user. + /// + public uint SinkCount; + + /// + /// The maximum amount of effects that could be used by the user. + /// + public uint EffectCount; + + /// + /// The maximum amount of performance metric frames that could be used by the user. + /// + public uint PerformanceMetricFramesCount; + + /// + /// Set to true if the user allows the to drop voices. + /// + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropEnabled; + + /// + /// Reserved/unused + /// + private byte _reserved; + + /// + /// The target rendering device. + /// + /// Must be + public AudioRendererRenderingDevice RenderingDevice; + + /// + /// The target execution mode. + /// + /// Must be + public AudioRendererExecutionMode ExecutionMode; + + /// + /// The maximum amount of splitters that could be used by the user. + /// + public uint SplitterCount; + + /// + /// The maximum amount of splitters destinations that could be used by the user. + /// + public uint SplitterDestinationCount; + + /// + /// The size of the external context. + /// + /// This is a leftover of the old "codec" interface system that was present between 1.0.0 and 3.0.0. This was entirely removed from the server side with REV8. + public uint ExternalContextSize; + + /// + /// The user audio revision + /// + /// + public int Revision; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/BehaviourErrorInfoOutStatus.cs b/Ryujinx.Audio.Renderer/Parameter/BehaviourErrorInfoOutStatus.cs new file mode 100644 index 00000000..7e1af5e9 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/BehaviourErrorInfoOutStatus.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for behaviour. + /// + /// This is used to report errors to the user during processing. + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BehaviourErrorInfoOutStatus + { + /// + /// The reported errors. + /// + public Array10 ErrorInfos; + + /// + /// The amount of error that got reported. + /// + public uint ErrorInfosCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/BiquadFilterParameter.cs b/Ryujinx.Audio.Renderer/Parameter/BiquadFilterParameter.cs new file mode 100644 index 00000000..fdbea61e --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/BiquadFilterParameter.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Biquad filter parameters. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 1)] + public struct BiquadFilterParameter + { + /// + /// Set to true if the biquad filter is active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool Enable; + + /// + /// Reserved/padding. + /// + private byte _reserved; + + /// + /// Biquad filter numerator (b0, b1, b2). + /// + public Array3 Numerator; + + /// + /// Biquad filter denominator (a1, a2). + /// + /// a0 = 1 + public Array2 Denominator; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs b/Ryujinx.Audio.Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs new file mode 100644 index 00000000..e18c05db --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs @@ -0,0 +1,100 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AuxiliaryBufferParameter + { + /// + /// The input channel indices that will be used by the to write data to . + /// + public Array24 Input; + + /// + /// The output channel indices that will be used by the to read data from . + /// + public Array24 Output; + + /// + /// The total channel count used. + /// + public uint ChannelCount; + + /// + /// The target sample rate. + /// + public uint SampleRate; + + /// + /// The buffer storage total size. + /// + public uint BufferStorageSize; + + /// + /// The maximum number of channels supported. + /// + /// This is unused. + public uint ChannelCountMax; + + /// + /// The address of the start of the region containing two followed by the data that will be written by the . + /// + public ulong SendBufferInfoAddress; + + /// + /// The address of the start of the region containling data that will be written by the . + /// + /// This is unused. + public ulong SendBufferStorageAddress; + + /// + /// The address of the start of the region containing two followed by the data that will be read by the . + /// + public ulong ReturnBufferInfoAddress; + + /// + /// The address of the start of the region containling data that will be read by the . + /// + /// This is unused. + public ulong ReturnBufferStorageAddress; + + /// + /// Size of a sample of the mix buffer. + /// + /// This is unused. + public uint MixBufferSampleSize; + + /// + /// The total count of sample that can be stored. + /// + /// This is unused. + public uint TotalSampleCount; + + /// + /// The count of sample of the mix buffer. + /// + /// This is unused. + public uint MixBufferSampleCount; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs b/Ryujinx.Audio.Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs new file mode 100644 index 00000000..26fff310 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BiquadFilterEffectParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// Biquad filter numerator (b0, b1, b2). + /// + public Array3 Numerator; + + /// + /// Biquad filter denominator (a1, a2). + /// + /// a0 = 1 + public Array2 Denominator; + + /// + /// The total channel count used. + /// + public byte ChannelCount; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/BufferMixerParameter.cs b/Ryujinx.Audio.Renderer/Parameter/Effect/BufferMixerParameter.cs new file mode 100644 index 00000000..94f77033 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/Effect/BufferMixerParameter.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BufferMixParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array24 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array24 Output; + + /// + /// The output volumes of the mixes. + /// + public Array24 Volumes; + + /// + /// The total count of mixes used. + /// + public uint MixesCount; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/DelayParameter.cs b/Ryujinx.Audio.Renderer/Parameter/Effect/DelayParameter.cs new file mode 100644 index 00000000..2f2c907f --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/Effect/DelayParameter.cs @@ -0,0 +1,118 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DelayParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The maximum delay time in milliseconds. + /// + public uint DelayTimeMax; + + /// + /// The delay time in milliseconds. + /// + public uint DelayTime; + + /// + /// The target sample rate. (Q15) + /// + public uint SampleRate; + + /// + /// The input gain. (Q15) + /// + public uint InGain; + + /// + /// The feedback gain. (Q15) + /// + public uint FeedbackGain; + + /// + /// The output gain. (Q15) + /// + public uint OutGain; + + /// + /// The dry gain. (Q15) + /// + public uint DryGain; + + /// + /// The channel spread of the . (Q15) + /// + public uint ChannelSpread; + + /// + /// The low pass amount. (Q15) + /// + public uint LowPassAmount; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountValid() + { + return EffectInParameter.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountMaxValid() + { + return EffectInParameter.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/Reverb3dParameter.cs b/Ryujinx.Audio.Renderer/Parameter/Effect/Reverb3dParameter.cs new file mode 100644 index 00000000..9f31cd15 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/Effect/Reverb3dParameter.cs @@ -0,0 +1,144 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct Reverb3dParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// Reserved/unused. + /// + private uint _reserved; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public uint SampleRate; + + /// + /// Gain of the room high-frequency effect. + /// + public float RoomHf; + + /// + /// Reference high frequency. + /// + public float HfReference; + + /// + /// Reverberation decay time at low frequencies. + /// + public float DecayTime; + + /// + /// Ratio of the decay time at high frequencies to the decay time at low frequencies. + /// + public float HfDecayRatio; + + /// + /// Gain of the room effect. + /// + public float RoomGain; + + /// + /// Gain of the early reflections relative to . + /// + public float ReflectionsGain; + + /// + /// Gain of the late reverberation relative to . + /// + public float ReverbGain; + + /// + /// Echo density in the late reverberation decay. + /// + public float Diffusion; + + /// + /// Modal density in the late reverberation decay. + /// + public float ReflectionDelay; + + /// + /// Time limit between the early reflections and the late reverberation relative to the time of the first reflection. + /// + public float ReverbDelayTime; + + /// + /// Modal density in the late reverberation decay. + /// + public float Density; + + /// + /// The dry gain. + /// + public float DryGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState ParameterStatus; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountValid() + { + return EffectInParameter.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountMaxValid() + { + return EffectInParameter.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/ReverbParameter.cs b/Ryujinx.Audio.Renderer/Parameter/Effect/ReverbParameter.cs new file mode 100644 index 00000000..ebe0ffdd --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/Effect/ReverbParameter.cs @@ -0,0 +1,136 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ReverbParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. (Q15) + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The early mode to use. + /// + public ReverbEarlyMode EarlyMode; + + /// + /// The gain to apply to the result of the early reflection. (Q15) + /// + public int EarlyGain; + + /// + /// The pre-delay time in milliseconds. (Q15) + /// + public int PreDelayTime; + + /// + /// The late mode to use. + /// + public ReverbLateMode LateMode; + + /// + /// The gain to apply to the result of the late reflection. (Q15) + /// + public int LateGain; + + /// + /// The decay time. (Q15) + /// + public int DecayTime; + + /// + /// The high frequency decay ratio. (Q15) + /// + /// If >= 0.995f, it is considered disabled. + public int HighFrequencyDecayRatio; + + /// + /// The coloration of the decay. (Q15) + /// + public int Coloration; + + /// + /// The reverb gain. (Q15) + /// + public int ReverbGain; + + /// + /// The output gain. (Q15) + /// + public int OutGain; + + /// + /// The dry gain. (Q15) + /// + public int DryGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountValid() + { + return EffectInParameter.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountMaxValid() + { + return EffectInParameter.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/EffectInParameter.cs b/Ryujinx.Audio.Renderer/Parameter/EffectInParameter.cs new file mode 100644 index 00000000..197cb0f0 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/EffectInParameter.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for an effect. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectInParameter + { + /// + /// Type of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the effect must be active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEnabled; + + /// + /// Reserved/padding. + /// + private byte _reserved1; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferBase; + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferSize; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Reserved/padding. + /// + private uint _reserved2; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)] + private struct SpecificDataStruct { } + + /// + /// Specific data changing depending of the . See also the namespace. + /// + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + + /// + /// Check if the given channel count is valid. + /// + /// The channel count to check + /// Returns true if the channel count is valid. + public static bool IsChannelCountValid(int channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/EffectOutStatus.cs b/Ryujinx.Audio.Renderer/Parameter/EffectOutStatus.cs new file mode 100644 index 00000000..6e81524f --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/EffectOutStatus.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for an effect. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectOutStatus + { + /// + /// The state of an effect. + /// + public enum EffectState : byte + { + /// + /// The effect is enabled. + /// + Enabled = 3, + + /// + /// The effect is disabled. + /// + Disabled = 4 + } + + /// + /// Current effect state. + /// + // TODO: enum? + public EffectState State; + + /// + /// Unused/Reserved. + /// + private unsafe fixed byte _reserved[15]; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/MemoryPoolInParameter.cs b/Ryujinx.Audio.Renderer/Parameter/MemoryPoolInParameter.cs new file mode 100644 index 00000000..b86a8992 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/MemoryPoolInParameter.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MemoryPoolInParameter + { + /// + /// The CPU address used by the memory pool. + /// + public CpuAddress CpuAddress; + + /// + /// The size used by the memory pool. + /// + public ulong Size; + + /// + /// The target state the user wants. + /// + public MemoryPoolUserState State; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/MemoryPoolOutStatus.cs b/Ryujinx.Audio.Renderer/Parameter/MemoryPoolOutStatus.cs new file mode 100644 index 00000000..a29bc4a7 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/MemoryPoolOutStatus.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MemoryPoolOutStatus + { + /// + /// The current server memory pool state. + /// + public MemoryPoolUserState State; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs b/Ryujinx.Audio.Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs new file mode 100644 index 00000000..774c7b7a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information header for mix updates on REV7 and later + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MixInParameterDirtyOnlyUpdate + { + /// + /// Magic of the header + /// + /// Never checked on hardware. + public uint Magic; + + /// + /// The count of following this header. + /// + public uint MixCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed byte _reserved[24]; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/MixParameter.cs b/Ryujinx.Audio.Renderer/Parameter/MixParameter.cs new file mode 100644 index 00000000..e666f669 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/MixParameter.cs @@ -0,0 +1,112 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a mix. + /// + /// Also used on the client side for mix tracking. + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MixParameter + { + /// + /// Base volume of the mix. + /// + public float Volume; + + /// + /// Target sample rate of the mix. + /// + public uint SampleRate; + + /// + /// Target buffer count. + /// + public uint BufferCount; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if it was changed. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsDirty; + + /// + /// Reserved/padding. + /// + private ushort _reserved1; + + /// + /// The id of the mix. + /// + public int MixId; + + /// + /// The effect count. (client side) + /// + public uint EffectCount; + + /// + /// The mix node id. + /// + public int NodeId; + + /// + /// Reserved/padding. + /// + private ulong _reserved2; + + /// + /// Mix buffer volumes storage. + /// + private MixVolumeArray _mixBufferVolumeArray; + + /// + /// The mix to output the result of this mix. + /// + public int DestinationMixId; + + /// + /// The splitter to output the result of this mix. + /// + public uint DestinationSplitterId; + + /// + /// Reserved/padding. + /// + private uint _reserved3; + + [StructLayout(LayoutKind.Sequential, Size = 4 * RendererConstants.MixBufferCountMax * RendererConstants.MixBufferCountMax, Pack = 1)] + private struct MixVolumeArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when no splitter id is specified. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolumeArray); + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceInParameter.cs b/Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceInParameter.cs new file mode 100644 index 00000000..798329d2 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceInParameter.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Performance +{ + /// + /// Input information for performance monitoring. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PerformanceInParameter + { + /// + /// The target node id to monitor performance on. + /// + public int TargetNodeId; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceOutStatus.cs b/Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceOutStatus.cs new file mode 100644 index 00000000..e4cd28f3 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceOutStatus.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Performance +{ + /// + /// Output information for performance monitoring. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PerformanceOutStatus + { + /// + /// Indicates the total size output to the performance buffer. + /// + public uint HistorySize; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/RendererInfoOutStatus.cs b/Ryujinx.Audio.Renderer/Parameter/RendererInfoOutStatus.cs new file mode 100644 index 00000000..7648ffd4 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/RendererInfoOutStatus.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Renderer output information on REV5 and later. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RendererInfoOutStatus + { + /// + /// The count of updates sent to the . + /// + public ulong ElapsedFrameCount; + + /// + /// Reserved/Unused. + /// + private ulong _reserved; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/Sink/CircularBufferParameter.cs b/Ryujinx.Audio.Renderer/Parameter/Sink/CircularBufferParameter.cs new file mode 100644 index 00000000..a314213b --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/Sink/CircularBufferParameter.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Parameter.Sink +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CircularBufferParameter + { + /// + /// The CPU address of the user circular buffer. + /// + public CpuAddress BufferAddress; + + /// + /// The size of the user circular buffer. + /// + public uint BufferSize; + + /// + /// The total count of channels to output to the circular buffer. + /// + public uint InputCount; + + /// + /// The target sample count to output per update in the circular buffer. + /// + public uint SampleCount; + + /// + /// Last read offset on the CPU side. + /// + public uint LastReadOffset; + + /// + /// The target . + /// + /// Only is supported. + public SampleFormat SampleFormat; + + /// + /// Reserved/padding. + /// + private unsafe fixed byte _reserved1[3]; + + /// + /// The input channels index that will be used. + /// + public Array6 Input; + + /// + /// Reserved/padding. + /// + private ushort _reserved2; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/Sink/DeviceParameter.cs b/Ryujinx.Audio.Renderer/Parameter/Sink/DeviceParameter.cs new file mode 100644 index 00000000..d4629ef2 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/Sink/DeviceParameter.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Sink +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DeviceParameter + { + /// + /// Device name storage. + /// + private DeviceNameStruct _deviceName; + + /// + /// Reserved/padding. + /// + private byte _padding; + + /// + /// The total count of channels to output to the device. + /// + public uint InputCount; + + /// + /// The input channels index that will be used. + /// + public Array6 Input; + + /// + /// Reserved/padding. + /// + private byte _reserved; + + /// + /// Set to true if the user controls Surround to Stereo downmixing coefficients. + /// + [MarshalAs(UnmanagedType.I1)] + public bool DownMixParameterEnabled; + + /// + /// The user Surround to Stereo downmixing coefficients. + /// + public Array4 DownMixParameter; + + [StructLayout(LayoutKind.Sequential, Size = 0xFF, Pack = 1)] + private struct DeviceNameStruct { } + + /// + /// The output device name. + /// + public Span DeviceName => SpanHelpers.AsSpan(ref _deviceName); + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/SinkInParameter.cs b/Ryujinx.Audio.Renderer/Parameter/SinkInParameter.cs new file mode 100644 index 00000000..dab40f0e --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/SinkInParameter.cs @@ -0,0 +1,70 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a sink. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SinkInParameter + { + /// + /// Type of the sink. + /// + public SinkType Type; + + /// + /// Set to true if the sink is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Reserved/padding. + /// + private ushort _reserved1; + + /// + /// The node id of the sink. + /// + public int NodeId; + + /// + /// Reserved/padding. + /// + private unsafe fixed ulong _reserved2[3]; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0x120, Pack = 1)] + private struct SpecificDataStruct { } + + /// + /// Specific data changing depending of the . See also the namespace. + /// + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/SinkOutStatus.cs b/Ryujinx.Audio.Renderer/Parameter/SinkOutStatus.cs new file mode 100644 index 00000000..a57a7113 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/SinkOutStatus.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for a sink. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SinkOutStatus + { + /// + /// Last written offset if the sink type is . + /// + public uint LastWrittenOffset; + + /// + /// Reserved/padding. + /// + private uint _padding; + + /// + /// Reserved/padding. + /// + private unsafe fixed ulong _reserved[3]; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/SplitterDestinationInParameter.cs b/Ryujinx.Audio.Renderer/Parameter/SplitterDestinationInParameter.cs new file mode 100644 index 00000000..61e55cb4 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/SplitterDestinationInParameter.cs @@ -0,0 +1,84 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for a splitter destination update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterDestinationInParameter + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// Target splitter destination data id. + /// + public int Id; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mixBufferVolume; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Reserved/padding. + /// + private unsafe fixed byte _reserved[3]; + + [StructLayout(LayoutKind.Sequential, Size = 4 * RendererConstants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume); + + /// + /// The expected constant of any input header. + /// + private const uint ValidMagic = 0x44444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/SplitterInParameter.cs b/Ryujinx.Audio.Renderer/Parameter/SplitterInParameter.cs new file mode 100644 index 00000000..8f095f48 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/SplitterInParameter.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for a splitter state update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterInParameter + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// Target splitter id. + /// + public int Id; + + /// + /// Target sample rate to use on the splitter. + /// + public uint SampleRate; + + /// + /// Count of splitter destinations. + /// + /// Splitter destination ids are defined right after this header. + public int DestinationCount; + + /// + /// The expected constant of any input header. + /// + private const uint ValidMagic = 0x49444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/SplitterInParameterHeader.cs b/Ryujinx.Audio.Renderer/Parameter/SplitterInParameterHeader.cs new file mode 100644 index 00000000..d3298d09 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/SplitterInParameterHeader.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for splitter update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterInParameterHeader + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// The count of after the header. + /// + public uint SplitterCount; + + /// + /// The count of splitter destinations after the header and splitter info. + /// + public uint SplitterDestinationCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[5]; + + /// + /// The expected constant of any input splitter header. + /// + private const uint ValidMagic = 0x48444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/VoiceChannelResourceInParameter.cs b/Ryujinx.Audio.Renderer/Parameter/VoiceChannelResourceInParameter.cs new file mode 100644 index 00000000..93977f1c --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/VoiceChannelResourceInParameter.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a voice channel resources. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)] + public struct VoiceChannelResourceInParameter + { + /// + /// The id of the voice channel resource. + /// + public uint Id; + + /// + /// Mix volumes for the voice channel resource. + /// + public Array24 Mix; + + /// + /// Indicate if the voice channel resource is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/VoiceInParameter.cs b/Ryujinx.Audio.Renderer/Parameter/VoiceInParameter.cs new file mode 100644 index 00000000..220edcb0 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/VoiceInParameter.cs @@ -0,0 +1,360 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp; +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a voice. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)] + public struct VoiceInParameter + { + /// + /// Id of the voice. + /// + public int Id; + + /// + /// Node id of the voice. + /// + public int NodeId; + + /// + /// Set to true if the voice is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the voice is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool InUse; + + /// + /// The voice wanted by the user. + /// + public PlayState PlayState; + + /// + /// The of the voice. + /// + public SampleFormat SampleFormat; + + /// + /// The sample rate of the voice. + /// + public uint SampleRate; + + /// + /// The priority of the voice. + /// + public uint Priority; + + /// + /// Target sorting position of the voice. (Used to sort voices with the same ) + /// + public uint SortingOrder; + + /// + /// The total channel count used. + /// + public uint ChannelCount; + + /// + /// The pitch used on the voice. + /// + public float Pitch; + + /// + /// The output volume of the voice. + /// + public float Volume; + + /// + /// Biquad filters to apply to the output of the voice. + /// + public Array2 BiquadFilters; + + /// + /// Total count of of the voice. + /// + public uint WaveBuffersCount; + + /// + /// Current playing of the voice. + /// + public uint WaveBuffersIndex; + + /// + /// Reserved/unused. + /// + private uint _reserved1; + + /// + /// User state address required by the data source. + /// + /// Only used for as the address of the GC-ADPCM coefficients. + public ulong DataSourceStateAddress; + + /// + /// User state size required by the data source. + /// + /// Only used for as the size of the GC-ADPCM coefficients. + public ulong DataSourceStateSize; + + /// + /// The target mix id of the voice. + /// + public int MixId; + + /// + /// The target splitter id of the voice. + /// + public uint SplitterId; + + /// + /// The wavebuffer parameters of this voice. + /// + public Array4 WaveBuffers; + + /// + /// The channel resource ids associated to the voice. + /// + public Array6 ChannelResourceIds; + + /// + /// Reset the voice drop flag during voice server update. + /// + [MarshalAs(UnmanagedType.I1)] + public bool ResetVoiceDropFlag; + + /// + /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played. + /// + /// This was added on REV5. + public byte FlushWaveBufferCount; + + /// + /// Reserved/unused. + /// + private ushort _reserved2; + + /// + /// Change the behaviour of the voice. + /// + /// This was added on REV5. + public DecodingBehaviour DecodingBehaviourFlags; + + /// + /// Change the Sample Rate Conversion (SRC) quality of the voice. + /// + /// This was added on REV8. + public SampleRateConversionQuality SrcQuality; + + /// + /// This was previously used for opus codec support on the Audio Renderer and was removed on REV3. + /// + public uint ExternalContext; + + /// + /// This was previously used for opus codec support on the Audio Renderer and was removed on REV3. + /// + public uint ExternalContextSize; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved3[2]; + + /// + /// Input information for a voice wavebuffer. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)] + public struct WaveBufferInternal + { + /// + /// Address of the wavebuffer data. + /// + public ulong Address; + + /// + /// Size of the wavebuffer data. + /// + public ulong Size; + + /// + /// Offset of the first sample to play. + /// + public uint StartSampleOffset; + + /// + /// Offset of the last sample to play. + /// + public uint EndSampleOffset; + + /// + /// If set to true, the wavebuffer will loop when reaching . + /// + /// + /// Starting with REV8, you can specify how many times to loop the wavebuffer () and where it should start and end when looping ( and ) + /// + [MarshalAs(UnmanagedType.I1)] + public bool ShouldLoop; + + /// + /// Indicates that this is the last wavebuffer to play of the voice. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Indicates if the server should update its internal state. + /// + [MarshalAs(UnmanagedType.I1)] + public bool SentToServer; + + /// + /// Reserved/unused. + /// + private byte _reserved; + + /// + /// If set to anything other than 0, specifies how many times to loop the wavebuffer. + /// + /// This was added in REV8. + public int LoopCount; + + /// + /// Address of the context used by the sample decoder. + /// + /// This is only currently used by . + public ulong ContextAddress; + + /// + /// Size of the context used by the sample decoder. + /// + /// This is only currently used by . + public ulong ContextSize; + + /// + /// If set to anything other than 0, specifies the offset of the first sample to play when looping. + /// + /// This was added in REV8. + public uint LoopFirstSampleOffset; + + /// + /// If set to anything other than 0, specifies the offset of the last sample to play when looping. + /// + /// This was added in REV8. + public uint LoopLastSampleOffset; + + /// + /// Check if the sample offsets are in a valid range for generic PCM. + /// + /// The PCM sample type + /// Returns true if the sample offset are in range of the size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsSampleOffsetInRangeForPcm() where T : unmanaged + { + uint dataTypeSize = (uint)Unsafe.SizeOf(); + + return StartSampleOffset * dataTypeSize <= Size && + EndSampleOffset * dataTypeSize <= Size; + } + + /// + /// Check if the sample offsets are in a valid range for the given . + /// + /// The target + /// Returns true if the sample offset are in range of the size. + public bool IsSampleOffsetValid(SampleFormat format) + { + bool result; + + switch (format) + { + case SampleFormat.PcmInt16: + result = IsSampleOffsetInRangeForPcm(); + break; + case SampleFormat.PcmFloat: + result = IsSampleOffsetInRangeForPcm(); + break; + case SampleFormat.Adpcm: + result = AdpcmHelper.GetAdpcmDataSize((int)StartSampleOffset) <= Size && + AdpcmHelper.GetAdpcmDataSize((int)EndSampleOffset) <= Size; + break; + default: + throw new NotImplementedException($"{format} not implemented!"); + } + + return result; + } + } + + /// + /// Flag altering the behaviour of wavebuffer decoding. + /// + [Flags] + public enum DecodingBehaviour : ushort + { + /// + /// Default decoding behaviour. + /// + Default = 0, + + /// + /// Reset the played samples accumulator when looping. + /// + PlayedSampleCountResetWhenLooping = 1, + + /// + /// Skip pitch and Sample Rate Conversion (SRC). + /// + SkipPitchAndSampleRateConversion = 2 + } + + /// + /// Specify the quality to use during Sample Rate Conversion (SRC) and pitch handling. + /// + /// This was added in REV8. + public enum SampleRateConversionQuality : byte + { + /// + /// Resample interpolating 4 samples per output sample. + /// + Default, + + /// + /// Resample interpolating 8 samples per output sample. + /// + High, + + /// + /// Resample interpolating 1 samples per output sample. + /// + Low + } + } +} diff --git a/Ryujinx.Audio.Renderer/Parameter/VoiceOutStatus.cs b/Ryujinx.Audio.Renderer/Parameter/VoiceOutStatus.cs new file mode 100644 index 00000000..1b21175e --- /dev/null +++ b/Ryujinx.Audio.Renderer/Parameter/VoiceOutStatus.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information about a voice. + /// + /// See + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct VoiceOutStatus + { + /// + /// 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 total amount of consumed. + /// + public uint PlayedWaveBuffersCount; + + /// + /// If set to true, the voice was dropped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropFlag; + + /// + /// Reserved/unused. + /// + private unsafe fixed byte _reserved[3]; + } +} diff --git a/Ryujinx.Audio.Renderer/RendererConstants.cs b/Ryujinx.Audio.Renderer/RendererConstants.cs new file mode 100644 index 00000000..32efebc8 --- /dev/null +++ b/Ryujinx.Audio.Renderer/RendererConstants.cs @@ -0,0 +1,167 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer +{ + /// + /// Define constants used by the Audio Renderer. + /// + public static class RendererConstants + { + /// + /// The maximum number of channels supported. (6 channels for 5.1 surround) + /// + public const int ChannelCountMax = 6; + + /// + /// The maximum number of channels supported per voice. + /// + public const int VoiceChannelCountMax = ChannelCountMax; + + /// + /// The max count of mix buffer supported per operations (volumes, mix effect, ...) + /// + public const int MixBufferCountMax = 24; + + /// + /// The max count of wavebuffer per voice. + /// + public const int VoiceWaveBufferCount = 4; + + /// + /// The max count of biquad filter per voice. + /// + public const int VoiceBiquadFilterCount = 2; + + /// + /// The lowest priority that a voice can have. + /// + public const int VoiceLowestPriority = 0xFF; + + /// + /// The highest priority that a voice can have. + /// + /// Voices with the highest priority will not be dropped if a voice drop needs to occur. + public const int VoiceHighestPriority = 0; + + /// + /// Max that can be returned by . + /// + public const int MaxErrorInfos = 10; + + /// + /// Default alignment for buffers. + /// + public const int BufferAlignment = 0x40; + + /// + /// Alignment required for the work buffer. + /// + public const int WorkBufferAlignment = 0x1000; + + /// + /// Alignment required for every performance metrics frame. + /// + public const int PerformanceMetricsPerFramesSizeAlignment = 0x100; + + /// + /// The id of the final mix. + /// + public const int FinalMixId = 0; + + /// + /// The id defining an unused mix id. + /// + public const int UnusedMixId = int.MaxValue; + + /// + /// The id defining an unused splitter id as a signed integer. + /// + public const int UnusedSplitterIdInt = -1; + + /// + /// The id defining an unused splitter id. + /// + public const uint UnusedSplitterId = uint.MaxValue; + + /// + /// The id of invalid/unused node id. + /// + public const int InvalidNodeId = -268435456; + + /// + /// The indice considered invalid for processing order. + /// + public const int InvalidProcessingOrder = -1; + + /// + /// The max number of audio renderer sessions allowed to be created system wide. + /// + public const int AudioRendererSessionCountMax = 2; + + /// + /// The target sample rate of the audio renderer. (48kHz) + /// + public const uint TargetSampleRate = 48000; + + /// + /// The target sample size of the audio renderer. (PCM16) + /// + public const int TargetSampleSize = sizeof(ushort); + + /// + /// The target sample count per audio renderer update. + /// + public const int TargetSampleCount = 240; + + /// + /// The size of an upsampler entry to process upsampling to . + /// + public const int UpSampleEntrySize = TargetSampleCount * VoiceChannelCountMax; + + /// + /// The target audio latency computed from and . + /// + public const int AudioProcessorMaxUpdateTimeTarget = 1000000000 / ((int)TargetSampleRate / TargetSampleCount); // 5.00 ms + + /// + /// The max update time of the DSP on original hardware. + /// + public const int AudioProcessorMaxUpdateTime = 5760000; // 5.76 ms + + /// + /// The max update time per audio renderer session. + /// + public const int AudioProcessorMaxUpdateTimePerSessions = AudioProcessorMaxUpdateTime / AudioRendererSessionCountMax; + + /// + /// Guest timer frequency used for system ticks. + /// + public const int TargetTimerFrequency = 19200000; + + /// + /// The default coefficients used for standard 5.1 surround to stereo downmixing. + /// + public static float[] DefaultSurroundToStereoCoefficients = new float[4] + { + 1.0f, + 0.707f, + 0.251f, + 0.707f, + }; + } +} diff --git a/Ryujinx.Audio.Renderer/ResultCode.cs b/Ryujinx.Audio.Renderer/ResultCode.cs new file mode 100644 index 00000000..84a47eb2 --- /dev/null +++ b/Ryujinx.Audio.Renderer/ResultCode.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer +{ + public enum ResultCode + { + ModuleId = 153, + ErrorCodeShift = 9, + + Success = 0, + + OperationFailed = (2 << ErrorCodeShift) | ModuleId, + WorkBufferTooSmall = (4 << ErrorCodeShift) | ModuleId, + InvalidUpdateInfo = (41 << ErrorCodeShift) | ModuleId, + InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId, + InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId, + UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId, + } +} diff --git a/Ryujinx.Audio.Renderer/Ryujinx.Audio.Renderer.csproj b/Ryujinx.Audio.Renderer/Ryujinx.Audio.Renderer.csproj new file mode 100644 index 00000000..391e648a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Ryujinx.Audio.Renderer.csproj @@ -0,0 +1,38 @@ + + + + netcoreapp3.1 + win-x64;osx-x64;linux-x64 + Debug;Release;Profile Debug;Profile Release + + + + true + + + + true + TRACE;USE_PROFILING + false + + + + true + + + + true + TRACE;USE_PROFILING + true + + + + + + + + + + + + diff --git a/Ryujinx.Audio.Renderer/Server/AudioRenderSystem.cs b/Ryujinx.Audio.Renderer/Server/AudioRenderSystem.cs new file mode 100644 index 00000000..30f326a6 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/AudioRenderSystem.cs @@ -0,0 +1,838 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Server.Mix; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Server.Types; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Audio.Renderer.Server.Voice; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Threading; + +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server +{ + public class AudioRenderSystem : IDisposable + { + private object _lock = new object(); + + private AudioRendererExecutionMode _executionMode; + private IWritableEvent _systemEvent; + private ManualResetEvent _terminationEvent; + private MemoryPoolState _dspMemoryPoolState; + private VoiceContext _voiceContext; + private MixContext _mixContext; + private SinkContext _sinkContext; + private SplitterContext _splitterContext; + private EffectContext _effectContext; + private PerformanceManager _performanceManager; + private UpsamplerManager _upsamplerManager; + private bool _isActive; + private BehaviourContext _behaviourContext; + private ulong _totalElapsedTicksUpdating; + private ulong _totalElapsedTicks; + private int _sessionId; + private Memory _memoryPools; + + private uint _sampleRate; + private uint _sampleCount; + private uint _mixBufferCount; + private uint _voiceChannelCountMax; + private uint _upsamplerCount; + private uint _memoryPoolCount; + private uint _processHandle; + private ulong _appletResourceId; + + private WritableRegion _workBufferRegion; + private MemoryHandle _workBufferMemoryPin; + + private Memory _mixBuffer; + private Memory _depopBuffer; + + private uint _renderingTimeLimitPercent; + private bool _voiceDropEnabled; + private uint _voiceDropCount; + private bool _isDspRunningBehind; + + private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator; + + private Memory _performanceBuffer; + + public MemoryManager MemoryManager { get; private set; } + + private ulong _elapsedFrameCount; + private ulong _renderingStartTick; + + private AudioRendererManager _manager; + + public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent) + { + _manager = manager; + _terminationEvent = new ManualResetEvent(false); + _dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); + _voiceContext = new VoiceContext(); + _mixContext = new MixContext(); + _sinkContext = new SinkContext(); + _splitterContext = new SplitterContext(); + _effectContext = new EffectContext(); + + _commandProcessingTimeEstimator = null; + _systemEvent = systemEvent; + _behaviourContext = new BehaviourContext(); + + _totalElapsedTicksUpdating = 0; + _sessionId = 0; + } + + public ResultCode Initialize(ref AudioRendererConfiguration parameter, uint processHandle, CpuAddress workBuffer, ulong workBufferSize, int sessionId, ulong appletResourceId, MemoryManager memoryManager) + { + if (!BehaviourContext.CheckValidRevision(parameter.Revision)) + { + return ResultCode.OperationFailed; + } + + if (GetWorkBufferSize(ref parameter) > workBufferSize) + { + return ResultCode.WorkBufferTooSmall; + } + + Debug.Assert(parameter.RenderingDevice == AudioRendererRenderingDevice.Dsp && parameter.ExecutionMode == AudioRendererExecutionMode.Auto); + + Logger.Info?.Print(LogClass.AudioRenderer, $"Initializing with REV{BehaviourContext.GetRevisionNumber(parameter.Revision)}"); + + _behaviourContext.SetUserRevision(parameter.Revision); + + _sampleRate = parameter.SampleRate; + _sampleCount = parameter.SampleCount; + _mixBufferCount = parameter.MixBufferCount; + _voiceChannelCountMax = RendererConstants.VoiceChannelCountMax; + _upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount; + _appletResourceId = appletResourceId; + _memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * RendererConstants.VoiceWaveBufferCount; + _executionMode = parameter.ExecutionMode; + _sessionId = sessionId; + MemoryManager = memoryManager; + + WorkBufferAllocator workBufferAllocator; + + _workBufferRegion = MemoryManager.GetWritableRegion(workBuffer, (int)workBufferSize); + _workBufferRegion.Memory.Span.Fill(0); + _workBufferMemoryPin = _workBufferRegion.Memory.Pin(); + + workBufferAllocator = new WorkBufferAllocator(_workBufferRegion.Memory); + + PoolMapper poolMapper = new PoolMapper(processHandle, false); + poolMapper.InitializeSystemPool(ref _dspMemoryPoolState, workBuffer, workBufferSize); + + _mixBuffer = workBufferAllocator.Allocate(_sampleCount * (_voiceChannelCountMax + _mixBufferCount), 0x10); + + if (_mixBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + Memory upSamplerWorkBuffer = workBufferAllocator.Allocate(RendererConstants.TargetSampleCount * (_voiceChannelCountMax + _mixBufferCount) * _upsamplerCount, 0x10); + + if (upSamplerWorkBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _depopBuffer = workBufferAllocator.Allocate((ulong)BitUtils.AlignUp(parameter.MixBufferCount, RendererConstants.BufferAlignment), RendererConstants.BufferAlignment); + + if (_depopBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Invalidate DSP cache on what was currently allocated with workBuffer. + AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset); + + Debug.Assert((workBufferAllocator.Offset % RendererConstants.BufferAlignment) == 0); + + Memory voices = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceState.Alignment); + + if (voices.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + foreach (ref VoiceState voice in voices.Span) + { + voice.Initialize(); + } + + // A pain to handle as we can't have VoiceState*, use indices to be a bit more safe + Memory sortedVoices = workBufferAllocator.Allocate(parameter.VoiceCount, 0x10); + + if (sortedVoices.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Clear memory (use -1 as it's an invalid index) + sortedVoices.Span.Fill(-1); + + Memory voiceChannelResources = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceChannelResource.Alignment); + + if (voiceChannelResources.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + for (uint id = 0; id < voiceChannelResources.Length; id++) + { + ref VoiceChannelResource voiceChannelResource = ref voiceChannelResources.Span[(int)id]; + + voiceChannelResource.Id = id; + voiceChannelResource.IsUsed = false; + } + + Memory voiceUpdateStates = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceUpdateState.Align); + + if (voiceUpdateStates.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + uint mixesCount = parameter.SubMixBufferCount + 1; + + Memory mixes = workBufferAllocator.Allocate(mixesCount, MixState.Alignment); + + if (mixes.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + if (parameter.EffectCount == 0) + { + foreach (ref MixState mix in mixes.Span) + { + mix = new MixState(Memory.Empty, ref _behaviourContext); + } + } + else + { + Memory effectProcessingOrderArray = workBufferAllocator.Allocate(parameter.EffectCount * mixesCount, 0x10); + + foreach (ref MixState mix in mixes.Span) + { + mix = new MixState(effectProcessingOrderArray.Slice(0, (int)parameter.EffectCount), ref _behaviourContext); + + effectProcessingOrderArray = effectProcessingOrderArray.Slice((int)parameter.EffectCount); + } + } + + // Initialize the final mix id + mixes.Span[0].MixId = RendererConstants.FinalMixId; + + Memory sortedMixesState = workBufferAllocator.Allocate(mixesCount, 0x10); + + if (sortedMixesState.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Clear memory (use -1 as it's an invalid index) + sortedMixesState.Span.Fill(-1); + + Memory nodeStatesWorkBuffer = Memory.Empty; + Memory edgeMatrixWorkBuffer = Memory.Empty; + + if (_behaviourContext.IsSplitterSupported()) + { + nodeStatesWorkBuffer = workBufferAllocator.Allocate((uint)NodeStates.GetWorkBufferSize((int)mixesCount), 1); + edgeMatrixWorkBuffer = workBufferAllocator.Allocate((uint)EdgeMatrix.GetWorkBufferSize((int)mixesCount), 1); + + if (nodeStatesWorkBuffer.IsEmpty || edgeMatrixWorkBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + } + + _mixContext.Initialize(sortedMixesState, mixes, nodeStatesWorkBuffer, edgeMatrixWorkBuffer); + + _memoryPools = workBufferAllocator.Allocate(_memoryPoolCount, MemoryPoolState.Alignment); + + if (_memoryPools.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + foreach (ref MemoryPoolState state in _memoryPools.Span) + { + state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + } + + if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator)) + { + return ResultCode.WorkBufferTooSmall; + } + + _processHandle = processHandle; + + _upsamplerManager = new UpsamplerManager(upSamplerWorkBuffer, _upsamplerCount); + + _effectContext.Initialize(parameter.EffectCount); + _sinkContext.Initialize(parameter.SinkCount); + + Memory voiceUpdateStatesDsp = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceUpdateState.Align); + + if (voiceUpdateStatesDsp.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _voiceContext.Initialize(sortedVoices, voices, voiceChannelResources, voiceUpdateStates, voiceUpdateStatesDsp, parameter.VoiceCount); + + if (parameter.PerformanceMetricFramesCount > 0) + { + ulong performanceBufferSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref _behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC; + + _performanceBuffer = workBufferAllocator.Allocate(performanceBufferSize, RendererConstants.BufferAlignment); + + if (_performanceBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _performanceManager = PerformanceManager.Create(_performanceBuffer, ref parameter, _behaviourContext); + } + else + { + _performanceManager = null; + } + + _totalElapsedTicksUpdating = 0; + _totalElapsedTicks = 0; + _renderingTimeLimitPercent = 100; + _voiceDropEnabled = parameter.VoiceDropEnabled && _executionMode == AudioRendererExecutionMode.Auto; + + AudioProcessorMemoryManager.InvalidateDataCache(workBuffer, workBufferSize); + + _processHandle = processHandle; + _elapsedFrameCount = 0; + + switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion()) + { + case 1: + _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion1(_sampleCount, _mixBufferCount); + break; + case 2: + _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion2(_sampleCount, _mixBufferCount); + break; + case 3: + _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion3(_sampleCount, _mixBufferCount); + break; + default: + throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}."); + } + + return ResultCode.Success; + } + + public void Start() + { + Logger.Info?.Print(LogClass.AudioRenderer, $"Starting renderer id {_sessionId}"); + + lock (_lock) + { + _elapsedFrameCount = 0; + _isActive = true; + } + } + + public void Stop() + { + Logger.Info?.Print(LogClass.AudioRenderer, $"Stopping renderer id {_sessionId}"); + + lock (_lock) + { + _isActive = false; + } + + if (_executionMode == AudioRendererExecutionMode.Auto) + { + _terminationEvent.WaitOne(); + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}"); + } + + public ResultCode Update(Memory output, Memory performanceOutput, ReadOnlyMemory input) + { + lock (_lock) + { + ulong updateStartTicks = GetSystemTicks(); + + output.Span.Fill(0); + + StateUpdater stateUpdater = new StateUpdater(input, output, _processHandle, _behaviourContext); + + ResultCode result; + + result = stateUpdater.UpdateBehaviourContext(); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateMemoryPools(_memoryPools.Span); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateVoiceChannelResources(_voiceContext); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools); + + if (result != ResultCode.Success) + { + return result; + } + + if (_behaviourContext.IsSplitterSupported()) + { + result = stateUpdater.UpdateSplitter(_splitterContext); + + if (result != ResultCode.Success) + { + return result; + } + } + + result = stateUpdater.UpdateMixes(_mixContext, GetMixBufferCount(), _effectContext, _splitterContext); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdatePerformanceBuffer(_performanceManager, performanceOutput.Span); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateErrorInfo(); + + if (result != ResultCode.Success) + { + return result; + } + + if (_behaviourContext.IsElapsedFrameCountSupported()) + { + result = stateUpdater.UpdateRendererInfo(_elapsedFrameCount); + + if (result != ResultCode.Success) + { + return result; + } + } + + result = stateUpdater.CheckConsumedSize(); + + if (result != ResultCode.Success) + { + return result; + } + + _systemEvent.Clear(); + + ulong updateEndTicks = GetSystemTicks(); + + _totalElapsedTicksUpdating += (updateEndTicks - updateStartTicks); + + return result; + } + } + + private ulong GetSystemTicks() + { + double ticks = ARMeilleure.State.ExecutionContext.ElapsedTicks * ARMeilleure.State.ExecutionContext.TickFrequency; + + return (ulong)(ticks * RendererConstants.TargetTimerFrequency); + } + + private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp) + { + int i; + + for (i = 0; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand command = commandBuffer.CommandList.Commands[i]; + + CommandType commandType = command.CommandType; + + if (commandType == CommandType.AdpcmDataSourceVersion1 || + commandType == CommandType.AdpcmDataSourceVersion2 || + commandType == CommandType.PcmInt16DataSourceVersion1 || + commandType == CommandType.PcmInt16DataSourceVersion2 || + commandType == CommandType.PcmFloatDataSourceVersion1 || + commandType == CommandType.PcmFloatDataSourceVersion2 || + commandType == CommandType.Performance) + { + break; + } + } + + uint voiceDropped = 0; + + for (; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand targetCommand = commandBuffer.CommandList.Commands[i]; + + int targetNodeId = targetCommand.NodeId; + + if (voicesEstimatedTime <= deltaTimeDsp || NodeIdHelper.GetType(targetNodeId) != NodeIdType.Voice) + { + break; + } + + ref VoiceState voice = ref _voiceContext.GetState(NodeIdHelper.GetBase(targetNodeId)); + + if (voice.Priority == RendererConstants.VoiceHighestPriority) + { + break; + } + + // We can safely drop this voice, disable all associated commands while activating depop preparation commands. + voiceDropped++; + voice.VoiceDropFlag = true; + + Logger.Warning?.Print(LogClass.AudioRenderer, $"Dropping voice {voice.NodeId}"); + + for (; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand command = commandBuffer.CommandList.Commands[i]; + + if (command.NodeId != targetNodeId) + { + break; + } + + if (command.CommandType == CommandType.DepopPrepare) + { + command.Enabled = true; + } + else if (command.CommandType == CommandType.Performance || !command.Enabled) + { + continue; + } + else + { + command.Enabled = false; + + voicesEstimatedTime -= (long)command.EstimatedProcessingTime; + } + } + } + + return voiceDropped; + } + + private void GenerateCommandList(out CommandList commandList) + { + Debug.Assert(_executionMode == AudioRendererExecutionMode.Auto); + + PoolMapper.ClearUsageState(_memoryPools); + + ulong startTicks = GetSystemTicks(); + + commandList = new CommandList(this); + + if (_performanceManager != null) + { + _performanceManager.TapFrame(_isDspRunningBehind, _voiceDropCount, _renderingStartTick); + + _isDspRunningBehind = false; + _voiceDropCount = 0; + _renderingStartTick = 0; + } + + CommandBuffer commandBuffer = new CommandBuffer(commandList, _commandProcessingTimeEstimator); + + CommandGenerator commandGenerator = new CommandGenerator(commandBuffer, GetContext(), _voiceContext, _mixContext, _effectContext, _sinkContext, _splitterContext, _performanceManager); + + _voiceContext.Sort(); + commandGenerator.GenerateVoices(); + + long voicesEstimatedTime = (long)commandBuffer.EstimatedProcessingTime; + + commandGenerator.GenerateSubMixes(); + commandGenerator.GenerateFinalMixes(); + commandGenerator.GenerateSinks(); + + long totalEstimatedTime = (long)commandBuffer.EstimatedProcessingTime; + + if (_voiceDropEnabled) + { + long maxDspTime = GetMaxAllocatedTimeForDsp(); + + long restEstimateTime = totalEstimatedTime - voicesEstimatedTime; + + long deltaTimeDsp = Math.Max(maxDspTime - restEstimateTime, 0); + + _voiceDropCount = ComputeVoiceDrop(commandBuffer, voicesEstimatedTime, deltaTimeDsp); + } + + _voiceContext.UpdateForCommandGeneration(); + + ulong endTicks = GetSystemTicks(); + + _totalElapsedTicks = endTicks - startTicks; + + _renderingStartTick = GetSystemTicks(); + _elapsedFrameCount++; + } + + private int GetMaxAllocatedTimeForDsp() + { + return (int)(RendererConstants.AudioProcessorMaxUpdateTimePerSessions * _behaviourContext.GetAudioRendererProcessingTimeLimit() * (GetRenderingTimeLimit() / 100.0f)); + } + + public void SendCommands() + { + lock (_lock) + { + if (_isActive) + { + _terminationEvent.Reset(); + + GenerateCommandList(out CommandList commands); + + _manager.Processor.Send(_sessionId, + commands, + GetMaxAllocatedTimeForDsp(), + _appletResourceId); + + _systemEvent.Signal(); + } + else + { + _terminationEvent.Set(); + } + } + } + + public uint GetMixBufferCount() + { + return _mixBufferCount; + } + + public void SetRenderingTimeLimitPercent(uint percent) + { + Debug.Assert(percent <= 100); + + _renderingTimeLimitPercent = percent; + } + + public uint GetRenderingTimeLimit() + { + return _renderingTimeLimitPercent; + } + + public Memory GetMixBuffer() + { + return _mixBuffer; + } + + public uint GetSampleCount() + { + return _sampleCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public uint GetVoiceChannelCountMax() + { + return _voiceChannelCountMax; + } + + public bool IsActive() + { + return _isActive; + } + + private RendererSystemContext GetContext() + { + return new RendererSystemContext + { + ChannelCount = _manager.OutputDevices[_sessionId].GetChannelCount(), + BehaviourContext = _behaviourContext, + DepopBuffer = _depopBuffer, + MixBufferCount = GetMixBufferCount(), + SessionId = _sessionId, + UpsamplerManager = _upsamplerManager + }; + } + + public int GetSessionId() + { + return _sessionId; + } + + public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(parameter.Revision); + + uint mixesCount = parameter.SubMixBufferCount + 1; + + uint memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * RendererConstants.VoiceWaveBufferCount; + + ulong size = 0; + + // Mix Buffers + size = WorkBufferAllocator.GetTargetSize(size, parameter.SampleCount * (RendererConstants.VoiceChannelCountMax + parameter.MixBufferCount), 0x10); + + // Upsampler workbuffer + size = WorkBufferAllocator.GetTargetSize(size, RendererConstants.TargetSampleCount * (RendererConstants.VoiceChannelCountMax + parameter.MixBufferCount) * (parameter.SinkCount + parameter.SubMixBufferCount), 0x10); + + // Depop buffer + size = WorkBufferAllocator.GetTargetSize(size, (ulong)BitUtils.AlignUp(parameter.MixBufferCount, RendererConstants.BufferAlignment), RendererConstants.BufferAlignment); + + // Voice + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, 0x10); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceChannelResource.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align); + + // Mix + size = WorkBufferAllocator.GetTargetSize(size, mixesCount, MixState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.EffectCount * mixesCount, 0x10); + size = WorkBufferAllocator.GetTargetSize(size, mixesCount, 0x10); + + if (behaviourContext.IsSplitterSupported()) + { + size += (ulong)BitUtils.AlignUp(NodeStates.GetWorkBufferSize((int)mixesCount) + EdgeMatrix.GetWorkBufferSize((int)mixesCount), 0x10); + } + + // Memory Pool + size = WorkBufferAllocator.GetTargetSize(size, memoryPoolCount, MemoryPoolState.Alignment); + + // Splitter + size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter); + + // DSP Voice + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align); + + // Performance + if (parameter.PerformanceMetricFramesCount > 0) + { + ulong performanceMetricsPerFramesSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC; + + size += BitUtils.AlignUp(performanceMetricsPerFramesSize, RendererConstants.PerformanceMetricsPerFramesSizeAlignment); + } + + return BitUtils.AlignUp(size, RendererConstants.WorkBufferAlignment); + } + + public ResultCode QuerySystemEvent(out IWritableEvent systemEvent) + { + systemEvent = default; + + if (_executionMode == AudioRendererExecutionMode.Manual) + { + return ResultCode.UnsupportedOperation; + } + + systemEvent = _systemEvent; + + return ResultCode.Success; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_isActive) + { + Stop(); + } + + PoolMapper mapper = new PoolMapper(_processHandle, false); + mapper.Unmap(ref _dspMemoryPoolState); + + PoolMapper.ClearUsageState(_memoryPools); + + for (int i = 0; i < _memoryPoolCount; i++) + { + ref MemoryPoolState memoryPool = ref _memoryPools.Span[i]; + + if (memoryPool.IsMapped()) + { + mapper.Unmap(ref memoryPool); + } + } + + _manager.Unregister(this); + _terminationEvent.Dispose(); + _workBufferMemoryPin.Dispose(); + _workBufferRegion.Dispose(); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs b/Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs new file mode 100644 index 00000000..023dd477 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs @@ -0,0 +1,333 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp; +using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using System; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// The audio renderer manager. + /// + public class AudioRendererManager : IDisposable + { + /// + /// Lock used for session allocation. + /// + private object _sessionLock = new object(); + + /// + /// Lock used to control the running state. + /// + private object _audioProcessorLock = new object(); + + /// + /// The session ids allocation table. + /// + private int[] _sessionIds; + + /// + /// The events linked to each session. + /// + private IWritableEvent[] _sessionsSystemEvent; + + /// + /// The sessions instances. + /// + private AudioRenderSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// The worker thread used to run . + /// + private Thread _workerThread; + + /// + /// Indicate if the worker thread and are running. + /// + private bool _isRunning; + + /// + /// The output devices associated to each session. + /// + // TODO: get ride of this with the audout rewrite. + public HardwareDevice[] OutputDevices { get; private set; } + + /// + /// The instance associated to this manager. + /// + public AudioProcessor Processor { get; } + + /// + /// Create a new . + /// + public AudioRendererManager() + { + Processor = new AudioProcessor(); + _sessionIds = new int[RendererConstants.AudioRendererSessionCountMax]; + _sessions = new AudioRenderSystem[RendererConstants.AudioRendererSessionCountMax]; + _activeSessionCount = 0; + + for (int i = 0; i < _sessionIds.Length; i++) + { + _sessionIds[i] = i; + } + } + + /// + /// Initialize the . + /// + /// The events associated to each session. + /// The output devices associated to each session. + public void Initialize(IWritableEvent[] sessionSystemEvents, HardwareDevice[] devices) + { + _sessionsSystemEvent = sessionSystemEvents; + OutputDevices = devices; + } + + /// + /// Get the work buffer size required by a session. + /// + /// The user configuration + /// The work buffer size required by a session. + public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) + { + return AudioRenderSystem.GetWorkBufferSize(ref parameter); + } + + /// + /// Acquire a new session id. + /// + /// A new session id. + private int AcquireSessionId() + { + lock (_sessionLock) + { + int index = _activeSessionCount; + + Debug.Assert(index < _sessionIds.Length); + + int sessionId = _sessionIds[index]; + + _sessionIds[index] = -1; + + _activeSessionCount++; + + Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new renderer ({sessionId})"); + + return sessionId; + } + } + + /// + /// Release a given . + /// + /// The session id to release. + private void ReleaseSessionId(int sessionId) + { + lock (_sessionLock) + { + Debug.Assert(_activeSessionCount > 0); + + int newIndex = --_activeSessionCount; + + _sessionIds[newIndex] = sessionId; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered renderer ({sessionId})"); + } + + /// + /// Check if there is any audio renderer active. + /// + /// Returns true if there is any audio renderer active. + private bool HasAnyActiveRendererLocked() + { + foreach (AudioRenderSystem renderer in _sessions) + { + if (renderer != null) + { + return true; + } + } + + return false; + } + + /// + /// Start the and worker thread. + /// + private void StartLocked() + { + _isRunning = true; + + // TODO: virtual device mapping (IAudioDevice) + Processor.SetOutputDevices(OutputDevices); + Processor.Start(); + + _workerThread = new Thread(SendCommands) + { + Name = "AudioRendererManager.Worker" + }; + + _workerThread.Start(); + } + + /// + /// Stop the and worker thread. + /// + private void StopLocked() + { + _isRunning = false; + + _workerThread.Join(); + Processor.Stop(); + + Logger.Info?.Print(LogClass.AudioRenderer, "Stopped audio renderer"); + } + + /// + /// Worker main function. This is used to dispatch audio renderer commands to the . + /// + private void SendCommands() + { + Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio renderer"); + Processor.Wait(); + + while (_isRunning) + { + lock (_sessionLock) + { + foreach(AudioRenderSystem renderer in _sessions) + { + renderer?.SendCommands(); + } + } + + Processor.Signal(); + Processor.Wait(); + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioRenderSystem renderer) + { + lock (_sessionLock) + { + _sessions[renderer.GetSessionId()] = renderer; + } + + lock (_audioProcessorLock) + { + if (!_isRunning) + { + StartLocked(); + } + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioRenderSystem renderer) + { + lock (_sessionLock) + { + int sessionId = renderer.GetSessionId(); + + _sessions[renderer.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + + lock (_audioProcessorLock) + { + if (_isRunning && !HasAnyActiveRendererLocked()) + { + StopLocked(); + } + } + } + + /// + /// Open a new + /// + /// The new + /// The memory manager that will be used for all guest memory operations. + /// The user configuration + /// The applet resource user id of the application. + /// The guest work buffer address. + /// The guest work buffer size. + /// The process handle of the application. + /// A reporting an error or a success. + public ResultCode OpenAudioRenderer(out AudioRenderSystem renderer, MemoryManager memoryManager, ref AudioRendererConfiguration parameter, ulong appletResourceUserId, ulong workBufferAddress, ulong workBufferSize, uint processHandle) + { + int sessionId = AcquireSessionId(); + + AudioRenderSystem audioRenderer = new AudioRenderSystem(this, _sessionsSystemEvent[sessionId]); + + ResultCode result = audioRenderer.Initialize(ref parameter, processHandle, workBufferAddress, workBufferSize, sessionId, appletResourceUserId, memoryManager); + + if (result == ResultCode.Success) + { + renderer = audioRenderer; + + Register(renderer); + } + else + { + ReleaseSessionId(sessionId); + + renderer = null; + } + + return result; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Processor.Dispose(); + + foreach (HardwareDevice device in OutputDevices) + { + device.Dispose(); + } + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/BehaviourContext.cs b/Ryujinx.Audio.Renderer/Server/BehaviourContext.cs new file mode 100644 index 00000000..8a3e1aa9 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/BehaviourContext.cs @@ -0,0 +1,405 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Behaviour context. + /// + /// This handles features based on the audio renderer revision provided by the user. + public class BehaviourContext + { + /// + /// The base magic of the Audio Renderer revision. + /// + public const int BaseRevisionMagic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24); + + /// + /// REV1: first revision. + /// + public const int Revision1 = 1 << 24; + + /// + /// REV2: Added support for splitter and fix GC-ADPCM context not being provided to the DSP. + /// + /// This was added in system update 2.0.0 + public const int Revision2 = 2 << 24; + + /// + /// REV3: Incremented the max pre-delay from 150 to 350 for the reverb command and removed the (unused) codec system. + /// + /// This was added in system update 3.0.0 + public const int Revision3 = 3 << 24; + + /// + /// REV4: Added USB audio device support and incremented the rendering limit percent to 75%. + /// + /// This was added in system update 4.0.0 + public const int Revision4 = 4 << 24; + + /// + /// REV5: , were added to voice. + /// A new performance frame format (version 2) was added with support for more information about DSP timing. + /// was added to supply the count of update done sent to the DSP. + /// A new version of the command estimator was added to address timing changes caused by the voice changes. + /// Additionally, the rendering limit percent was incremented to 80%. + /// + /// + /// This was added in system update 6.0.0 + public const int Revision5 = 5 << 24; + + /// + /// REV6: This fixed a bug in the biquad filter command not clearing up with usage state. + /// + /// This was added in system update 6.1.0 + public const int Revision6 = 6 << 24; + + /// + /// REV7: Client side (finally) doesn't send all the mix client state to the server and can do partial updates. + /// + /// This was added in system update 8.0.0 + public const int Revision7 = 7 << 24; + + /// + /// REV8: + /// Wavebuffer was changed to support more control over loop (you can now specify where to start and end a loop, and how many times to loop). + /// was added (see for more info). + /// Final leftovers of the codec system were removed. + /// support was added. + /// A new version of the command estimator was added to address timing changes caused by the voice and command changes. + /// + /// This was added in system update 9.0.0 + public const int Revision8 = 8 << 24; + + /// + /// Last revision supported by the implementation. + /// + public const int LastRevision = Revision8; + + /// + /// Target revision magic supported by the implementation. + /// + public const int ProcessRevision = BaseRevisionMagic + LastRevision; + + /// + /// Get the revision number from the revision magic. + /// + /// The revision magic. + /// The revision number. + public static int GetRevisionNumber(int revision) => (revision - BaseRevisionMagic) >> 24; + + /// + /// Current active revision. + /// + public int UserRevision { get; private set; } + + /// + /// Error storage. + /// + private ErrorInfo[] _errorInfos; + + /// + /// Current position in the array. + /// + private uint _errorIndex; + + /// + /// Current flags of the . + /// + private ulong _flags; + + /// + /// Create a new instance of . + /// + public BehaviourContext() + { + UserRevision = 0; + _errorInfos = new ErrorInfo[RendererConstants.MaxErrorInfos]; + _errorIndex = 0; + } + + /// + /// Set the active revision. + /// + /// The active revision. + public void SetUserRevision(int userRevision) + { + UserRevision = userRevision; + } + + /// + /// Update flags of the . + /// + /// The new flags. + public void UpdateFlags(ulong flags) + { + _flags = flags; + } + + /// + /// Check if a given revision is valid/supported. + /// + /// The revision magic to check. + /// Returns true if the given revision is valid/supported + public static bool CheckValidRevision(int revision) + { + return GetRevisionNumber(revision) <= GetRevisionNumber(ProcessRevision); + } + + /// + /// Check if the given revision is greater than or equal the supported revision. + /// + /// The revision magic to check. + /// The revision magic of the supported revision. + /// Returns true if the given revision is greater than or equal the supported revision. + public static bool CheckFeatureSupported(int revision, int supportedRevision) + { + int revA = GetRevisionNumber(revision); + int revB = GetRevisionNumber(supportedRevision); + + if (revA > LastRevision) + { + revA = 1; + } + + if (revB > LastRevision) + { + revB = 1; + } + + return revA >= revB; + } + + /// + /// Check if the memory pool mapping bypass flag is active. + /// + /// True if the memory pool mapping bypass flag is active. + public bool IsMemoryPoolForceMappingEnabled() + { + return (_flags & 1) != 0; + } + + /// + /// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP. + /// + /// True if if the audio renderer should fix it. + public bool IsAdpcmLoopContextBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2); + } + + /// + /// Check if the audio renderer should accept splitters. + /// + /// True if the audio renderer should accept splitters. + public bool IsSplitterSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2); + } + + /// + /// Check if the audio renderer should use a max pre-delay of 350 instead of 150. + /// + /// True if the max pre-delay must be 350. + public bool IsLongSizePreDelaySupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision3); + } + + /// + /// Check if the audio renderer should expose USB audio device. + /// + /// True if the audio renderer should expose USB audio device. + public bool IsAudioUsbDeviceOutputSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4); + } + + /// + /// Get the percentage allocated to the audio renderer on the DSP for processing. + /// + /// The percentage allocated to the audio renderer on the DSP for processing. + public float GetAudioRendererProcessingTimeLimit() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 0.80f; + } + else if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4)) + { + return 0.75f; + } + + return 0.70f; + } + + /// + /// Check if the audio render should support voice flushing. + /// + /// True if the audio render should support voice flushing. + public bool IsFlushVoiceWaveBuffersSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should trust the user destination count in . + /// + /// True if the audio renderer should trust the user destination count. + public bool IsSplitterBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should supply the elapsed frame count to the user when updating. + /// + /// True if the audio renderer should supply the elapsed frame count to the user when updating. + public bool IsElapsedFrameCountSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Get the performance metric data format version. + /// + /// The performance metric data format version. + public uint GetPerformanceMetricsDataFormat() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 2; + } + else + { + return 1; + } + } + + /// + /// Check if the audio renderer should support . + /// + /// True if the audio renderer should support . + public bool IsDecodingBehaviourFlagSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should fix the biquad filter command not clearing up with usage state. + /// + /// True if the biquad filter state should be cleared. + public bool IsBiquadFilterEffectStateClearBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision6); + } + + /// + /// Check if the audio renderer should accept partial mix updates. + /// + /// True if the audio renderer should accept partial mix updates. + public bool IsMixInParameterDirtyOnlyUpdateSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision7); + } + + /// + /// Check if the audio renderer should use the new wavebuffer format. + /// + /// True if the audio renderer should use the new wavebuffer format. + public bool IsWaveBufferVersion2Supported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8); + } + + /// + /// Get the version of the . + /// + /// The version of the . + public int GetCommandProcessingTimeEstimatorVersion() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8)) + { + return 3; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 2; + } + + return 1; + } + + /// + /// Append a new to the error array. + /// + /// The new to add. + public void AppendError(ref ErrorInfo errorInfo) + { + Debug.Assert(errorInfo.ErrorCode == ResultCode.Success); + + if (_errorIndex <= RendererConstants.MaxErrorInfos - 1) + { + _errorInfos[_errorIndex++] = errorInfo; + } + } + + /// + /// Copy the internal array to the given and output the count copied. + /// + /// The output . + /// The output error count containing the count of copied. + public void CopyErrorInfo(Span errorInfos, out uint errorCount) + { + if (errorInfos.Length != RendererConstants.MaxErrorInfos) + { + throw new ArgumentException("Invalid size of errorInfos span!"); + } + + errorCount = Math.Min(_errorIndex, RendererConstants.MaxErrorInfos); + + for (int i = 0; i < RendererConstants.MaxErrorInfos; i++) + { + if (i < errorCount) + { + errorInfos[i] = _errorInfos[i]; + } + else + { + errorInfos[i] = new ErrorInfo + { + ErrorCode = 0, + ExtraErrorInfo = 0 + }; + } + } + } + + /// + /// Clear the array. + /// + public void ClearError() + { + _errorIndex = 0; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/CommandBuffer.cs b/Ryujinx.Audio.Renderer/Server/CommandBuffer.cs new file mode 100644 index 00000000..c5e6d10a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/CommandBuffer.cs @@ -0,0 +1,484 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// An API to generate commands and aggregate them into a . + /// + public class CommandBuffer + { + /// + /// The command processing time estimator in use. + /// + private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator; + + /// + /// The estimated total processing time. + /// + public ulong EstimatedProcessingTime { get; set; } + + /// + /// The command list that is populated by the . + /// + public CommandList CommandList { get; } + + /// + /// Create a new . + /// + /// The command list that will store the generated commands. + /// The command processing time estimator to use. + public CommandBuffer(CommandList commandList, ICommandProcessingTimeEstimator commandProcessingTimeEstimator) + { + CommandList = commandList; + EstimatedProcessingTime = 0; + _commandProcessingTimeEstimator = commandProcessingTimeEstimator; + } + + /// + /// Add a new generated command to the . + /// + /// The command to add. + private void AddCommand(ICommand command) + { + EstimatedProcessingTime += command.EstimatedProcessingTime; + + CommandList.AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The node id associated to this command. + public void GenerateClearMixBuffer(int nodeId) + { + ClearMixBufferCommand command = new ClearMixBufferCommand(nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The voice state associated. + /// The depop buffer. + /// The buffer count. + /// The target buffer offset. + /// The node id associated to this command. + /// Set to true if the voice was playing previously. + public void GenerateDepopPrepare(Memory state, Memory depopBuffer, uint bufferCount, uint bufferOffset, int nodeId, bool wasPlaying) + { + DepopPrepareCommand command = new DepopPrepareCommand(state, depopBuffer, bufferCount, bufferOffset, nodeId, wasPlaying); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The . + /// The performance operation to perform. + /// The node id associated to this command. + public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId) + { + PerformanceCommand command = new PerformanceCommand(ref performanceEntryAddresses, type, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The previous volume. + /// The new volume. + /// The index of the mix buffer to use. + /// The node id associated to this command. + public void GenerateVolumeRamp(float previousVolume, float volume, uint bufferIndex, int nodeId) + { + VolumeRampCommand command = new VolumeRampCommand(previousVolume, volume, bufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GenerateDataSourceVersion2(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + DataSourceVersion2Command command = new DataSourceVersion2Command(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GeneratePcmInt16DataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + PcmInt16DataSourceCommandVersion1 command = new PcmInt16DataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GeneratePcmFloatDataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + PcmFloatDataSourceCommandVersion1 command = new PcmFloatDataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The node id associated to this command. + public void GenerateAdpcmDataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, int nodeId) + { + AdpcmDataSourceCommandVersion1 command = new AdpcmDataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The base index of the input and output buffer. + /// The biquad filter parameter. + /// The biquad state. + /// The input buffer offset. + /// The output buffer offset. + /// Set to true if the biquad filter state needs to be initialized. + /// The node id associated to this command. + public void GenerateBiquadFilter(int baseIndex, ref BiquadFilterParameter filter, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId) + { + BiquadFilterCommand command = new BiquadFilterCommand(baseIndex, ref filter, biquadFilterStateMemory, inputBufferOffset, outputBufferOffset, needInitialization, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The mix buffer count. + /// The base input index. + /// The base output index. + /// The previous volume. + /// The new volume. + /// The to generate the command from. + /// The node id associated to this command. + public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span previousVolume, Span volume, Memory state, int nodeId) + { + MixRampGroupedCommand command = new MixRampGroupedCommand(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The previous volume. + /// The new volume. + /// The input buffer index. + /// The output buffer index. + /// The index in the array to store the ramped sample. + /// The to generate the command from. + /// The node id associated to this command. + public void GenerateMixRamp(float previousVolume, float volume, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory state, int nodeId) + { + MixRampCommand command = new MixRampCommand(previousVolume, volume, inputBufferIndex, outputBufferIndex, lastSampleIndex, state, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The depop buffer. + /// The target buffer offset. + /// The buffer count. + /// The node id associated to this command. + /// The target sample rate in use. + public void GenerateDepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate) + { + DepopForMixBuffersCommand command = new DepopForMixBuffersCommand(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The input buffer index. + /// The output buffer index. + /// The node id associated to this command. + public void GenerateCopyMixBuffer(uint inputBufferIndex, uint outputBufferIndex, int nodeId) + { + CopyMixBufferCommand command = new CopyMixBufferCommand(inputBufferIndex, outputBufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The input buffer index. + /// The output buffer index. + /// The node id associated to this command. + /// The mix volume. + public void GenerateMix(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume) + { + MixCommand command = new MixCommand(inputBufferIndex, outputBufferIndex, nodeId, volume); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The reverb parameter. + /// The reverb state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the long size pre-delay is supported. + public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported) + { + if (parameter.IsChannelCountValid()) + { + ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The reverb 3d parameter. + /// The reverb 3d state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The delay parameter. + /// The delay state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The input buffer offset. + /// The output buffer offset. + /// The aux state. + /// Set to true if the effect should be active. + /// The limit of the circular buffer. + /// The guest address of the output buffer. + /// The guest address of the input buffer. + /// The count to add on the offset after write/read operations. + /// The write offset. + /// The node id associated to this command. + public void GenerateAuxEffect(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset, ref AuxiliaryBufferAddresses state, bool isEnabled, uint countMax, CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + if (state.SendBufferInfoBase != 0 && state.ReturnBufferInfoBase != 0) + { + AuxiliaryBufferCommand command = new AuxiliaryBufferCommand(bufferOffset, inputBufferOffset, outputBufferOffset, ref state, isEnabled, countMax, outputBuffer, inputBuffer, updateCount, writeOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target volume to apply. + /// The offset of the mix buffer. + /// The node id associated to this command. + public void GenerateVolume(float volume, uint bufferOffset, int nodeId) + { + VolumeCommand command = new VolumeCommand(volume, bufferOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The of the circular buffer. + /// The node id associated to this command. + public void GenerateCircularBuffer(uint bufferOffset, CircularBufferSink sink, int nodeId) + { + CircularBufferSinkCommand command = new CircularBufferSinkCommand(bufferOffset, ref sink.Parameter, ref sink.CircularBufferAddressInfo, sink.CurrentWriteOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The input buffer offset. + /// The output buffer offset. + /// The downmixer parameters to use. + /// The node id associated to this command. + public void GenerateDownMixSurroundToStereo(uint bufferOffset, Span inputBufferOffset, Span outputBufferOffset, ReadOnlySpan downMixParameter, int nodeId) + { + DownMixSurroundToStereoCommand command = new DownMixSurroundToStereoCommand(bufferOffset, inputBufferOffset, outputBufferOffset, downMixParameter, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The associated. + /// The total input count. + /// The input buffer mix offset. + /// The buffer count per sample. + /// The source sample count. + /// The source sample rate. + /// The node id associated to this command. + public void GenerateUpsample(uint bufferOffset, UpsamplerState upsampler, uint inputCount, Span inputBufferOffset, uint bufferCountPerSample, uint sampleCount, uint sampleRate, int nodeId) + { + UpsampleCommand command = new UpsampleCommand(bufferOffset, upsampler, inputCount, inputBufferOffset, bufferCountPerSample, sampleCount, sampleRate, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The of the device sink. + /// The current audio renderer session id. + /// The mix buffer in use. + /// The node id associated to this command. + public void GenerateDeviceSink(uint bufferOffset, DeviceSink sink, int sessionId, Memory buffer, int nodeId) + { + DeviceSinkCommand command = new DeviceSinkCommand(bufferOffset, sink, sessionId, buffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/CommandGenerator.cs b/Ryujinx.Audio.Renderer/Server/CommandGenerator.cs new file mode 100644 index 00000000..41f1c334 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/CommandGenerator.cs @@ -0,0 +1,938 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.Mix; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Server.Voice; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + public class CommandGenerator + { + private CommandBuffer _commandBuffer; + private RendererSystemContext _rendererContext; + private VoiceContext _voiceContext; + private MixContext _mixContext; + private EffectContext _effectContext; + private SinkContext _sinkContext; + private SplitterContext _splitterContext; + private PerformanceManager _performanceManager; + + public CommandGenerator(CommandBuffer commandBuffer, RendererSystemContext rendererContext, VoiceContext voiceContext, MixContext mixContext, EffectContext effectContext, SinkContext sinkContext, SplitterContext splitterContext, PerformanceManager performanceManager) + { + _commandBuffer = commandBuffer; + _rendererContext = rendererContext; + _voiceContext = voiceContext; + _mixContext = mixContext; + _effectContext = effectContext; + _sinkContext = sinkContext; + _splitterContext = splitterContext; + _performanceManager = performanceManager; + + _commandBuffer.GenerateClearMixBuffer(RendererConstants.InvalidNodeId); + } + + private void GenerateDataSource(ref VoiceState voiceState, Memory dspState, int channelIndex) + { + if (voiceState.MixId != RendererConstants.UnusedMixId) + { + ref MixState mix = ref _mixContext.GetState(voiceState.MixId); + + _commandBuffer.GenerateDepopPrepare(dspState, + _rendererContext.DepopBuffer, + mix.BufferCount, + mix.BufferOffset, + voiceState.NodeId, + voiceState.WasPlaying); + } + else if (voiceState.SplitterId != RendererConstants.UnusedSplitterId) + { + int destinationId = 0; + + while (true) + { + Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++); + + if (destinationSpan.IsEmpty) + { + break; + } + + ref SplitterDestination destination = ref destinationSpan[0]; + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != RendererConstants.UnusedSplitterIdInt) + { + ref MixState mix = ref _mixContext.GetState(mixId); + + _commandBuffer.GenerateDepopPrepare(dspState, + _rendererContext.DepopBuffer, + mix.BufferCount, + mix.BufferOffset, + voiceState.NodeId, + voiceState.WasPlaying); + + destination.MarkAsNeedToUpdateInternalState(); + } + } + } + } + + if (!voiceState.WasPlaying) + { + if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported()) + { + _commandBuffer.GenerateDataSourceVersion2(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + } + else + { + switch (voiceState.SampleFormat) + { + case SampleFormat.PcmInt16: + _commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + break; + case SampleFormat.PcmFloat: + _commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + break; + case SampleFormat.Adpcm: + _commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + voiceState.NodeId); + break; + default: + throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}"); + } + } + } + } + + private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory state, int baseIndex, int bufferOffset, int nodeId) + { + for (int i = 0; i < voiceState.BiquadFilters.Length; i++) + { + ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i]; + + if (filter.Enable) + { + Memory biquadStateRawMemory = SpanMemoryManager.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * RendererConstants.VoiceBiquadFilterCount); + + Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory); + + _commandBuffer.GenerateBiquadFilter(baseIndex, + ref filter, + stateMemory.Slice(i, 1), + bufferOffset, + bufferOffset, + !voiceState.BiquadFilterNeedInitialization[i], + nodeId); + } + } + } + + private void GenerateVoiceMix(Span mixVolumes, Span previousMixVolumes, Memory state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId) + { + if (bufferCount > RendererConstants.VoiceChannelCountMax) + { + _commandBuffer.GenerateMixRampGrouped(bufferCount, + bufferIndex, + bufferOffset, + previousMixVolumes, + mixVolumes, + state, + nodeId); + } + else + { + for (int i = 0; i < bufferCount; i++) + { + float previousMixVolume = previousMixVolumes[i]; + float mixVolume = mixVolumes[i]; + + if (mixVolume != 0.0f || previousMixVolume != 0.0f) + { + _commandBuffer.GenerateMixRamp(previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + nodeId); + } + } + } + } + + private void GenerateVoice(ref VoiceState voiceState) + { + int nodeId = voiceState.NodeId; + uint channelsCount = voiceState.ChannelsCount; + + for (int channelIndex = 0; channelIndex < channelsCount; channelIndex++) + { + Memory dspStateMemory = _voiceContext.GetUpdateStateForDsp(voiceState.ChannelResourceIds[channelIndex]); + + ref VoiceChannelResource channelResource = ref _voiceContext.GetChannelResource(voiceState.ChannelResourceIds[channelIndex]); + + PerformanceDetailType dataSourceDetailType = PerformanceDetailType.Adpcm; + + if (voiceState.SampleFormat == SampleFormat.PcmInt16) + { + dataSourceDetailType = PerformanceDetailType.PcmInt16; + } + else if (voiceState.SampleFormat == SampleFormat.PcmFloat) + { + dataSourceDetailType = PerformanceDetailType.PcmFloat; + } + + bool performanceInitialized = false; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, dataSourceDetailType, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateDataSource(ref voiceState, dspStateMemory, channelIndex); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + if (voiceState.WasPlaying) + { + voiceState.PreviousVolume = 0.0f; + } + else if (voiceState.HasAnyDestination()) + { + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.BiquadFilter, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateBiquadFilterForVoice(ref voiceState, dspStateMemory, (int)_rendererContext.MixBufferCount, channelIndex, nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + _commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume, + voiceState.Volume, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + voiceState.PreviousVolume = voiceState.Volume; + + if (voiceState.MixId == RendererConstants.UnusedMixId) + { + if (voiceState.SplitterId != RendererConstants.UnusedSplitterId) + { + int destinationId = channelIndex; + + while (true) + { + Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId); + + if (destinationSpan.IsEmpty) + { + break; + } + + ref SplitterDestination destination = ref destinationSpan[0]; + + destinationId += (int)channelsCount; + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != RendererConstants.UnusedSplitterIdInt) + { + ref MixState mix = ref _mixContext.GetState(mixId); + + GenerateVoiceMix(destination.MixBufferVolume, + destination.PreviousMixBufferVolume, + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + destination.MarkAsNeedToUpdateInternalState(); + } + } + } + } + } + else + { + ref MixState mix = ref _mixContext.GetState(voiceState.MixId); + + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateVoiceMix(channelResource.Mix.ToSpan(), + channelResource.PreviousMix.ToSpan(), + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + channelResource.UpdateState(); + } + + for (int i = 0; i < voiceState.BiquadFilterNeedInitialization.Length; i++) + { + voiceState.BiquadFilterNeedInitialization[i] = voiceState.BiquadFilters[i].Enable; + } + } + } + } + + public void GenerateVoices() + { + for (int i = 0; i < _voiceContext.GetCount(); i++) + { + ref VoiceState sortedState = ref _voiceContext.GetSortedState(i); + + if (!sortedState.ShouldSkip() && sortedState.UpdateForCommandGeneration(_voiceContext)) + { + int nodeId = sortedState.NodeId; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateVoice(ref sortedState); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + + _splitterContext.UpdateInternalState(); + } + + public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId) + { + _commandBuffer.GeneratePerformance(ref performanceEntryAddresses, type, nodeId); + } + + private void GenerateBufferMixerEffect(int bufferOffset, BufferMixEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.BufferMix); + + if (effect.IsEnabled) + { + for (int i = 0; i < effect.Parameter.MixesCount; i++) + { + if (effect.Parameter.Volumes[i] != 0.0f) + { + _commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i], + (uint)bufferOffset + effect.Parameter.Output[i], + nodeId, + effect.Parameter.Volumes[i]); + } + } + } + } + + private void GenerateAuxEffect(uint bufferOffset, AuxiliaryBufferEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.AuxiliaryBuffer); + + if (effect.IsEnabled) + { + effect.GetWorkBuffer(0); + effect.GetWorkBuffer(1); + } + + if (effect.State.SendBufferInfoBase != 0 && effect.State.ReturnBufferInfoBase != 0) + { + int i = 0; + uint writeOffset = 0; + for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--) + { + uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount; + + uint updateCount; + + if ((channelIndex - 1) != 0) + { + updateCount = 0; + } + else + { + updateCount = newUpdateCount; + } + + _commandBuffer.GenerateAuxEffect(bufferOffset, + effect.Parameter.Input[i], + effect.Parameter.Output[i], + ref effect.State, + effect.IsEnabled, + effect.Parameter.BufferStorageSize, + effect.State.SendBufferInfoBase, + effect.State.ReturnBufferInfoBase, + updateCount, + writeOffset, + nodeId); + + writeOffset = newUpdateCount; + + i++; + } + } + } + + private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.Delay); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId); + } + + private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported) + { + Debug.Assert(effect.Type == EffectType.Reverb); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported); + } + + private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.Reverb3d); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId); + } + + private void GenerateBiquadFilterEffect(uint bufferOffset, BiquadFilterEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.BiquadFilter); + + if (effect.IsEnabled) + { + bool needInitialization = effect.Parameter.Status == UsageState.Invalid || + (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + + BiquadFilterParameter parameter = new BiquadFilterParameter(); + + parameter.Enable = true; + effect.Parameter.Denominator.ToSpan().CopyTo(parameter.Denominator.ToSpan()); + effect.Parameter.Numerator.ToSpan().CopyTo(parameter.Numerator.ToSpan()); + + for (int i = 0; i < effect.Parameter.ChannelCount; i++) + { + _commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1), + effect.Parameter.Input[i], + effect.Parameter.Output[i], + needInitialization, + nodeId); + } + } + else + { + for (int i = 0; i < effect.Parameter.ChannelCount; i++) + { + uint inputBufferIndex = bufferOffset + effect.Parameter.Input[i]; + uint outputBufferIndex = bufferOffset + effect.Parameter.Output[i]; + + // If the input and output isn't the same, generate a command. + if (inputBufferIndex != outputBufferIndex) + { + _commandBuffer.GenerateCopyMixBuffer(inputBufferIndex, outputBufferIndex, nodeId); + } + } + } + } + + private void GenerateEffect(ref MixState mix, BaseEffect effect) + { + int nodeId = mix.NodeId; + + bool isFinalMix = mix.MixId == RendererConstants.FinalMixId; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(), + isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + switch (effect.Type) + { + case EffectType.BufferMix: + GenerateBufferMixerEffect((int)mix.BufferOffset, (BufferMixEffect)effect, nodeId); + break; + case EffectType.AuxiliaryBuffer: + GenerateAuxEffect(mix.BufferOffset, (AuxiliaryBufferEffect)effect, nodeId); + break; + case EffectType.Delay: + GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId); + break; + case EffectType.Reverb: + GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported); + break; + case EffectType.Reverb3d: + GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId); + break; + case EffectType.BiquadFilter: + GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId); + break; + default: + throw new NotImplementedException($"Unsupported effect type {effect.Type}"); + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + effect.UpdateForCommandGeneration(); + } + + private void GenerateEffects(ref MixState mix) + { + ReadOnlySpan effectProcessingOrderArray = mix.EffectProcessingOrderArray; + + Debug.Assert(_effectContext.GetCount() == 0 || !effectProcessingOrderArray.IsEmpty); + + for (int i = 0; i < _effectContext.GetCount(); i++) + { + int effectOrder = effectProcessingOrderArray[i]; + + if (effectOrder == RendererConstants.InvalidProcessingOrder) + { + break; + } + + // BaseEffect is a class, we don't need to pass it by ref + BaseEffect effect = _effectContext.GetEffect(effectOrder); + + Debug.Assert(effect.Type != EffectType.Invalid); + Debug.Assert(effect.MixId == mix.MixId); + + if (!effect.ShouldSkip()) + { + GenerateEffect(ref mix, effect); + } + } + } + + private void GenerateMix(ref MixState mix) + { + if (mix.HasAnyDestination()) + { + Debug.Assert(mix.DestinationMixId != RendererConstants.UnusedMixId || mix.DestinationSplitterId != RendererConstants.UnusedSplitterId); + + if (mix.DestinationMixId == RendererConstants.UnusedMixId) + { + if (mix.DestinationSplitterId != RendererConstants.UnusedSplitterId) + { + int destinationId = 0; + + while (true) + { + int destinationIndex = destinationId++; + + Span destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex); + + if (destinationSpan.IsEmpty) + { + break; + } + + ref SplitterDestination destination = ref destinationSpan[0]; + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != RendererConstants.UnusedSplitterIdInt) + { + ref MixState destinationMix = ref _mixContext.GetState(mixId); + + uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount); + + for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++) + { + float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex); + + if (volume != 0.0f) + { + _commandBuffer.GenerateMix(inputBufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + mix.NodeId, + volume); + } + } + } + } + } + } + } + else + { + ref MixState destinationMix = ref _mixContext.GetState(mix.DestinationMixId); + + for (uint bufferIndex = 0; bufferIndex < mix.BufferCount; bufferIndex++) + { + for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++) + { + float volume = mix.Volume * mix.GetMixBufferVolume((int)bufferIndex, (int)bufferDestinationIndex); + + if (volume != 0.0f) + { + _commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + mix.NodeId, + volume); + } + } + } + } + } + } + + private void GenerateSubMix(ref MixState subMix) + { + _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer, + subMix.BufferOffset, + subMix.BufferCount, + subMix.NodeId, + subMix.SampleRate); + + GenerateEffects(ref subMix); + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + int nodeId = subMix.NodeId; + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateMix(ref subMix); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + public void GenerateSubMixes() + { + for (int id = 0; id < _mixContext.GetCount(); id++) + { + ref MixState sortedState = ref _mixContext.GetSortedState(id); + + if (sortedState.IsUsed && sortedState.MixId != RendererConstants.FinalMixId) + { + int nodeId = sortedState.NodeId; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateSubMix(ref sortedState); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + } + + private void GenerateFinalMix() + { + ref MixState finalMix = ref _mixContext.GetFinalState(); + + _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer, + finalMix.BufferOffset, + finalMix.BufferCount, + finalMix.NodeId, + finalMix.SampleRate); + + GenerateEffects(ref finalMix); + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + int nodeId = finalMix.NodeId; + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.FinalMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + // Only generate volume command if the volume isn't 100%. + if (finalMix.Volume != 1.0f) + { + for (uint bufferIndex = 0; bufferIndex < finalMix.BufferCount; bufferIndex++) + { + bool performanceSubInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.FinalMix, nodeId)) + { + performanceSubInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + _commandBuffer.GenerateVolume(finalMix.Volume, + finalMix.BufferOffset + bufferIndex, + nodeId); + + if (performanceSubInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + public void GenerateFinalMixes() + { + int nodeId = _mixContext.GetFinalState().NodeId; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.FinalMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateFinalMix(); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + private void GenerateCircularBuffer(CircularBufferSink sink, ref MixState finalMix) + { + _commandBuffer.GenerateCircularBuffer(finalMix.BufferOffset, sink, RendererConstants.InvalidNodeId); + } + + private void GenerateDevice(DeviceSink sink, ref MixState finalMix) + { + if (_commandBuffer.CommandList.SampleRate != 48000 && sink.UpsamplerState == null) + { + sink.UpsamplerState = _rendererContext.UpsamplerManager.Allocate(); + } + + bool useCustomDownMixingCommand = _rendererContext.ChannelCount == 2 && sink.Parameter.DownMixParameterEnabled; + + if (useCustomDownMixingCommand) + { + _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset, + sink.Parameter.Input.ToSpan(), + sink.Parameter.Input.ToSpan(), + sink.DownMixCoefficients, + RendererConstants.InvalidNodeId); + } + // NOTE: we do the downmixing at the DSP level as right now the renderer interface doesn't use audout. + // TODO: Remove this when audout is rewritten. + else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6) + { + _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset, + sink.Parameter.Input.ToSpan(), + sink.Parameter.Input.ToSpan(), + RendererConstants.DefaultSurroundToStereoCoefficients, + RendererConstants.InvalidNodeId); + } + + CommandList commandList = _commandBuffer.CommandList; + + if (sink.UpsamplerState != null) + { + _commandBuffer.GenerateUpsample(finalMix.BufferOffset, + sink.UpsamplerState, + sink.Parameter.InputCount, + sink.Parameter.Input.ToSpan(), + commandList.BufferCount, + commandList.SampleCount, + commandList.SampleRate, + RendererConstants.InvalidNodeId); + } + + _commandBuffer.GenerateDeviceSink(finalMix.BufferOffset, + sink, + _rendererContext.SessionId, + commandList.Buffers, + RendererConstants.InvalidNodeId); + } + + private void GenerateSink(BaseSink sink, ref MixState finalMix) + { + bool performanceInitialized = false; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Sink, sink.NodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, sink.NodeId); + } + + if (!sink.ShouldSkip) + { + switch (sink.Type) + { + case SinkType.CircularBuffer: + GenerateCircularBuffer((CircularBufferSink)sink, ref finalMix); + break; + case SinkType.Device: + GenerateDevice((DeviceSink)sink, ref finalMix); + break; + default: + throw new NotImplementedException($"Unsupported sink type {sink.Type}"); + } + + sink.UpdateForCommandGeneration(); + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, sink.NodeId); + } + } + + public void GenerateSinks() + { + ref MixState finalMix = ref _mixContext.GetFinalState(); + + for (int i = 0; i < _sinkContext.GetCount(); i++) + { + // BaseSink is a class, we don't need to pass it by ref + BaseSink sink = _sinkContext.GetSink(i); + + if (sink.IsUsed) + { + GenerateSink(sink, ref finalMix); + } + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs new file mode 100644 index 00000000..7afa88a4 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs @@ -0,0 +1,180 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.Command; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 1. + /// + public class CommandProcessingTimeEstimatorVersion1 : ICommandProcessingTimeEstimator + { + private uint _sampleCount; + private uint _bufferCount; + + public CommandProcessingTimeEstimatorVersion1(uint sampleCount, uint bufferCount) + { + _sampleCount = sampleCount; + _bufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + return 1454; + } + + public uint Estimate(ClearMixBufferCommand command) + { + return (uint)(_sampleCount * 0.83f * _bufferCount * 1.2f); + } + + public uint Estimate(BiquadFilterCommand command) + { + return (uint)(_sampleCount * 58.0f * 1.2f); + } + + public uint Estimate(MixRampGroupedCommand command) + { + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(_sampleCount * 14.4f * 1.2f * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + return (uint)(_sampleCount * 14.4f * 1.2f); + } + + public uint Estimate(DepopPrepareCommand command) + { + return 1080; + } + + public uint Estimate(VolumeRampCommand command) + { + return (uint)(_sampleCount * 9.8f * 1.2f); + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + return (uint)(command.Pitch * 0.25f * 1.2f); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + return (uint)(command.Pitch * 0.46f * 1.2f); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + return (uint)(_sampleCount * 8.9f * command.MixBufferCount); + } + + public uint Estimate(CopyMixBufferCommand command) + { + // NOTE: Nintendo returns 0 here for some reasons even if it will generate a command like that on version 1.. maybe a mistake? + return 0; + } + + public uint Estimate(MixCommand command) + { + return (uint)(_sampleCount * 10.0f * 1.2f); + } + + public uint Estimate(DelayCommand command) + { + return (uint)(_sampleCount * command.Parameter.ChannelCount * 202.5f); + } + + public uint Estimate(ReverbCommand command) + { + Debug.Assert(command.Parameter.IsChannelCountValid()); + + if (command.Enabled) + { + return (uint)(750 * _sampleCount * command.Parameter.ChannelCount * 1.2f); + } + + return 0; + } + + public uint Estimate(Reverb3dCommand command) + { + if (command.Enabled) + { + return (uint)(530 * _sampleCount * command.Parameter.ChannelCount * 1.2f); + } + + return 0; + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + if (command.Enabled) + { + return 15956; + } + + return 3765; + } + + public uint Estimate(VolumeCommand command) + { + return (uint)(_sampleCount * 8.8f * 1.2f); + } + + public uint Estimate(CircularBufferSinkCommand command) + { + return 55; + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + return 16108; + } + + public uint Estimate(UpsampleCommand command) + { + return 357915; + } + + public uint Estimate(DeviceSinkCommand command) + { + return 10042; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + return 0; + } + + public uint Estimate(DataSourceVersion2Command command) + { + return 0; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs new file mode 100644 index 00000000..7f33b1bf --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs @@ -0,0 +1,544 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.Command; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 2. (added with REV5) + /// + public class CommandProcessingTimeEstimatorVersion2 : ICommandProcessingTimeEstimator + { + private uint _sampleCount; + private uint _bufferCount; + + public CommandProcessingTimeEstimatorVersion2(uint sampleCount, uint bufferCount) + { + _sampleCount = sampleCount; + _bufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)489.35f; + } + + return (uint)491.18f; + } + + public uint Estimate(ClearMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 668.8f; + float baseCost = 193.2f; + + if (_sampleCount == 160) + { + costPerBuffer = 260.4f; + baseCost = 139.65f; + } + + return (uint)(baseCost + costPerBuffer * _bufferCount); + } + + public uint Estimate(BiquadFilterCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)4813.2f; + } + + return (uint)6915.4f; + } + + public uint Estimate(MixRampGroupedCommand command) + { + const float costPerSample = 7.245f; + + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(_sampleCount * costPerSample * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1859.0f; + } + + return (uint)2286.1f; + } + + public uint Estimate(DepopPrepareCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)306.62f; + } + + return (uint)293.22f; + } + + public uint Estimate(VolumeRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1403.9f; + } + + return (uint)1884.3f; + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 1195.5f; + float baseCost = 7797.0f; + + if (_sampleCount == 160) + { + costPerSample = 749.27f; + baseCost = 6138.9f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3564.1f; + float baseCost = 6225.5f; + + if (_sampleCount == 160) + { + costPerSample = 2125.6f; + baseCost = 9039.5f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)762.96f; + } + + return (uint)726.96f; + } + + public uint Estimate(CopyMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)836.32f; + } + + return (uint)1000.9f; + } + + public uint Estimate(MixCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1342.2f; + } + + return (uint)1833.2f; + } + + public uint Estimate(DelayCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)41636.0f; + case 2: + return (uint)97861.0f; + case 4: + return (uint)192520.0f; + case 6: + return (uint)301760.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)578.53f; + case 2: + return (uint)663.06f; + case 4: + return (uint)703.98f; + case 6: + return (uint)760.03f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)8770.3f; + case 2: + return (uint)25741.0f; + case 4: + return (uint)47551.0f; + case 6: + return (uint)81629.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)521.28f; + case 2: + return (uint)585.4f; + case 4: + return (uint)629.88f; + case 6: + return (uint)713.57f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public uint Estimate(ReverbCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)97192.0f; + case 2: + return (uint)103280.0f; + case 4: + return (uint)109580.0f; + case 6: + return (uint)115070.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)492.01f; + case 2: + return (uint)554.46f; + case 4: + return (uint)595.86f; + case 6: + return (uint)656.62f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)136460.0f; + case 2: + return (uint)145750.0f; + case 4: + return (uint)154800.0f; + case 6: + return (uint)161970.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)495.79f; + case 2: + return (uint)527.16f; + case 4: + return (uint)598.75f; + case 6: + return (uint)666.03f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public uint Estimate(Reverb3dCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)138840.0f; + case 2: + return (uint)135430.0f; + case 4: + return (uint)199180.0f; + case 6: + return (uint)247350.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)718.7f; + case 2: + return (uint)751.3f; + case 4: + return (uint)797.46f; + case 6: + return (uint)867.43f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)199950.0f; + case 2: + return (uint)195200.0f; + case 4: + return (uint)290580.0f; + case 6: + return (uint)363490.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)534.24f; + case 2: + return (uint)570.87f; + case 4: + return (uint)660.93f; + case 6: + return (uint)694.6f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return (uint)7177.9f; + } + + return (uint)489.16f; + } + + if (command.Enabled) + { + return (uint)9499.8f; + } + + return (uint)485.56f; + } + + public uint Estimate(VolumeCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1280.3f; + } + + return (uint)1737.8f; + } + + public uint Estimate(CircularBufferSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 1726.0f; + float baseCost = 1369.7f; + + if (_sampleCount == 160) + { + costPerBuffer = 853.63f; + baseCost = 1284.5f; + } + + return (uint)(baseCost + costPerBuffer * command.InputCount); + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)10009.0f; + } + + return (uint)14577.0f; + } + + public uint Estimate(UpsampleCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)292000.0f; + } + + return (uint)0.0f; + } + + public uint Estimate(DeviceSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + Debug.Assert(command.InputCount == 2 || command.InputCount == 6); + + if (command.InputCount == 2) + { + if (_sampleCount == 160) + { + return (uint)9261.5f; + } + + return (uint)9336.1f; + } + + if (_sampleCount == 160) + { + return (uint)9111.8f; + } + + return (uint)9566.7f; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + // NOTE: This was added between REV7 and REV8 and for some reasons the estimator v2 was changed... + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3490.9f; + float baseCost = 10091.0f; + + if (_sampleCount == 160) + { + costPerSample = 2310.4f; + baseCost = 7845.3f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DataSourceVersion2Command command) + { + return 0; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs new file mode 100644 index 00000000..19a8174d --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs @@ -0,0 +1,635 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 3. (added with REV8) + /// + public class CommandProcessingTimeEstimatorVersion3 : ICommandProcessingTimeEstimator + { + private uint _sampleCount; + private uint _bufferCount; + + public CommandProcessingTimeEstimatorVersion3(uint sampleCount, uint bufferCount) + { + _sampleCount = sampleCount; + _bufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)498.17f; + } + + return (uint)489.42f; + } + + public uint Estimate(ClearMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 440.68f; + float baseCost = 0; + + if (_sampleCount == 160) + { + costPerBuffer = 266.65f; + } + + return (uint)(baseCost + costPerBuffer * _bufferCount); + } + + public uint Estimate(BiquadFilterCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)4173.2f; + } + + return (uint)5585.1f; + } + + public uint Estimate(MixRampGroupedCommand command) + { + float costPerSample = 6.4434f; + + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + costPerSample = 6.708f; + } + + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(_sampleCount * costPerSample * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1968.7f; + } + + return (uint)2459.4f; + } + + public uint Estimate(DepopPrepareCommand command) + { + return 0; + } + + public uint Estimate(VolumeRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1425.3f; + } + + return (uint)1700.0f; + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 710.143f; + float baseCost = 7853.286f; + + if (_sampleCount == 160) + { + costPerSample = 427.52f; + baseCost = 6329.442f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3564.1f; + float baseCost = 9736.702f; + + if (_sampleCount == 160) + { + costPerSample = 2125.6f; + baseCost = 7913.808f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)739.64f; + } + + return (uint)910.97f; + } + + public uint Estimate(CopyMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)842.59f; + } + + return (uint)986.72f; + } + + public uint Estimate(MixCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1402.8f; + } + + return (uint)1853.2f; + } + + public uint Estimate(DelayCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)8929.04f; + case 2: + return (uint)25500.75f; + case 4: + return (uint)47759.62f; + case 6: + return (uint)82203.07f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)1295.20f; + case 2: + return (uint)1213.60f; + case 4: + return (uint)942.03f; + case 6: + return (uint)1001.55f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)11941.05f; + case 2: + return (uint)37197.37f; + case 4: + return (uint)69749.84f; + case 6: + return (uint)120042.40f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)997.67f; + case 2: + return (uint)977.63f; + case 4: + return (uint)792.30f; + case 6: + return (uint)875.43f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public uint Estimate(ReverbCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)81475.05f; + case 2: + return (uint)84975.0f; + case 4: + return (uint)91625.15f; + case 6: + return (uint)95332.27f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)536.30f; + case 2: + return (uint)588.70f; + case 4: + return (uint)643.70f; + case 6: + return (uint)706.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)120174.47f; + case 2: + return (uint)25262.22f; + case 4: + return (uint)135751.23f; + case 6: + return (uint)141129.23f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)617.64f; + case 2: + return (uint)659.54f; + case 4: + return (uint)711.43f; + case 6: + return (uint)778.07f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public uint Estimate(Reverb3dCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)116754.0f; + case 2: + return (uint)125912.05f; + case 4: + return (uint)146336.03f; + case 6: + return (uint)165812.66f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)734.0f; + case 2: + return (uint)766.62f; + case 4: + return (uint)797.46f; + case 6: + return (uint)867.43f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)170292.34f; + case 2: + return (uint)183875.63f; + case 4: + return (uint)214696.19f; + case 6: + return (uint)243846.77f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)508.47f; + case 2: + return (uint)582.45f; + case 4: + return (uint)626.42f; + case 6: + return (uint)682.47f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return (uint)7182.14f; + } + + return (uint)472.11f; + } + + if (command.Enabled) + { + return (uint)9435.96f; + } + + return (uint)462.62f; + } + + public uint Estimate(VolumeCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1311.1f; + } + + return (uint)1713.6f; + } + + public uint Estimate(CircularBufferSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 770.26f; + float baseCost = 0f; + + if (_sampleCount == 160) + { + costPerBuffer = 531.07f; + } + + return (uint)(baseCost + costPerBuffer * command.InputCount); + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)9949.7f; + } + + return (uint)14679.0f; + } + + public uint Estimate(UpsampleCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)312990.0f; + } + + return (uint)0.0f; + } + + public uint Estimate(DeviceSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + Debug.Assert(command.InputCount == 2 || command.InputCount == 6); + + if (command.InputCount == 2) + { + if (_sampleCount == 160) + { + return (uint)8980.0f; + } + + return (uint)9221.9f; + } + + if (_sampleCount == 160) + { + return (uint)9177.9f; + } + + return (uint)9725.9f; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3490.9f; + float baseCost = 10090.9f; + + if (_sampleCount == 160) + { + costPerSample = 2310.4f; + baseCost = 7845.25f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DataSourceVersion2Command command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + (float baseCost, float costPerSample) = GetCostByFormat(_sampleCount, command.SampleFormat, command.SrcQuality); + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f) - 1.0f))); + } + + private static (float, float) GetCostByFormat(uint sampleCount, SampleFormat format, SampleRateConversionQuality quality) + { + Debug.Assert(sampleCount == 160 || sampleCount == 240); + + switch (format) + { + case SampleFormat.PcmInt16: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (6329.44f, 427.52f); + } + + return (7853.28f, 710.14f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (8049.42f, 371.88f); + } + + return (10138.84f, 610.49f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (5062.66f, 423.43f); + } + + return (5810.96f, 676.72f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + case SampleFormat.PcmFloat: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (7845.25f, 2310.4f); + } + + return (10090.9f, 3490.9f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (9446.36f, 2308.91f); + } + + return (12520.85f, 3480.61f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (9446.36f, 2308.91f); + } + + return (12520.85f, 3480.61f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + case SampleFormat.Adpcm: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (7913.81f, 1827.66f); + } + + return (9736.70f, 2756.37f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (9607.81f, 1829.29f); + } + + return (12154.38f, 2731.31f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (6517.48f, 1824.61f); + } + + return (7929.44f, 2732.15f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + default: + throw new NotImplementedException($"{format}"); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/Ryujinx.Audio.Renderer/Server/Effect/AuxiliaryBufferEffect.cs new file mode 100644 index 00000000..02f47a4a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Effect/AuxiliaryBufferEffect.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for an auxiliary buffer effect. + /// + public class AuxiliaryBufferEffect : BaseEffect + { + /// + /// The auxiliary buffer parameter. + /// + public AuxiliaryBufferParameter Parameter; + + /// + /// Auxiliary buffer state. + /// + public AuxiliaryBufferAddresses State; + + public override EffectType TargetEffectType => EffectType.AuxiliaryBuffer; + + public override DspAddress GetWorkBuffer(int index) + { + return WorkBuffers[index].GetReference(true); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (BufferUnmapped || parameter.IsNew) + { + ulong bufferSize = (ulong)Unsafe.SizeOf() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf() * 2; + + bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize); + bool returnBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[1], Parameter.ReturnBufferInfoAddress, bufferSize); + + BufferUnmapped = sendBufferUnmapped && returnBufferUnmapped; + + if (!BufferUnmapped) + { + DspAddress sendDspAddress = WorkBuffers[0].GetReference(false); + DspAddress returnDspAddress = WorkBuffers[1].GetReference(false); + + State.SendBufferInfo = sendDspAddress + (uint)Unsafe.SizeOf(); + State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf() * 2; + + State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf(); + State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf() * 2; + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Effect/BaseEffect.cs b/Ryujinx.Audio.Renderer/Server/Effect/BaseEffect.cs new file mode 100644 index 00000000..55705f0a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Effect/BaseEffect.cs @@ -0,0 +1,257 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Base class used as a server state for an effect. + /// + public class BaseEffect + { + /// + /// The of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect must be active. + /// + public bool IsEnabled; + + /// + /// Set to true if the internal effect work buffers used wasn't mapped. + /// + public bool BufferUnmapped; + + /// + /// The current state of the effect. + /// + public UsageState UsageState; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Array of all the work buffer used by the effect. + /// + protected AddressInfo[] WorkBuffers; + + /// + /// Create a new . + /// + public BaseEffect() + { + Type = TargetEffectType; + UsageState = UsageState.Invalid; + + IsEnabled = false; + BufferUnmapped = false; + MixId = RendererConstants.UnusedMixId; + ProcessingOrder = uint.MaxValue; + + WorkBuffers = new AddressInfo[2]; + + foreach (ref AddressInfo info in WorkBuffers.AsSpan()) + { + info = AddressInfo.Create(); + } + } + + /// + /// The target handled by this . + /// + public virtual EffectType TargetEffectType => EffectType.Invalid; + + /// + /// Check if the sent by the user match the internal . + /// + /// The user parameter. + /// Returns true if the sent by the user matches the internal . + public bool IsTypeValid(ref EffectInParameter parameter) + { + return parameter.Type == TargetEffectType; + } + + /// + /// Update the usage state during command generation. + /// + protected void UpdateUsageStateForCommandGeneration() + { + UsageState = IsEnabled ? UsageState.Enabled : UsageState.Disabled; + } + + /// + /// Update the internal common parameters from a user parameter. + /// + /// The user parameter. + protected void UpdateParameterBase(ref EffectInParameter parameter) + { + MixId = parameter.MixId; + ProcessingOrder = parameter.ProcessingOrder; + } + + /// + /// Force unmap all the work buffers. + /// + /// The mapper to use. + public void ForceUnmapBuffers(PoolMapper mapper) + { + foreach (ref AddressInfo info in WorkBuffers.AsSpan()) + { + if (info.GetReference(false) != 0) + { + mapper.ForceUnmap(ref info); + } + } + } + + /// + /// Check if the effect needs to be skipped. + /// + /// Returns true if the effect needs to be skipped. + public bool ShouldSkip() + { + return BufferUnmapped; + } + + /// + /// Update the state during command generation. + /// + public virtual void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetEffectType); + } + + /// + /// Update the internal state from a user parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + updateErrorInfo = new ErrorInfo(); + } + + /// + /// Get the work buffer DSP address at the given index. + /// + /// The index of the work buffer + /// The work buffer DSP address at the given index. + public virtual DspAddress GetWorkBuffer(int index) + { + throw new InvalidOperationException(); + } + + /// + /// Get the first work buffer DSP address. + /// + /// The first work buffer DSP address. + protected DspAddress GetSingleBuffer() + { + if (IsEnabled) + { + return WorkBuffers[0].GetReference(true); + } + + if (UsageState != UsageState.Disabled) + { + DspAddress address = WorkBuffers[0].GetReference(false); + ulong size = WorkBuffers[0].Size; + + if (address != 0 && size != 0) + { + AudioProcessorMemoryManager.InvalidateDataCache(address, size); + } + } + + return 0; + } + + /// + /// Store the output status to the given user output. + /// + /// The given user output. + /// If set to true, the is active. + public void StoreStatus(ref EffectOutStatus outStatus, bool isAudioRendererActive) + { + if (isAudioRendererActive) + { + if (UsageState == UsageState.Disabled) + { + outStatus.State = EffectOutStatus.EffectState.Disabled; + } + else + { + outStatus.State = EffectOutStatus.EffectState.Enabled; + } + } + else if (UsageState == UsageState.New) + { + outStatus.State = EffectOutStatus.EffectState.Enabled; + } + else + { + outStatus.State = EffectOutStatus.EffectState.Disabled; + } + } + + /// + /// Get the associated to the of this effect. + /// + /// The associated to the of this effect. + public PerformanceDetailType GetPerformanceDetailType() + { + switch (Type) + { + case EffectType.BiquadFilter: + return PerformanceDetailType.BiquadFilter; + case EffectType.AuxiliaryBuffer: + return PerformanceDetailType.Aux; + case EffectType.Delay: + return PerformanceDetailType.Delay; + case EffectType.Reverb: + return PerformanceDetailType.Reverb; + case EffectType.Reverb3d: + return PerformanceDetailType.Reverb3d; + case EffectType.BufferMix: + return PerformanceDetailType.Mix; + default: + throw new NotImplementedException($"{Type}"); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Effect/BiquadFilterEffect.cs b/Ryujinx.Audio.Renderer/Server/Effect/BiquadFilterEffect.cs new file mode 100644 index 00000000..1e47d675 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Effect/BiquadFilterEffect.cs @@ -0,0 +1,74 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a biquad filter effect. + /// + public class BiquadFilterEffect : BaseEffect + { + /// + /// The biquad filter parameter. + /// + public BiquadFilterEffectParameter Parameter; + + /// + /// The biquad filter state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public BiquadFilterEffect() + { + Parameter = new BiquadFilterEffectParameter(); + State = new BiquadFilterState[RendererConstants.ChannelCountMax]; + } + + public override EffectType TargetEffectType => EffectType.BiquadFilter; + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Effect/BufferMixEffect.cs b/Ryujinx.Audio.Renderer/Server/Effect/BufferMixEffect.cs new file mode 100644 index 00000000..7f5b1629 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Effect/BufferMixEffect.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a buffer mix effect. + /// + public class BufferMixEffect : BaseEffect + { + /// + /// The buffer mix parameter. + /// + public BufferMixParameter Parameter; + + public override EffectType TargetEffectType => EffectType.BufferMix; + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Effect/DelayEffect.cs b/Ryujinx.Audio.Renderer/Server/Effect/DelayEffect.cs new file mode 100644 index 00000000..9134e003 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Effect/DelayEffect.cs @@ -0,0 +1,100 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a delay effect. + /// + public class DelayEffect : BaseEffect + { + /// + /// The delay parameter. + /// + public DelayParameter Parameter; + + /// + /// The delay state. + /// + public Memory State { get; } + + public DelayEffect() + { + State = new DelayState[1]; + } + + public override EffectType TargetEffectType => EffectType.Delay; + + public override DspAddress GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref DelayParameter delayParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (delayParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(ref parameter); + + UsageState oldParameterStatus = Parameter.Status; + + Parameter = delayParameter; + + if (delayParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.Status = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Effect/EffectContext.cs b/Ryujinx.Audio.Renderer/Server/Effect/EffectContext.cs new file mode 100644 index 00000000..d637592e --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Effect/EffectContext.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Effect context. + /// + public class EffectContext + { + /// + /// Storage for . + /// + private BaseEffect[] _effects; + + /// + /// The total effect count. + /// + private uint _effectCount; + + /// + /// Create a new . + /// + public EffectContext() + { + _effects = null; + _effectCount = 0; + } + + /// + /// Initialize the . + /// + /// The total effect count. + public void Initialize(uint effectCount) + { + _effectCount = effectCount; + _effects = new BaseEffect[effectCount]; + + for (int i = 0; i < _effectCount; i++) + { + _effects[i] = new BaseEffect(); + } + } + + /// + /// Get the total effect count. + /// + /// The total effect count. + public uint GetCount() + { + return _effectCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref BaseEffect GetEffect(int index) + { + Debug.Assert(index >= 0 && index < _effectCount); + + return ref _effects[index]; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Effect/Reverb3dEffect.cs b/Ryujinx.Audio.Renderer/Server/Effect/Reverb3dEffect.cs new file mode 100644 index 00000000..b9db2294 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Effect/Reverb3dEffect.cs @@ -0,0 +1,99 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a 3D reverberation effect. + /// + public class Reverb3dEffect : BaseEffect + { + /// + /// The 3D reverberation parameter. + /// + public Reverb3dParameter Parameter; + + /// + /// The 3D reverberation state. + /// + public Memory State { get; } + + public Reverb3dEffect() + { + State = new Reverb3dState[1]; + } + + public override EffectType TargetEffectType => EffectType.Reverb3d; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (reverbParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(ref parameter); + + UsageState oldParameterStatus = Parameter.ParameterStatus; + + Parameter = reverbParameter; + + if (reverbParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.ParameterStatus = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.ParameterStatus = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.ParameterStatus = UsageState.Enabled; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Effect/ReverbEffect.cs b/Ryujinx.Audio.Renderer/Server/Effect/ReverbEffect.cs new file mode 100644 index 00000000..adc89798 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Effect/ReverbEffect.cs @@ -0,0 +1,102 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a reverberation effect. + /// + public class ReverbEffect : BaseEffect + { + /// + /// The reverberation parameter. + /// + public ReverbParameter Parameter; + + /// + /// The reverberation state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public ReverbEffect() + { + State = new ReverbState[1]; + } + + public override EffectType TargetEffectType => EffectType.Reverb; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameter parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (reverbParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(ref parameter); + + UsageState oldParameterStatus = Parameter.Status; + + Parameter = reverbParameter; + + if (reverbParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.Status = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Effect/UsageState.cs b/Ryujinx.Audio.Renderer/Server/Effect/UsageState.cs new file mode 100644 index 00000000..afb7c1fc --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Effect/UsageState.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// The usage state of an effect. + /// + public enum UsageState : byte + { + /// + /// The effect is in an invalid state. + /// + Invalid, + + /// + /// The effect is new. + /// + New, + + /// + /// The effect is enabled. + /// + Enabled, + + /// + /// The effect is disabled. + /// + Disabled + } +} diff --git a/Ryujinx.Audio.Renderer/Server/ICommandProcessingTimeEstimator.cs b/Ryujinx.Audio.Renderer/Server/ICommandProcessingTimeEstimator.cs new file mode 100644 index 00000000..f533f57b --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/ICommandProcessingTimeEstimator.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Dsp.Command; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Estimate the time that a should take. + /// + /// This is used for voice dropping. + public interface ICommandProcessingTimeEstimator + { + uint Estimate(AuxiliaryBufferCommand command); + uint Estimate(BiquadFilterCommand command); + uint Estimate(ClearMixBufferCommand command); + uint Estimate(DelayCommand command); + uint Estimate(Reverb3dCommand command); + uint Estimate(ReverbCommand command); + uint Estimate(DepopPrepareCommand command); + uint Estimate(DepopForMixBuffersCommand command); + uint Estimate(MixCommand command); + uint Estimate(MixRampCommand command); + uint Estimate(MixRampGroupedCommand command); + uint Estimate(CopyMixBufferCommand command); + uint Estimate(PerformanceCommand command); + uint Estimate(VolumeCommand command); + uint Estimate(VolumeRampCommand command); + uint Estimate(PcmInt16DataSourceCommandVersion1 command); + uint Estimate(PcmFloatDataSourceCommandVersion1 command); + uint Estimate(AdpcmDataSourceCommandVersion1 command); + uint Estimate(DataSourceVersion2Command command); + uint Estimate(CircularBufferSinkCommand command); + uint Estimate(DeviceSinkCommand command); + uint Estimate(DownMixSurroundToStereoCommand command); + uint Estimate(UpsampleCommand command); + } +} diff --git a/Ryujinx.Audio.Renderer/Server/MemoryPool/AddressInfo.cs b/Ryujinx.Audio.Renderer/Server/MemoryPool/AddressInfo.cs new file mode 100644 index 00000000..d6c16174 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/MemoryPool/AddressInfo.cs @@ -0,0 +1,151 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Runtime.InteropServices; + +using DspAddress = System.UInt64; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Represents the information of a region shared between the CPU and DSP. + /// + public struct AddressInfo + { + /// + /// The target CPU address of the region. + /// + public CpuAddress CpuAddress; + + /// + /// The size of the region. + /// + public ulong Size; + + private unsafe MemoryPoolState* _memoryPools; + + /// + /// The forced DSP address of the region. + /// + public DspAddress ForceMappedDspAddress; + + private unsafe ref MemoryPoolState MemoryPoolState => ref *_memoryPools; + + public unsafe bool HasMemoryPoolState => (IntPtr)_memoryPools != IntPtr.Zero; + + /// + /// Create an new empty . + /// + /// A new empty . + public static AddressInfo Create() + { + return Create(0, 0); + } + + /// + /// Create a new . + /// + /// The target of the region. + /// The target size of the region. + /// A new . + public static AddressInfo Create(CpuAddress cpuAddress, ulong size) + { + unsafe + { + return new AddressInfo + { + CpuAddress = cpuAddress, + _memoryPools = MemoryPoolState.Null, + Size = size, + ForceMappedDspAddress = 0 + }; + } + } + + /// + /// Setup the CPU address and size of the . + /// + /// The target of the region. + /// The size of the region. + public void Setup(CpuAddress cpuAddress, ulong size) + { + CpuAddress = cpuAddress; + Size = size; + ForceMappedDspAddress = 0; + + unsafe + { + _memoryPools = MemoryPoolState.Null; + } + } + + /// + /// Set the associated. + /// + /// The associated. + public void SetupMemoryPool(Span memoryPoolState) + { + unsafe + { + fixed (MemoryPoolState* ptr = &MemoryMarshal.GetReference(memoryPoolState)) + { + SetupMemoryPool(ptr); + } + } + } + + /// + /// Set the associated. + /// + /// The associated. + public unsafe void SetupMemoryPool(MemoryPoolState* memoryPoolState) + { + _memoryPools = memoryPoolState; + } + + /// + /// Check if the is mapped. + /// + /// Returns true if the is mapped. + public bool HasMappedMemoryPool() + { + return HasMemoryPoolState && MemoryPoolState.IsMapped(); + } + + /// + /// Get the DSP address associated to the . + /// + /// If true, mark the as used. + /// Returns the DSP address associated to the . + public DspAddress GetReference(bool markUsed) + { + if (!HasMappedMemoryPool()) + { + return ForceMappedDspAddress; + } + + if (markUsed) + { + MemoryPoolState.IsUsed = true; + } + + return MemoryPoolState.Translate(CpuAddress, Size); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/MemoryPool/MemoryPoolState.cs b/Ryujinx.Audio.Renderer/Server/MemoryPool/MemoryPoolState.cs new file mode 100644 index 00000000..2f5dbf08 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/MemoryPool/MemoryPoolState.cs @@ -0,0 +1,148 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Runtime.InteropServices; + +using DspAddress = System.UInt64; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Server state for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)] + public struct MemoryPoolState + { + public const int Alignment = 0x10; + + /// + /// The location of the . + /// + public enum LocationType : uint + { + /// + /// located on the CPU side for user use. + /// + Cpu, + + /// + /// located on the DSP side for system use. + /// + Dsp + } + + /// + /// The CPU address associated to the . + /// + public CpuAddress CpuAddress; + + /// + /// The DSP address associated to the . + /// + public DspAddress DspAddress; + + /// + /// The size associated to the . + /// + public ulong Size; + + /// + /// The associated to the . + /// + public LocationType Location; + + /// + /// Set to true if the is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + public static unsafe MemoryPoolState* Null => (MemoryPoolState*)IntPtr.Zero.ToPointer(); + + /// + /// Create a new with the given . + /// + /// The location type to use. + /// A new with the given . + public static MemoryPoolState Create(LocationType location) + { + return new MemoryPoolState + { + CpuAddress = 0, + DspAddress = 0, + Size = 0, + Location = location + }; + } + + /// + /// Set the and size of the . + /// + /// The . + /// The size. + public void SetCpuAddress(CpuAddress cpuAddress, ulong size) + { + CpuAddress = cpuAddress; + Size = size; + } + + /// + /// Check if the given and size is contains in the . + /// + /// The . + /// The size. + /// True if the is contained inside the . + public bool Contains(CpuAddress targetCpuAddress, ulong size) + { + if (CpuAddress <= targetCpuAddress && size + targetCpuAddress <= Size + CpuAddress) + { + return true; + } + + return false; + } + + /// + /// Translate the given CPU address to a DSP address. + /// + /// The . + /// The size. + /// the target DSP address. + public DspAddress Translate(CpuAddress targetCpuAddress, ulong size) + { + if (Contains(targetCpuAddress, size) && IsMapped()) + { + ulong offset = targetCpuAddress - CpuAddress; + + return DspAddress + offset; + } + + return 0; + } + + /// + /// Is the mapped on the DSP? + /// + /// Returns true if the is mapped on the DSP. + public bool IsMapped() + { + return DspAddress != 0; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/MemoryPool/PoolMapper.cs b/Ryujinx.Audio.Renderer/Server/MemoryPool/PoolMapper.cs new file mode 100644 index 00000000..1aafe04e --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/MemoryPool/PoolMapper.cs @@ -0,0 +1,383 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common.Logging; +using System; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Memory pool mapping helper. + /// + public class PoolMapper + { + const uint CurrentProcessPseudoHandle = 0xFFFF8001; + + /// + /// The result of . + /// + public enum UpdateResult : uint + { + /// + /// No error reported. + /// + Success = 0, + + /// + /// The user parameters were invalid. + /// + InvalidParameter = 1, + + /// + /// mapping failed. + /// + MapError = 2, + + /// + /// unmapping failed. + /// + UnmapError = 3 + } + + /// + /// The handle of the process owning the CPU memory manipulated. + /// + private uint _processHandle; + + /// + /// The that will be manipulated. + /// + private Memory _memoryPools; + + /// + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + /// + private bool _isForceMapEnabled; + + /// + /// Create a new used for system mapping. + /// + /// The handle of the process owning the CPU memory manipulated. + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + public PoolMapper(uint processHandle, bool isForceMapEnabled) + { + _processHandle = processHandle; + _isForceMapEnabled = isForceMapEnabled; + _memoryPools = Memory.Empty; + } + + /// + /// Create a new used for user mapping. + /// + /// The handle of the process owning the CPU memory manipulated. + /// The user memory pools. + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + public PoolMapper(uint processHandle, Memory memoryPool, bool isForceMapEnabled) + { + _processHandle = processHandle; + _memoryPools = memoryPool; + _isForceMapEnabled = isForceMapEnabled; + } + + /// + /// Initialize the for system use. + /// + /// The for system use. + /// The to assign. + /// The size to assign. + /// Returns true if mapping on the succeeded. + public bool InitializeSystemPool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size) + { + if (memoryPool.Location != MemoryPoolState.LocationType.Dsp) + { + return false; + } + + return InitializePool(ref memoryPool, cpuAddress, size); + } + + /// + /// Initialize the . + /// + /// The . + /// The to assign. + /// The size to assign. + /// Returns true if mapping on the succeeded. + public bool InitializePool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size) + { + memoryPool.SetCpuAddress(cpuAddress, size); + + return Map(ref memoryPool) != 0; + } + + /// + /// Get the process handle associated to the . + /// + /// The . + /// Returns the process handle associated to the . + public uint GetProcessHandle(ref MemoryPoolState memoryPool) + { + if (memoryPool.Location == MemoryPoolState.LocationType.Cpu) + { + return CurrentProcessPseudoHandle; + } + else if (memoryPool.Location == MemoryPoolState.LocationType.Dsp) + { + return _processHandle; + } + + return 0; + } + + /// + /// Map the on the . + /// + /// The to map. + /// Returns the DSP address mapped. + public DspAddress Map(ref MemoryPoolState memoryPool) + { + DspAddress result = AudioProcessorMemoryManager.Map(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size); + + if (result != 0) + { + memoryPool.DspAddress = result; + } + + return result; + } + + /// + /// Unmap the from the . + /// + /// The to unmap. + /// Returns true if unmapped. + public bool Unmap(ref MemoryPoolState memoryPool) + { + if (memoryPool.IsUsed) + { + return false; + } + + AudioProcessorMemoryManager.Unmap(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size); + + memoryPool.SetCpuAddress(0, 0); + memoryPool.DspAddress = 0; + + return true; + } + + /// + /// Find a associated to the region given. + /// + /// The region . + /// The region size. + /// Returns the found or if not found. + private Span FindMemoryPool(CpuAddress cpuAddress, ulong size) + { + if (!_memoryPools.IsEmpty && _memoryPools.Length > 0) + { + for (int i = 0; i < _memoryPools.Length; i++) + { + if (_memoryPools.Span[i].Contains(cpuAddress, size)) + { + return _memoryPools.Span.Slice(i, 1); + } + } + } + + return Span.Empty; + } + + /// + /// Force unmap the given . + /// + /// The to force unmap + public void ForceUnmap(ref AddressInfo addressInfo) + { + if (_isForceMapEnabled) + { + Span memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size); + + if (!memoryPool.IsEmpty) + { + AudioProcessorMemoryManager.Unmap(_processHandle, memoryPool[0].CpuAddress, memoryPool[0].Size); + + return; + } + + AudioProcessorMemoryManager.Unmap(_processHandle, addressInfo.CpuAddress, 0); + } + } + + /// + /// Try to attach the given region to the . + /// + /// The error information if an error was generated. + /// The to attach the region to. + /// The region . + /// The region size. + /// Returns true if mapping was performed. + public bool TryAttachBuffer(out ErrorInfo errorInfo, ref AddressInfo addressInfo, CpuAddress cpuAddress, ulong size) + { + errorInfo = new ErrorInfo(); + + addressInfo.Setup(cpuAddress, size); + + if (AssignDspAddress(ref addressInfo)) + { + errorInfo.ErrorCode = 0x0; + errorInfo.ExtraErrorInfo = 0x0; + + return true; + } + else + { + errorInfo.ErrorCode = ResultCode.InvalidAddressInfo; + errorInfo.ExtraErrorInfo = addressInfo.CpuAddress; + + return _isForceMapEnabled; + } + } + + /// + /// Update a using user parameters. + /// + /// The to update. + /// Input user parameter. + /// Output user parameter. + /// Returns the of the operations performed. + public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) + { + MemoryPoolUserState inputState = inParameter.State; + + MemoryPoolUserState outputState; + + const uint pageSize = 0x1000; + + if (inputState != MemoryPoolUserState.RequestAttach && inputState != MemoryPoolUserState.RequestDetach) + { + return UpdateResult.Success; + } + + if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress & (pageSize - 1)) != 0) + { + return UpdateResult.InvalidParameter; + } + + if (inParameter.Size == 0 || (inParameter.Size & (pageSize - 1)) != 0) + { + return UpdateResult.InvalidParameter; + } + + if (inputState == MemoryPoolUserState.RequestAttach) + { + bool initializeSuccess = InitializePool(ref memoryPool, inParameter.CpuAddress, inParameter.Size); + + if (!initializeSuccess) + { + memoryPool.SetCpuAddress(0, 0); + + Logger.Error?.Print(LogClass.AudioRenderer, $"Map of memory pool (address: 0x{inParameter.CpuAddress:x}, size 0x{inParameter.Size:x}) failed!"); + return UpdateResult.MapError; + } + + outputState = MemoryPoolUserState.Attached; + } + else + { + if (memoryPool.CpuAddress != inParameter.CpuAddress || memoryPool.Size != inParameter.Size) + { + return UpdateResult.InvalidParameter; + } + + if (!Unmap(ref memoryPool)) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Unmap of memory pool (address: 0x{memoryPool.CpuAddress:x}, size 0x{memoryPool.Size:x}) failed!"); + return UpdateResult.UnmapError; + } + + outputState = MemoryPoolUserState.Detached; + } + + outStatus.State = outputState; + + return UpdateResult.Success; + } + + /// + /// Map the to the . + /// + /// The to map. + /// Returns true if mapping was performed. + private bool AssignDspAddress(ref AddressInfo addressInfo) + { + if (addressInfo.CpuAddress == 0) + { + return false; + } + + if (_memoryPools.Length > 0) + { + Span memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size); + + if (!memoryPool.IsEmpty) + { + addressInfo.SetupMemoryPool(memoryPool); + + return true; + } + } + + if (_isForceMapEnabled) + { + DspAddress dspAddress = AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size); + + addressInfo.ForceMappedDspAddress = dspAddress; + + AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size); + } + else + { + unsafe + { + addressInfo.SetupMemoryPool(MemoryPoolState.Null); + } + } + + return false; + } + + /// + /// Remove the usage flag from all the . + /// + /// The to reset. + public static void ClearUsageState(Memory memoryPool) + { + foreach (ref MemoryPoolState info in memoryPool.Span) + { + info.IsUsed = false; + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs b/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs new file mode 100644 index 00000000..46c244bb --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs @@ -0,0 +1,276 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Mix +{ + /// + /// Mix context. + /// + public class MixContext + { + /// + /// The total mix count. + /// + private uint _mixesCount; + + /// + /// Storage for . + /// + private Memory _mixes; + + /// + /// Storage of the sorted indices to . + /// + private Memory _sortedMixes; + + /// + /// Graph state. + /// + public NodeStates NodeStates { get; } + + /// + /// The instance of the adjacent matrix. + /// + public EdgeMatrix EdgeMatrix { get; } + + /// + /// Create a new instance of . + /// + public MixContext() + { + NodeStates = new NodeStates(); + EdgeMatrix = new EdgeMatrix(); + } + + /// + /// Initialize the . + /// + /// The storage for sorted indices. + /// The storage of . + /// The storage used for the . + /// The storage used for the . + public void Initialize(Memory sortedMixes, Memory mixes, Memory nodeStatesWorkBuffer, Memory edgeMatrixWorkBuffer) + { + _mixesCount = (uint)mixes.Length; + _mixes = mixes; + _sortedMixes = sortedMixes; + + if (!nodeStatesWorkBuffer.IsEmpty && !edgeMatrixWorkBuffer.IsEmpty) + { + NodeStates.Initialize(nodeStatesWorkBuffer, mixes.Length); + EdgeMatrix.Initialize(edgeMatrixWorkBuffer, mixes.Length); + } + + int sortedId = 0; + for (int i = 0; i < _mixes.Length; i++) + { + SetSortedState(sortedId++, i); + } + } + + /// + /// Associate the given to a given . + /// + /// The sorted id. + /// The index to associate. + private void SetSortedState(int id, int targetIndex) + { + _sortedMixes.Span[id] = targetIndex; + } + + /// + /// Get a reference to the final . + /// + /// A reference to the final . + public ref MixState GetFinalState() + { + return ref GetState(RendererConstants.FinalMixId); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref MixState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_mixes, id, _mixesCount); + } + + /// + /// Get a reference to a at the given of the sorted mix info. + /// + /// The index to use. + /// A reference to a at the given . + public ref MixState GetSortedState(int id) + { + Debug.Assert(id >= 0 && id < _mixesCount); + + return ref GetState(_sortedMixes.Span[id]); + } + + /// + /// Get the total mix count. + /// + /// The total mix count. + public uint GetCount() + { + return _mixesCount; + } + + /// + /// Update the internal distance from the final mix value of every . + /// + private void UpdateDistancesFromFinalMix() + { + foreach (ref MixState mix in _mixes.Span) + { + mix.ClearDistanceFromFinalMix(); + } + + for (int i = 0; i < GetCount(); i++) + { + ref MixState mix = ref GetState(i); + + SetSortedState(i, i); + + if (mix.IsUsed) + { + uint distance; + + if (mix.MixId != RendererConstants.FinalMixId) + { + int mixId = mix.MixId; + + for (distance = 0; distance < GetCount(); distance++) + { + if (mixId == RendererConstants.UnusedMixId) + { + distance = MixState.InvalidDistanceFromFinalMix; + break; + } + + ref MixState distanceMix = ref GetState(mixId); + + if (distanceMix.DistanceFromFinalMix != MixState.InvalidDistanceFromFinalMix) + { + distance = distanceMix.DistanceFromFinalMix + 1; + break; + } + + mixId = distanceMix.DestinationMixId; + + if (mixId == RendererConstants.FinalMixId) + { + break; + } + } + + if (distance > GetCount()) + { + distance = MixState.InvalidDistanceFromFinalMix; + } + } + else + { + distance = MixState.InvalidDistanceFromFinalMix; + } + + mix.DistanceFromFinalMix = distance; + } + } + } + + /// + /// Update the internal mix buffer offset of all . + /// + private void UpdateMixBufferOffset() + { + uint offset = 0; + + foreach (ref MixState mix in _mixes.Span) + { + mix.BufferOffset = offset; + + offset += mix.BufferCount; + } + } + + /// + /// Sort the mixes using distance from the final mix. + /// + public void Sort() + { + UpdateDistancesFromFinalMix(); + + int[] sortedMixesTemp = _sortedMixes.Slice(0, (int)GetCount()).ToArray(); + + Array.Sort(sortedMixesTemp, (a, b) => + { + ref MixState stateA = ref GetState(a); + ref MixState stateB = ref GetState(b); + + return stateB.DistanceFromFinalMix.CompareTo(stateA.DistanceFromFinalMix); + }); + + sortedMixesTemp.AsSpan().CopyTo(_sortedMixes.Span); + + UpdateMixBufferOffset(); + } + + /// + /// Sort the mixes and splitters using an adjacency matrix. + /// + /// The used. + /// Return true, if no errors in the graph were detected. + public bool Sort(SplitterContext splitterContext) + { + if (splitterContext.UsingSplitter()) + { + bool isValid = NodeStates.Sort(EdgeMatrix); + + if (isValid) + { + ReadOnlySpan sortedMixesIndex = NodeStates.GetTsortResult(); + + int id = 0; + + for (int i = sortedMixesIndex.Length - 1; i >= 0; i--) + { + SetSortedState(id++, sortedMixesIndex[i]); + } + + UpdateMixBufferOffset(); + } + + return isValid; + } + else + { + UpdateMixBufferOffset(); + + return true; + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Mix/MixState.cs b/Ryujinx.Audio.Renderer/Server/Mix/MixState.cs new file mode 100644 index 00000000..ed4665f5 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Mix/MixState.cs @@ -0,0 +1,330 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using static Ryujinx.Audio.Renderer.RendererConstants; + +namespace Ryujinx.Audio.Renderer.Server.Mix +{ + /// + /// Server state for a mix. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x940, Pack = Alignment)] + public struct MixState + { + public const uint InvalidDistanceFromFinalMix = 0x80000000; + + public const int Alignment = 0x10; + + /// + /// Base volume of the mix. + /// + public float Volume; + + /// + /// Target sample rate of the mix. + /// + public uint SampleRate; + + /// + /// Target buffer count. + /// + public uint BufferCount; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// The id of the mix. + /// + public int MixId; + + /// + /// The mix node id. + /// + public int NodeId; + + /// + /// the buffer offset to use for command generation. + /// + public uint BufferOffset; + + /// + /// The distance of the mix from the final mix. + /// + public uint DistanceFromFinalMix; + + /// + /// The effect processing order storage. + /// + private IntPtr _effectProcessingOrderArrayPointer; + + /// + /// The max element count that can be found in the effect processing order storage. + /// + public uint EffectProcessingOrderArrayMaxCount; + + /// + /// The mix to output the result of this mix. + /// + public int DestinationMixId; + + /// + /// Mix buffer volumes storage. + /// + private MixVolumeArray _mixVolumeArray; + + /// + /// The splitter to output the result of this mix. + /// + public uint DestinationSplitterId; + + /// + /// If set to true, the long size pre-delay is supported on the reverb command. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsLongSizePreDelaySupported; + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + private struct MixVolumeArray + { + private const int Size = 4 * MixBufferCountMax * MixBufferCountMax; + } + + /// + /// Mix buffer volumes. + /// + /// Used when no splitter id is specified. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixVolumeArray); + + /// + /// Get the volume for a given connection destination. + /// + /// The source node index. + /// The destination node index + /// The volume for the given connection destination. + public float GetMixBufferVolume(int sourceIndex, int destinationIndex) + { + return MixBufferVolume[sourceIndex * MixBufferCountMax + destinationIndex]; + } + + /// + /// The array used to order effects associated to this mix. + /// + public Span EffectProcessingOrderArray + { + get + { + if (_effectProcessingOrderArrayPointer == IntPtr.Zero) + { + return Span.Empty; + } + + unsafe + { + return new Span((void*)_effectProcessingOrderArrayPointer, (int)EffectProcessingOrderArrayMaxCount); + } + } + } + + /// + /// Create a new + /// + /// + /// + public MixState(Memory effectProcessingOrderArray, ref BehaviourContext behaviourContext) : this() + { + MixId = UnusedMixId; + + DistanceFromFinalMix = InvalidDistanceFromFinalMix; + + DestinationMixId = UnusedMixId; + + DestinationSplitterId = UnusedSplitterId; + + unsafe + { + // SAFETY: safe as effectProcessingOrderArray comes from the work buffer memory that is pinned. + _effectProcessingOrderArrayPointer = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(effectProcessingOrderArray.Span)); + } + + EffectProcessingOrderArrayMaxCount = (uint)effectProcessingOrderArray.Length; + + IsLongSizePreDelaySupported = behaviourContext.IsLongSizePreDelaySupported(); + + ClearEffectProcessingOrder(); + } + + /// + /// Clear the value to its default state. + /// + public void ClearDistanceFromFinalMix() + { + DistanceFromFinalMix = InvalidDistanceFromFinalMix; + } + + /// + /// Clear the to its default state. + /// + public void ClearEffectProcessingOrder() + { + EffectProcessingOrderArray.Fill(-1); + } + + /// + /// Return true if the mix has any destinations. + /// + /// True if the mix has any destinations. + public bool HasAnyDestination() + { + return DestinationMixId != UnusedMixId || DestinationSplitterId != UnusedSplitterId; + } + + /// + /// Update the mix connection on the adjacency matrix. + /// + /// The adjacency matrix. + /// The input parameter of the mix. + /// The splitter context. + /// Return true, new connections were done on the adjacency matrix. + private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext) + { + bool hasNewConnections; + + if (DestinationSplitterId == UnusedSplitterId) + { + hasNewConnections = false; + } + else + { + ref SplitterState splitter = ref splitterContext.GetState((int)DestinationSplitterId); + + hasNewConnections = splitter.HasNewConnection; + } + + if (DestinationMixId == parameter.DestinationMixId && DestinationSplitterId == parameter.DestinationSplitterId && !hasNewConnections) + { + return false; + } + + edgeMatrix.RemoveEdges(MixId); + + if (parameter.DestinationMixId == UnusedMixId) + { + if (parameter.DestinationSplitterId != UnusedSplitterId) + { + ref SplitterState splitter = ref splitterContext.GetState((int)parameter.DestinationSplitterId); + + for (int i = 0; i < splitter.DestinationCount; i++) + { + Span destination = splitter.GetData(i); + + if (!destination.IsEmpty) + { + int destinationMixId = destination[0].DestinationId; + + if (destinationMixId != UnusedMixId) + { + edgeMatrix.Connect(MixId, destinationMixId); + } + } + } + } + } + else + { + edgeMatrix.Connect(MixId, parameter.DestinationMixId); + } + + DestinationMixId = parameter.DestinationMixId; + DestinationSplitterId = parameter.DestinationSplitterId; + + return true; + } + + /// + /// Update the mix from user information. + /// + /// The adjacency matrix. + /// The input parameter of the mix. + /// The effect context. + /// The splitter context. + /// The behaviour context. + /// Return true if the mix was changed. + public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) + { + bool isDirty; + + Volume = parameter.Volume; + SampleRate = parameter.SampleRate; + BufferCount = parameter.BufferCount; + IsUsed = parameter.IsUsed; + MixId = parameter.MixId; + NodeId = parameter.NodeId; + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + if (behaviourContext.IsSplitterSupported()) + { + isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext); + } + else + { + isDirty = DestinationMixId != parameter.DestinationMixId; + + if (DestinationMixId != parameter.DestinationMixId) + { + DestinationMixId = parameter.DestinationMixId; + } + + DestinationSplitterId = UnusedSplitterId; + } + + ClearEffectProcessingOrder(); + + for (int i = 0; i < effectContext.GetCount(); i++) + { + ref BaseEffect effect = ref effectContext.GetEffect(i); + + if (effect.MixId == MixId) + { + Debug.Assert(effect.ProcessingOrder <= EffectProcessingOrderArrayMaxCount); + + if (effect.ProcessingOrder > EffectProcessingOrderArrayMaxCount) + { + return isDirty; + } + + EffectProcessingOrderArray[(int)effect.ProcessingOrder] = i; + } + } + + return isDirty; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceDetailEntry.cs b/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceDetailEntry.cs new file mode 100644 index 00000000..d45b60eb --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceDetailEntry.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Represents a detailed entry in a performance frame. + /// + public interface IPerformanceDetailEntry + { + /// + /// Get the start time of this entry event (in microseconds). + /// + /// The start time of this entry event (in microseconds). + int GetStartTime(); + + /// + /// Get the start time offset in this structure. + /// + /// The start time offset in this structure. + int GetStartTimeOffset(); + + /// + /// Get the processing time of this entry event (in microseconds). + /// + /// The processing time of this entry event (in microseconds). + int GetProcessingTime(); + + /// + /// Get the processing time offset in this structure. + /// + /// The processing time offset in this structure. + int GetProcessingTimeOffset(); + + /// + /// Set the of this entry. + /// + /// The node id of this entry. + void SetNodeId(int nodeId); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetEntryType(PerformanceEntryType type); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetDetailType(PerformanceDetailType detailType); + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceEntry.cs b/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceEntry.cs new file mode 100644 index 00000000..2b7b5405 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceEntry.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Represents an entry in a performance frame. + /// + public interface IPerformanceEntry + { + /// + /// Get the start time of this entry event (in microseconds). + /// + /// The start time of this entry event (in microseconds). + int GetStartTime(); + + /// + /// Get the start time offset in this structure. + /// + /// The start time offset in this structure. + int GetStartTimeOffset(); + + /// + /// Get the processing time of this entry event (in microseconds). + /// + /// The processing time of this entry event (in microseconds). + int GetProcessingTime(); + + /// + /// Get the processing time offset in this structure. + /// + /// The processing time offset in this structure. + int GetProcessingTimeOffset(); + + /// + /// Set the of this entry. + /// + /// The node id of this entry. + void SetNodeId(int nodeId); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetEntryType(PerformanceEntryType type); + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceHeader.cs b/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceHeader.cs new file mode 100644 index 00000000..d5e6e9ae --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceHeader.cs @@ -0,0 +1,97 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// The header of a performance frame. + /// + public interface IPerformanceHeader + { + /// + /// Get the entry count offset in this structure. + /// + /// The entry count offset in this structure. + int GetEntryCountOffset(); + + /// + /// Set the DSP running behind flag. + /// + /// The flag. + void SetDspRunningBehind(bool isRunningBehind); + + /// + /// Set the count of voices that were dropped. + /// + /// The count of voices that were dropped. + void SetVoiceDropCount(uint voiceCount); + + /// + /// Set the start ticks of the . (before sending commands) + /// + /// The start ticks of the . (before sending commands) + void SetStartRenderingTicks(ulong startTicks); + + /// + /// Set the header magic. + /// + /// The header magic. + void SetMagic(uint magic); + + /// + /// Set the offset of the next performance header. + /// + /// The offset of the next performance header. + void SetNextOffset(int nextOffset); + + /// + /// Set the total time taken by all the commands profiled. + /// + /// The total time taken by all the commands profiled. + void SetTotalProcessingTime(int totalProcessingTime); + + /// + /// Set the index of this performance frame. + /// + /// The index of this performance frame. + void SetIndex(uint index); + + /// + /// Get the total count of entries in this frame. + /// + /// The total count of entries in this frame. + int GetEntryCount(); + + /// + /// Get the total count of detailed entries in this frame. + /// + /// The total count of detailed entries in this frame. + int GetEntryDetailCount(); + + /// + /// Set the total count of entries in this frame. + /// + /// The total count of entries in this frame. + void SetEntryCount(int entryCount); + + /// + /// Set the total count of detailed entries in this frame. + /// + /// The total count of detailed entries in this frame. + void SetEntryDetailCount(int entryDetailCount); + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion1.cs b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion1.cs new file mode 100644 index 00000000..6f6e7e57 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion1.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct PerformanceDetailVersion1 : IPerformanceDetailEntry + { + /// + /// The node id associated to this detailed entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this detailed entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this detailed entry. + /// + public int ProcessingTime; + + /// + /// The detailed entry type associated to this detailed entry. + /// + public PerformanceDetailType DetailType; + + /// + /// The entry type associated to this detailed entry. + /// + public PerformanceEntryType EntryType; + + public int GetProcessingTime() + { + return ProcessingTime; + } + + public int GetProcessingTimeOffset() + { + return 8; + } + + public int GetStartTime() + { + return StartTime; + } + + public int GetStartTimeOffset() + { + return 4; + } + + public void SetDetailType(PerformanceDetailType detailType) + { + DetailType = detailType; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion2.cs b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion2.cs new file mode 100644 index 00000000..edd371b5 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion2.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceDetailVersion2 : IPerformanceDetailEntry + { + /// + /// The node id associated to this detailed entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this detailed entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this detailed entry. + /// + public int ProcessingTime; + + /// + /// The detailed entry type associated to this detailed entry. + /// + public PerformanceDetailType DetailType; + + /// + /// The entry type associated to this detailed entry. + /// + public PerformanceEntryType EntryType; + + public int GetProcessingTime() + { + return ProcessingTime; + } + + public int GetProcessingTimeOffset() + { + return 8; + } + + public int GetStartTime() + { + return StartTime; + } + + public int GetStartTimeOffset() + { + return 4; + } + + public void SetDetailType(PerformanceDetailType detailType) + { + DetailType = detailType; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryAddresses.cs b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryAddresses.cs new file mode 100644 index 00000000..e56ec559 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryAddresses.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Information used by the performance command to store informations in the performance entry. + /// + public class PerformanceEntryAddresses + { + /// + /// The memory storing the performance entry. + /// + public Memory BaseMemory; + + /// + /// The offset to the start time field. + /// + public uint StartTimeOffset; + + /// + /// The offset to the entry count field. + /// + public uint EntryCountOffset; + + /// + /// The offset to the processing time field. + /// + public uint ProcessingTimeOffset; + + /// + /// Increment the entry count. + /// + public void IncrementEntryCount() + { + BaseMemory.Span[(int)EntryCountOffset / 4]++; + } + + /// + /// Set the start time in the entry. + /// + /// The start time in nanoseconds. + public void SetStartTime(ulong startTimeNano) + { + BaseMemory.Span[(int)StartTimeOffset / 4] = (int)(startTimeNano / 1000); + } + + /// + /// Set the processing time in the entry. + /// + /// The end time in nanoseconds. + public void SetProcessingTime(ulong endTimeNano) + { + BaseMemory.Span[(int)ProcessingTimeOffset / 4] = (int)(endTimeNano / 1000) - BaseMemory.Span[(int)StartTimeOffset / 4]; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion1.cs b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion1.cs new file mode 100644 index 00000000..0dd50875 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion1.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct PerformanceEntryVersion1 : IPerformanceEntry + { + /// + /// The node id associated to this entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this entry. + /// + public int ProcessingTime; + + /// + /// The entry type associated to this entry. + /// + public PerformanceEntryType EntryType; + + public int GetProcessingTime() + { + return ProcessingTime; + } + + public int GetProcessingTimeOffset() + { + return 8; + } + + public int GetStartTime() + { + return StartTime; + } + + public int GetStartTimeOffset() + { + return 4; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion2.cs b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion2.cs new file mode 100644 index 00000000..7ce1e324 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion2.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceEntryVersion2 : IPerformanceEntry + { + /// + /// The node id associated to this entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this entry. + /// + public int ProcessingTime; + + /// + /// The entry type associated to this entry. + /// + public PerformanceEntryType EntryType; + + public int GetProcessingTime() + { + return ProcessingTime; + } + + public int GetProcessingTimeOffset() + { + return 8; + } + + public int GetStartTime() + { + return StartTime; + } + + public int GetStartTimeOffset() + { + return 4; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs new file mode 100644 index 00000000..f4df7b09 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs @@ -0,0 +1,118 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceFrameHeaderVersion1 : IPerformanceHeader + { + /// + /// The magic of the performance header. + /// + public uint Magic; + + /// + /// The total count of entries in this frame. + /// + public int EntryCount; + + /// + /// The total count of detailed entries in this frame. + /// + public int EntryDetailCount; + + /// + /// The offset of the next performance header. + /// + public int NextOffset; + + /// + /// The total time taken by all the commands profiled. + /// + public int TotalProcessingTime; + + /// + /// The count of voices that were dropped. + /// + public uint VoiceDropCount; + + public int GetEntryCount() + { + return EntryCount; + } + + public int GetEntryCountOffset() + { + return 4; + } + + public int GetEntryDetailCount() + { + return EntryDetailCount; + } + + public void SetDspRunningBehind(bool isRunningBehind) + { + // NOTE: Not present in version 1 + } + + public void SetEntryCount(int entryCount) + { + EntryCount = entryCount; + } + + public void SetEntryDetailCount(int entryDetailCount) + { + EntryDetailCount = entryDetailCount; + } + + public void SetIndex(uint index) + { + // NOTE: Not present in version 1 + } + + public void SetMagic(uint magic) + { + Magic = magic; + } + + public void SetNextOffset(int nextOffset) + { + NextOffset = nextOffset; + } + + public void SetStartRenderingTicks(ulong startTicks) + { + // NOTE: not present in version 1 + } + + public void SetTotalProcessingTime(int totalProcessingTime) + { + TotalProcessingTime = totalProcessingTime; + } + + public void SetVoiceDropCount(uint voiceCount) + { + VoiceDropCount = voiceCount; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs new file mode 100644 index 00000000..ae81fb99 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs @@ -0,0 +1,134 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x30)] + public struct PerformanceFrameHeaderVersion2 : IPerformanceHeader + { + /// + /// The magic of the performance header. + /// + public uint Magic; + + /// + /// The total count of entries in this frame. + /// + public int EntryCount; + + /// + /// The total count of detailed entries in this frame. + /// + public int EntryDetailCount; + + /// + /// The offset of the next performance header. + /// + public int NextOffset; + + /// + /// The total time taken by all the commands profiled. + /// + public int TotalProcessingTime; + + /// + /// The count of voices that were dropped. + /// + public uint VoiceDropCount; + + /// + /// The start ticks of the . (before sending commands) + /// + public ulong StartRenderingTicks; + + /// + /// The index of this performance frame. + /// + public uint Index; + + /// + /// If set to true, the DSP is running behind. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsDspRunningBehind; + + public int GetEntryCount() + { + return EntryCount; + } + + public int GetEntryCountOffset() + { + return 4; + } + + public int GetEntryDetailCount() + { + return EntryDetailCount; + } + + public void SetDspRunningBehind(bool isRunningBehind) + { + IsDspRunningBehind = isRunningBehind; + } + + public void SetEntryCount(int entryCount) + { + EntryCount = entryCount; + } + + public void SetEntryDetailCount(int entryDetailCount) + { + EntryDetailCount = entryDetailCount; + } + + public void SetIndex(uint index) + { + Index = index; + } + + public void SetMagic(uint magic) + { + Magic = magic; + } + + public void SetNextOffset(int nextOffset) + { + NextOffset = nextOffset; + } + + public void SetStartRenderingTicks(ulong startTicks) + { + StartRenderingTicks = startTicks; + } + + public void SetTotalProcessingTime(int totalProcessingTime) + { + TotalProcessingTime = totalProcessingTime; + } + + public void SetVoiceDropCount(uint voiceCount) + { + VoiceDropCount = voiceCount; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceManager.cs b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceManager.cs new file mode 100644 index 00000000..122f468c --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceManager.cs @@ -0,0 +1,124 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + public abstract class PerformanceManager + { + /// + /// Get the required size for a single performance frame. + /// + /// The audio renderer configuration. + /// The behaviour context. + /// The required size for a single performance frame. + public static ulong GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter, ref BehaviourContext behaviourContext) + { + uint version = behaviourContext.GetPerformanceMetricsDataFormat(); + + if (version == 2) + { + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + } + else if (version == 1) + { + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + } + + throw new NotImplementedException($"Unknown Performance metrics data format version {version}"); + } + + /// + /// Copy the performance frame history to the supplied user buffer and returns the size copied. + /// + /// The supplied user buffer to store the performance frame into. + /// The size copied to the supplied buffer. + public abstract uint CopyHistories(Span performanceOutput); + + /// + /// Set the target node id to profile. + /// + /// The target node id to profile. + public abstract void SetTargetNodeId(int target); + + /// + /// Check if the given target node id is profiled. + /// + /// The target node id to check. + /// Return true, if the given target node id is profiled. + public abstract bool IsTargetNodeId(int target); + + /// + /// Get the next buffer to store a performance entry. + /// + /// The output . + /// The info. + /// The node id of the entry. + /// Return true, if a valid was returned. + public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId); + + /// + /// Get the next buffer to store a performance detailed entry. + /// + /// The output . + /// The info. + /// The info. + /// The node id of the entry. + /// Return true, if a valid was returned. + public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId); + + /// + /// Finalize the current performance frame. + /// + /// Indicate if the DSP is running behind. + /// The count of voices that were dropped. + /// The start ticks of the audio rendering. + public abstract void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks); + + /// + /// Create a new . + /// + /// The backing memory available for use by the manager. + /// The audio renderer configuration. + /// The behaviour context; + /// A new . + public static PerformanceManager Create(Memory performanceBuffer, ref AudioRendererConfiguration parameter, BehaviourContext behaviourContext) + { + uint version = behaviourContext.GetPerformanceMetricsDataFormat(); + + switch (version) + { + case 1: + return new PerformanceManagerGeneric(performanceBuffer, + ref parameter); + case 2: + return new PerformanceManagerGeneric(performanceBuffer, + ref parameter); + default: + throw new NotImplementedException($"Unknown Performance metrics data format version {version}"); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceManagerGeneric.cs b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceManagerGeneric.cs new file mode 100644 index 00000000..54d9dc42 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Performance/PerformanceManagerGeneric.cs @@ -0,0 +1,311 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// A Generic implementation of . + /// + /// The header implementation of the performance frame. + /// The entry implementation of the performance frame. + /// A detailed implementation of the performance frame. + public class PerformanceManagerGeneric : PerformanceManager where THeader: unmanaged, IPerformanceHeader where TEntry : unmanaged, IPerformanceEntry where TEntryDetail: unmanaged, IPerformanceDetailEntry + { + /// + /// The magic used for the . + /// + private const uint MagicPerformanceBuffer = 0x46524550; + + /// + /// The fixed amount of that can be stored in a frame. + /// + private const int MaxFrameDetailCount = 100; + + private Memory _buffer; + private Memory _historyBuffer; + + private Memory CurrentBuffer => _buffer.Slice(0, _frameSize); + private Memory CurrentBufferData => CurrentBuffer.Slice(Unsafe.SizeOf()); + + private ref THeader CurrentHeader => ref MemoryMarshal.Cast(CurrentBuffer.Span)[0]; + + private Span Entries => MemoryMarshal.Cast(CurrentBufferData.Span.Slice(0, GetEntriesSize())); + private Span EntriesDetail => MemoryMarshal.Cast(CurrentBufferData.Span.Slice(GetEntriesSize(), GetEntriesDetailSize())); + + private int _frameSize; + private int _availableFrameCount; + private int _entryCountPerFrame; + private int _detailTarget; + private int _entryIndex; + private int _entryDetailIndex; + private int _indexHistoryWrite; + private int _indexHistoryRead; + private uint _historyFrameIndex; + + public PerformanceManagerGeneric(Memory buffer, ref AudioRendererConfiguration parameter) + { + _buffer = buffer; + _frameSize = GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + + _entryCountPerFrame = (int)GetEntryCount(ref parameter); + _availableFrameCount = buffer.Length / _frameSize - 1; + + _historyFrameIndex = 0; + + _historyBuffer = _buffer.Slice(_frameSize); + + SetupNewHeader(); + } + + private Span GetBufferFromIndex(Span data, int index) + { + return data.Slice(index * _frameSize, _frameSize); + } + + private ref THeader GetHeaderFromBuffer(Span data, int index) + { + return ref MemoryMarshal.Cast(GetBufferFromIndex(data, index))[0]; + } + + private Span GetEntriesFromBuffer(Span data, int index) + { + return MemoryMarshal.Cast(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf(), GetEntriesSize())); + } + + private Span GetEntriesDetailFromBuffer(Span data, int index) + { + return MemoryMarshal.Cast(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf() + GetEntriesSize(), GetEntriesDetailSize())); + } + + private void SetupNewHeader() + { + _entryIndex = 0; + _entryDetailIndex = 0; + + CurrentHeader.SetEntryCount(0); + CurrentHeader.SetEntryDetailCount(0); + } + + public static uint GetEntryCount(ref AudioRendererConfiguration parameter) + { + return parameter.VoiceCount + parameter.EffectCount + parameter.SubMixBufferCount + parameter.SinkCount + 1; + } + + public int GetEntriesSize() + { + return Unsafe.SizeOf() * _entryCountPerFrame; + } + + public static int GetEntriesDetailSize() + { + return Unsafe.SizeOf() * MaxFrameDetailCount; + } + + public static int GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter) + { + return Unsafe.SizeOf() * (int)GetEntryCount(ref parameter) + GetEntriesDetailSize() + Unsafe.SizeOf(); + } + + public override uint CopyHistories(Span performanceOutput) + { + if (performanceOutput.IsEmpty) + { + return 0; + } + + int nextOffset = 0; + + while (_indexHistoryRead != _indexHistoryWrite) + { + if (nextOffset >= performanceOutput.Length) + { + break; + } + + ref THeader inputHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, _indexHistoryRead); + Span inputEntries = GetEntriesFromBuffer(_historyBuffer.Span, _indexHistoryRead); + Span inputEntriesDetail = GetEntriesDetailFromBuffer(_historyBuffer.Span, _indexHistoryRead); + + Span targetSpan = performanceOutput.Slice(nextOffset); + + ref THeader outputHeader = ref MemoryMarshal.Cast(targetSpan)[0]; + + nextOffset += Unsafe.SizeOf(); + + Span outputEntries = MemoryMarshal.Cast(targetSpan.Slice(nextOffset)); + + int totalProcessingTime = 0; + + int effectiveEntryCount = 0; + + for (int entryIndex = 0; entryIndex < inputHeader.GetEntryCount(); entryIndex++) + { + ref TEntry input = ref inputEntries[entryIndex]; + + if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0) + { + ref TEntry output = ref outputEntries[effectiveEntryCount++]; + + output = input; + + nextOffset += Unsafe.SizeOf(); + + totalProcessingTime += input.GetProcessingTime(); + } + } + + Span outputEntriesDetail = MemoryMarshal.Cast(targetSpan.Slice(nextOffset)); + + int effectiveEntryDetailCount = 0; + + for (int entryDetailIndex = 0; entryDetailIndex < inputHeader.GetEntryDetailCount(); entryDetailIndex++) + { + ref TEntryDetail input = ref inputEntriesDetail[entryDetailIndex]; + + if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0) + { + ref TEntryDetail output = ref outputEntriesDetail[effectiveEntryDetailCount++]; + + output = input; + + nextOffset += Unsafe.SizeOf(); + } + } + + outputHeader = inputHeader; + outputHeader.SetMagic(MagicPerformanceBuffer); + outputHeader.SetTotalProcessingTime(totalProcessingTime); + outputHeader.SetNextOffset(nextOffset); + outputHeader.SetEntryCount(effectiveEntryCount); + outputHeader.SetEntryDetailCount(effectiveEntryDetailCount); + + _indexHistoryRead = (_indexHistoryRead + 1) % _availableFrameCount; + } + + if (nextOffset < performanceOutput.Length && (performanceOutput.Length - nextOffset) >= Unsafe.SizeOf()) + { + ref THeader outputHeader = ref MemoryMarshal.Cast(performanceOutput.Slice(nextOffset))[0]; + + outputHeader = default; + } + + return (uint)nextOffset; + } + + public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId) + { + performanceEntry = new PerformanceEntryAddresses(); + performanceEntry.BaseMemory = SpanMemoryManager.Cast(CurrentBuffer); + performanceEntry.EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(); + + uint baseEntryOffset = (uint)(Unsafe.SizeOf() + Unsafe.SizeOf() * _entryIndex); + + ref TEntry entry = ref Entries[_entryIndex]; + + performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entry.GetStartTimeOffset(); + performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entry.GetProcessingTimeOffset(); + + entry = default; + entry.SetEntryType(entryType); + entry.SetNodeId(nodeId); + + _entryIndex++; + + return true; + } + + public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId) + { + performanceEntry = null; + + if (_entryDetailIndex > MaxFrameDetailCount) + { + return false; + } + + performanceEntry = new PerformanceEntryAddresses(); + performanceEntry.BaseMemory = SpanMemoryManager.Cast(CurrentBuffer); + performanceEntry.EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(); + + uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _entryDetailIndex); + + ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex]; + + performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entryDetail.GetStartTimeOffset(); + performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entryDetail.GetProcessingTimeOffset(); + + entryDetail = default; + entryDetail.SetDetailType(detailType); + entryDetail.SetEntryType(entryType); + entryDetail.SetNodeId(nodeId); + + _entryDetailIndex++; + + return true; + } + + public override bool IsTargetNodeId(int target) + { + return _detailTarget == target; + } + + public override void SetTargetNodeId(int target) + { + _detailTarget = target; + } + + public override void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks) + { + if (_availableFrameCount > 1) + { + int targetIndexForHistory = _indexHistoryWrite; + + _indexHistoryWrite = (_indexHistoryWrite + 1) % _availableFrameCount; + + ref THeader targetHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, targetIndexForHistory); + + CurrentBuffer.Span.CopyTo(GetBufferFromIndex(_historyBuffer.Span, targetIndexForHistory)); + + uint targetHistoryFrameIndex = _historyFrameIndex; + + if (_historyFrameIndex == uint.MaxValue) + { + _historyFrameIndex = 0; + } + else + { + _historyFrameIndex++; + } + + targetHeader.SetDspRunningBehind(dspRunningBehind); + targetHeader.SetVoiceDropCount(voiceDropCount); + targetHeader.SetStartRenderingTicks(startRenderingTicks); + targetHeader.SetIndex(targetHistoryFrameIndex); + + // Finally setup the new header + SetupNewHeader(); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/RendererSystemContext.cs b/Ryujinx.Audio.Renderer/Server/RendererSystemContext.cs new file mode 100644 index 00000000..aa296d4f --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/RendererSystemContext.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Represents a lite version of used by the + /// + /// + /// This also allows to reduce dependencies on the for unit testing. + /// + public sealed class RendererSystemContext + { + /// + /// The session id of the current renderer. + /// + public int SessionId; + + /// + /// The target channel count for sink. + /// + /// See for usage. + public uint ChannelCount; + + /// + /// The total count of mix buffer. + /// + public uint MixBufferCount; + + /// + /// Instance of the used to derive bug fixes and features of the current audio renderer revision. + /// + public BehaviourContext BehaviourContext; + + /// + /// Instance of the used for upsampling (see ) + /// + public UpsamplerManager UpsamplerManager; + + /// + /// The memory to use for depop processing. + /// + /// + /// See and + /// + public Memory DepopBuffer; + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Sink/BaseSink.cs b/Ryujinx.Audio.Renderer/Server/Sink/BaseSink.cs new file mode 100644 index 00000000..0185f88a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Sink/BaseSink.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Base class used for server information of a sink. + /// + public class BaseSink + { + /// + /// The type of this . + /// + public SinkType Type; + + /// + /// Set to true if the sink is used. + /// + public bool IsUsed; + + /// + /// Set to true if the sink need to be skipped because of invalid state. + /// + public bool ShouldSkip; + + /// + /// The node id of the sink. + /// + public int NodeId; + + /// + /// Create a new . + /// + public BaseSink() + { + CleanUp(); + } + + /// + /// Clean up the internal state of the . + /// + public virtual void CleanUp() + { + Type = TargetSinkType; + IsUsed = false; + ShouldSkip = false; + } + + /// + /// The target handled by this . + /// + public virtual SinkType TargetSinkType => SinkType.Invalid; + + /// + /// Check if the sent by the user match the internal . + /// + /// The user parameter. + /// Return true, if the sent by the user match the internal . + public bool IsTypeValid(ref SinkInParameter parameter) + { + return parameter.Type == TargetSinkType; + } + + /// + /// Update the state during command generation. + /// + public virtual void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetSinkType); + } + + /// + /// Update the internal common parameters from user parameter. + /// + /// The user parameter. + protected void UpdateStandardParameter(ref SinkInParameter parameter) + { + if (IsUsed != parameter.IsUsed) + { + IsUsed = parameter.IsUsed; + NodeId = parameter.NodeId; + } + } + + /// + /// Update the internal state from user parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The user output status. + /// The mapper to use. + public virtual void Update(out ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + errorInfo = new ErrorInfo(); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Sink/CircularBufferSink.cs b/Ryujinx.Audio.Renderer/Server/Sink/CircularBufferSink.cs new file mode 100644 index 00000000..7b38d519 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Sink/CircularBufferSink.cs @@ -0,0 +1,126 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Server information for a circular buffer sink. + /// + public class CircularBufferSink : BaseSink + { + /// + /// The circular buffer parameter. + /// + public CircularBufferParameter Parameter; + + /// + /// The last written data offset on the circular buffer. + /// + private uint _lastWrittenOffset; + + /// + /// THe previous written offset of the circular buffer. + /// + private uint _oldWrittenOffset; + + /// + /// The current offset to write data on the circular buffer. + /// + public uint CurrentWriteOffset { get; private set; } + + /// + /// The of the circular buffer. + /// + public AddressInfo CircularBufferAddressInfo; + + public CircularBufferSink() + { + CircularBufferAddressInfo = AddressInfo.Create(); + } + + public override SinkType TargetSinkType => SinkType.CircularBuffer; + + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + errorInfo = new BehaviourParameter.ErrorInfo(); + outStatus = new SinkOutStatus(); + + Debug.Assert(IsTypeValid(ref parameter)); + + ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + if (parameter.IsUsed != IsUsed || ShouldSkip) + { + UpdateStandardParameter(ref parameter); + + if (parameter.IsUsed) + { + Debug.Assert(CircularBufferAddressInfo.CpuAddress == 0); + Debug.Assert(CircularBufferAddressInfo.GetReference(false) == 0); + + ShouldSkip = !mapper.TryAttachBuffer(out errorInfo, ref CircularBufferAddressInfo, inputDeviceParameter.BufferAddress, inputDeviceParameter.BufferSize); + } + else + { + Debug.Assert(CircularBufferAddressInfo.CpuAddress != 0); + Debug.Assert(CircularBufferAddressInfo.GetReference(false) != 0); + } + + Parameter = inputDeviceParameter; + } + + outStatus.LastWrittenOffset = _lastWrittenOffset; + } + + public override void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetSinkType); + + if (IsUsed) + { + uint frameSize = RendererConstants.TargetSampleSize * Parameter.SampleCount * Parameter.InputCount; + + _lastWrittenOffset = _oldWrittenOffset; + + _oldWrittenOffset = CurrentWriteOffset; + + CurrentWriteOffset += frameSize; + + if (Parameter.BufferSize > 0) + { + CurrentWriteOffset %= Parameter.BufferSize; + } + } + } + + public override void CleanUp() + { + CircularBufferAddressInfo = AddressInfo.Create(); + _lastWrittenOffset = 0; + _oldWrittenOffset = 0; + CurrentWriteOffset = 0; + base.CleanUp(); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Sink/DeviceSink.cs b/Ryujinx.Audio.Renderer/Server/Sink/DeviceSink.cs new file mode 100644 index 00000000..b7ed32fa --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Sink/DeviceSink.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Server information for a device sink. + /// + public class DeviceSink : BaseSink + { + /// + /// The downmix coefficients. + /// + public float[] DownMixCoefficients; + + /// + /// The device parameters. + /// + public DeviceParameter Parameter; + + /// + /// The upsampler instance used by this sink. + /// + /// Null if no upsampling is needed. + public UpsamplerState UpsamplerState; + + /// + /// Create a new . + /// + public DeviceSink() + { + DownMixCoefficients = new float[4]; + } + + public override void CleanUp() + { + UpsamplerState?.Release(); + + UpsamplerState = null; + + base.CleanUp(); + } + + public override SinkType TargetSinkType => SinkType.Device; + + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + if (parameter.IsUsed != IsUsed) + { + UpdateStandardParameter(ref parameter); + Parameter = inputDeviceParameter; + } + else + { + Parameter.DownMixParameterEnabled = inputDeviceParameter.DownMixParameterEnabled; + inputDeviceParameter.DownMixParameter.ToSpan().CopyTo(Parameter.DownMixParameter.ToSpan()); + } + + Parameter.DownMixParameter.ToSpan().CopyTo(DownMixCoefficients.AsSpan()); + + errorInfo = new BehaviourParameter.ErrorInfo(); + outStatus = new SinkOutStatus(); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Sink/SinkContext.cs b/Ryujinx.Audio.Renderer/Server/Sink/SinkContext.cs new file mode 100644 index 00000000..136f7538 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Sink/SinkContext.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Sink context. + /// + public class SinkContext + { + /// + /// Storage for . + /// + private BaseSink[] _sinks; + + /// + /// The total sink count. + /// + private uint _sinkCount; + + /// + /// Initialize the . + /// + /// The total sink count. + public void Initialize(uint sinksCount) + { + _sinkCount = sinksCount; + _sinks = new BaseSink[_sinkCount]; + + for (int i = 0; i < _sinkCount; i++) + { + _sinks[i] = new BaseSink(); + } + } + + /// + /// Get the total sink count. + /// + /// The total sink count. + public uint GetCount() + { + return _sinkCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref BaseSink GetSink(int id) + { + Debug.Assert(id >= 0 && id < _sinkCount); + + return ref _sinks[id]; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Splitter/SplitterContext.cs b/Ryujinx.Audio.Renderer/Server/Splitter/SplitterContext.cs new file mode 100644 index 00000000..f5599acc --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Splitter/SplitterContext.cs @@ -0,0 +1,320 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Splitter context. + /// + public class SplitterContext + { + /// + /// Storage for . + /// + private Memory _splitters; + + /// + /// Storage for . + /// + private Memory _splitterDestinations; + + /// + /// If set to true, trust the user destination count in . + /// + public bool IsBugFixed { get; private set; } + + /// + /// Initialize . + /// + /// The behaviour context. + /// The audio renderer configuration. + /// The . + /// Return true if the initialization was successful. + public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator) + { + if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0) + { + Setup(Memory.Empty, Memory.Empty, false); + + return true; + } + + Memory splitters = workBufferAllocator.Allocate(parameter.SplitterCount, SplitterState.Alignment); + + if (splitters.IsEmpty) + { + return false; + } + + int splitterId = 0; + + foreach (ref SplitterState splitter in splitters.Span) + { + splitter = new SplitterState(splitterId++); + } + + Memory splitterDestinations = workBufferAllocator.Allocate(parameter.SplitterDestinationCount, + SplitterDestination.Alignment); + + if (splitterDestinations.IsEmpty) + { + return false; + } + + int splitterDestinationId = 0; + foreach (ref SplitterDestination data in splitterDestinations.Span) + { + data = new SplitterDestination(splitterDestinationId++); + } + + SplitterState.InitializeSplitters(splitters.Span); + + Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed()); + + return true; + } + + /// + /// Get the work buffer size while adding the size needed for splitter to operate. + /// + /// The current size. + /// The behaviour context. + /// The renderer configuration. + /// Return the new size taking splitter into account. + public static ulong GetWorkBufferSize(ulong size, ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter) + { + if (behaviourContext.IsSplitterSupported()) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterCount, SplitterState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment); + + if (behaviourContext.IsSplitterBugFixed()) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, 0x10); + } + + return size; + } + else + { + return size; + } + } + + /// + /// Setup the instance. + /// + /// The storage. + /// The storage. + /// If set to true, trust the user destination count in . + private void Setup(Memory splitters, Memory splitterDestinations, bool isBugFixed) + { + _splitters = splitters; + _splitterDestinations = splitterDestinations; + IsBugFixed = isBugFixed; + } + + /// + /// Clear the new connection flag. + /// + private void ClearAllNewConnectionFlag() + { + foreach (ref SplitterState splitter in _splitters.Span) + { + splitter.ClearNewConnectionFlag(); + } + } + + /// + /// Get the destination count using the count of splitter. + /// + /// The destination count using the count of splitter. + public int GetDestinationCountPerStateForCompatibility() + { + if (_splitters.IsEmpty) + { + return 0; + } + + return _splitterDestinations.Length / _splitters.Length; + } + + /// + /// Update one or multiple from user parameters. + /// + /// The splitter header. + /// The raw data after the splitter header. + private void UpdateState(ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input) + { + for (int i = 0; i < inputHeader.SplitterCount; i++) + { + SplitterInParameter parameter = MemoryMarshal.Read(input); + + Debug.Assert(parameter.IsMagicValid()); + + if (parameter.IsMagicValid()) + { + if (parameter.Id >= 0 && parameter.Id < _splitters.Length) + { + ref SplitterState splitter = ref GetState(parameter.Id); + + splitter.Update(this, ref parameter, input.Slice(Unsafe.SizeOf())); + } + + input = input.Slice(0x1C + (int)parameter.DestinationCount * 4); + } + } + } + + /// + /// Update one or multiple from user parameters. + /// + /// The splitter header. + /// The raw data after the splitter header. + private void UpdateData(ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input) + { + for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) + { + SplitterDestinationInParameter parameter = MemoryMarshal.Read(input); + + Debug.Assert(parameter.IsMagicValid()); + + if (parameter.IsMagicValid()) + { + if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length) + { + ref SplitterDestination destination = ref GetDestination(parameter.Id); + + destination.Update(parameter); + } + + input = input.Slice(Unsafe.SizeOf()); + } + } + } + + /// + /// Update splitter from user parameters. + /// + /// The input raw user data. + /// The total consumed size. + /// Return true if the update was successful. + public bool Update(ReadOnlySpan input, out int consumedSize) + { + if (_splitterDestinations.IsEmpty || _splitters.IsEmpty) + { + consumedSize = 0; + + return true; + } + + int originalSize = input.Length; + + SplitterInParameterHeader header = SpanIOHelper.Read(ref input); + + if (header.IsMagicValid()) + { + ClearAllNewConnectionFlag(); + + UpdateState(ref header, ref input); + UpdateData(ref header, ref input); + + consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10); + + return true; + } + else + { + consumedSize = 0; + + return false; + } + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref SplitterState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_splitters, id, (uint)_splitters.Length); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref SplitterDestination GetDestination(int id) + { + return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + public Memory GetDestinationMemory(int id) + { + return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length); + } + + /// + /// Get a in the at and pass to . + /// + /// The index to use to get the . + /// The index of the . + /// A . + public Span GetDestination(int id, int destinationId) + { + ref SplitterState splitter = ref GetState(id); + + return splitter.GetData(destinationId); + } + + /// + /// Return true if the audio renderer has any splitters. + /// + /// True if the audio renderer has any splitters. + public bool UsingSplitter() + { + return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty; + } + + /// + /// Update the internal state of all splitters. + /// + public void UpdateInternalState() + { + foreach (ref SplitterState splitter in _splitters.Span) + { + splitter.UpdateInternalState(); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Splitter/SplitterDestination.cs b/Ryujinx.Audio.Renderer/Server/Splitter/SplitterDestination.cs new file mode 100644 index 00000000..5e07ba9d --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Splitter/SplitterDestination.cs @@ -0,0 +1,210 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter destination. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)] + public struct SplitterDestination + { + public const int Alignment = 0x10; + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mix; + private MixArray _previousMix; + + /// + /// Pointer to the next linked element. + /// + private unsafe SplitterDestination* _next; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if the internal state need to be updated. + /// + [MarshalAs(UnmanagedType.I1)] + public bool NeedToUpdateInternalState; + + [StructLayout(LayoutKind.Sequential, Size = 4 * RendererConstants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix); + + /// + /// Previous mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix); + + /// + /// Get the of the next element or if not present. + /// + public Span Next + { + get + { + unsafe + { + return _next != null ? new Span(_next, 1) : Span.Empty; + } + } + } + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterDestination(int id) : this() + { + Id = id; + DestinationId = RendererConstants.UnusedMixId; + + ClearVolumes(); + } + + /// + /// Update the from user parameter. + /// + /// The user parameter. + public void Update(SplitterDestinationInParameter parameter) + { + Debug.Assert(Id == parameter.Id); + + if (parameter.IsMagicValid() && Id == parameter.Id) + { + DestinationId = parameter.DestinationId; + + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + if (!IsUsed && parameter.IsUsed) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + + NeedToUpdateInternalState = false; + } + + IsUsed = parameter.IsUsed; + } + } + + /// + /// Update the internal state of the instance. + /// + public void UpdateInternalState() + { + if (IsUsed && NeedToUpdateInternalState) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + } + + NeedToUpdateInternalState = false; + } + + /// + /// Set the update internal state marker. + /// + public void MarkAsNeedToUpdateInternalState() + { + NeedToUpdateInternalState = true; + } + + /// + /// Return true if the is used and has a destination. + /// + /// True if the is used and has a destination. + public bool IsConfigured() + { + return IsUsed && DestinationId != RendererConstants.UnusedMixId; + } + + /// + /// Get the volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolume(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < RendererConstants.MixBufferCountMax); + + return MixBufferVolume[destinationIndex]; + } + + /// + /// Clear the volumes. + /// + public void ClearVolumes() + { + MixBufferVolume.Fill(0); + PreviousMixBufferVolume.Fill(0); + } + + /// + /// Link the next element to the given . + /// + /// The given to link. + public void Link(ref SplitterDestination next) + { + unsafe + { + fixed (SplitterDestination *nextPtr = &next) + { + _next = nextPtr; + } + } + } + + /// + /// Remove the link to the next element. + /// + public void Unlink() + { + unsafe + { + _next = null; + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Splitter/SplitterState.cs b/Ryujinx.Audio.Renderer/Server/Splitter/SplitterState.cs new file mode 100644 index 00000000..f86e75dd --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Splitter/SplitterState.cs @@ -0,0 +1,237 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// +using Ryujinx.Audio.Renderer.Parameter; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)] + public struct SplitterState + { + public const int Alignment = 0x10; + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// Target sample rate to use on the splitter. + /// + public uint SampleRate; + + /// + /// Count of splitter destinations (). + /// + public int DestinationCount; + + /// + /// Set to true if the splitter has a new connection. + /// + [MarshalAs(UnmanagedType.I1)] + public bool HasNewConnection; + + /// + /// Linked list of . + /// + private unsafe SplitterDestination* _destinationsData; + + /// + /// Span to the first element of the linked list of . + /// + public Span Destinations + { + get + { + unsafe + { + return (IntPtr)_destinationsData != IntPtr.Zero ? new Span(_destinationsData, 1) : Span.Empty; + } + } + } + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterState(int id) : this() + { + Id = id; + } + + public Span GetData(int index) + { + int i = 0; + + Span result = Destinations; + + while (i < index) + { + if (result.IsEmpty) + { + break; + } + + result = result[0].Next; + i++; + } + + return result; + } + + /// + /// Clear the new connection flag. + /// + public void ClearNewConnectionFlag() + { + HasNewConnection = false; + } + + /// + /// Utility function to apply a given to all . + /// + /// The action to execute on each elements. + private void ForEachDestination(SpanAction action) + { + Span temp = Destinations; + + int i = 0; + + while (true) + { + if (temp.IsEmpty) + { + break; + } + + Span next = temp[0].Next; + + action.Invoke(temp, i++); + + temp = next; + } + } + + /// + /// Update the from user parameter. + /// + /// The splitter context. + /// The user parameter. + /// The raw input data after the . + public void Update(SplitterContext context, ref SplitterInParameter parameter, ReadOnlySpan input) + { + ClearLinks(); + + int destinationCount; + + if (context.IsBugFixed) + { + destinationCount = parameter.DestinationCount; + } + else + { + destinationCount = Math.Min(context.GetDestinationCountPerStateForCompatibility(), parameter.DestinationCount); + } + + if (destinationCount > 0) + { + ReadOnlySpan destinationIds = MemoryMarshal.Cast(input); + + Memory destination = context.GetDestinationMemory(destinationIds[0]); + + SetDestination(ref destination.Span[0]); + + DestinationCount = destinationCount; + + for (int i = 1; i < destinationCount; i++) + { + Memory nextDestination = context.GetDestinationMemory(destinationIds[i]); + + destination.Span[0].Link(ref nextDestination.Span[0]); + destination = nextDestination; + } + } + + Debug.Assert(parameter.Id == Id); + + if (parameter.Id == Id) + { + SampleRate = parameter.SampleRate; + HasNewConnection = true; + } + } + + /// + /// Set the head of the linked list of . + /// + /// A reference to a . + public void SetDestination(ref SplitterDestination newValue) + { + unsafe + { + fixed (SplitterDestination* newValuePtr = &newValue) + { + _destinationsData = newValuePtr; + } + } + } + + /// + /// Update the internal state of this instance. + /// + public void UpdateInternalState() + { + ForEachDestination((destination, _) => destination[0].UpdateInternalState()); + } + + /// + /// Clear all links from the . + /// + public void ClearLinks() + { + ForEachDestination((destination, _) => destination[0].Unlink()); + + unsafe + { + _destinationsData = (SplitterDestination*)IntPtr.Zero; + } + } + + /// + /// Initialize a given . + /// + /// All the to initialize. + public static void InitializeSplitters(Span splitters) + { + foreach (ref SplitterState splitter in splitters) + { + unsafe + { + splitter._destinationsData = (SplitterDestination*)IntPtr.Zero; + } + + splitter.DestinationCount = 0; + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/StateUpdater.cs b/Ryujinx.Audio.Renderer/Server/StateUpdater.cs new file mode 100644 index 00000000..729a74b1 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/StateUpdater.cs @@ -0,0 +1,575 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Performance; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Server.Mix; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Server.Voice; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common.Logging; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + public class StateUpdater + { + private readonly ReadOnlyMemory _inputOrigin; + private ReadOnlyMemory _outputOrigin; + private ReadOnlyMemory _input; + + private Memory _output; + private uint _processHandle; + private BehaviourContext _behaviourContext; + + private UpdateDataHeader _inputHeader; + private Memory _outputHeader; + + private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; + + public StateUpdater(ReadOnlyMemory input, Memory output, uint processHandle, BehaviourContext behaviourContext) + { + _input = input; + _inputOrigin = _input; + _output = output; + _outputOrigin = _output; + _processHandle = processHandle; + _behaviourContext = behaviourContext; + + _inputHeader = SpanIOHelper.Read(ref _input); + + _outputHeader = SpanMemoryManager.Cast(_output.Slice(0, Unsafe.SizeOf())); + OutputHeader.Initialize(_behaviourContext.UserRevision); + _output = _output.Slice(Unsafe.SizeOf()); + } + + public ResultCode UpdateBehaviourContext() + { + BehaviourParameter parameter = SpanIOHelper.Read(ref _input); + + if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision) + { + return ResultCode.InvalidUpdateInfo; + } + + _behaviourContext.ClearError(); + _behaviourContext.UpdateFlags(parameter.Flags); + + if (_inputHeader.BehaviourSize != Unsafe.SizeOf()) + { + return ResultCode.InvalidUpdateInfo; + } + + return ResultCode.Success; + } + + public ResultCode UpdateMemoryPools(Span memoryPools) + { + PoolMapper mapper = new PoolMapper(_processHandle, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + if (memoryPools.Length * Unsafe.SizeOf() != _inputHeader.MemoryPoolsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + foreach (ref MemoryPoolState memoryPool in memoryPools) + { + MemoryPoolInParameter parameter = SpanIOHelper.Read(ref _input); + + ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus); + + if (updateResult != PoolMapper.UpdateResult.Success && + updateResult != PoolMapper.UpdateResult.MapError && + updateResult != PoolMapper.UpdateResult.UnmapError) + { + if (updateResult != PoolMapper.UpdateResult.InvalidParameter) + { + throw new InvalidOperationException($"{updateResult}"); + } + + return ResultCode.InvalidUpdateInfo; + } + } + + OutputHeader.MemoryPoolsSize = (uint)(Unsafe.SizeOf() * memoryPools.Length); + OutputHeader.TotalSize += OutputHeader.MemoryPoolsSize; + + return ResultCode.Success; + } + + public ResultCode UpdateVoiceChannelResources(VoiceContext context) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoiceResourcesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + for (int i = 0; i < context.GetCount(); i++) + { + VoiceChannelResourceInParameter parameter = SpanIOHelper.Read(ref _input); + + ref VoiceChannelResource resource = ref context.GetChannelResource(i); + + resource.Id = parameter.Id; + parameter.Mix.ToSpan().CopyTo(resource.Mix.ToSpan()); + resource.IsUsed = parameter.IsUsed; + } + + return ResultCode.Success; + } + + public ResultCode UpdateVoices(VoiceContext context, Memory memoryPools) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoicesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.VoicesSize).Span); + + _input = _input.Slice((int)_inputHeader.VoicesSize); + + PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + // First make everything not in use. + for (int i = 0; i < context.GetCount(); i++) + { + ref VoiceState state = ref context.GetState(i); + + state.InUse = false; + } + + // Start processing + for (int i = 0; i < context.GetCount(); i++) + { + VoiceInParameter parameter = parameters[i]; + + Memory[] voiceUpdateStates = new Memory[RendererConstants.VoiceChannelCountMax]; + + ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + if (parameter.InUse) + { + ref VoiceState currentVoiceState = ref context.GetState(i); + + for (int channelResourceIndex = 0; channelResourceIndex < parameter.ChannelCount; channelResourceIndex++) + { + int channelId = parameter.ChannelResourceIds[channelResourceIndex]; + + Debug.Assert(channelId >= 0 && channelId < context.GetCount()); + + voiceUpdateStates[channelResourceIndex] = context.GetUpdateStateForCpu(channelId); + } + + if (parameter.IsNew) + { + currentVoiceState.Initialize(); + } + + currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext); + + if (updateParameterError.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateParameterError); + } + + currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext); + + foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan()) + { + if (errorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref errorInfo); + } + } + + currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates); + } + } + + int currentOutputSize = _output.Length; + + OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.VoicesSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize); + + return ResultCode.Success; + } + + private static void ResetEffect(ref BaseEffect effect, ref EffectInParameter parameter, PoolMapper mapper) + { + effect.ForceUnmapBuffers(mapper); + + switch (parameter.Type) + { + case EffectType.Invalid: + effect = new BaseEffect(); + break; + case EffectType.BufferMix: + effect = new BufferMixEffect(); + break; + case EffectType.AuxiliaryBuffer: + effect = new AuxiliaryBufferEffect(); + break; + case EffectType.Delay: + effect = new DelayEffect(); + break; + case EffectType.Reverb: + effect = new ReverbEffect(); + break; + case EffectType.Reverb3d: + effect = new Reverb3dEffect(); + break; + case EffectType.BiquadFilter: + effect = new BiquadFilterEffect(); + break; + default: + throw new NotImplementedException($"EffectType {parameter.Type} not implemented!"); + } + } + + public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.EffectsSize).Span); + + _input = _input.Slice((int)_inputHeader.EffectsSize); + + PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + for (int i = 0; i < context.GetCount(); i++) + { + EffectInParameter parameter = parameters[i]; + + ref EffectOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + ref BaseEffect effect = ref context.GetEffect(i); + + if (!effect.IsTypeValid(ref parameter)) + { + ResetEffect(ref effect, ref parameter, mapper); + } + + effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); + + if (updateErrorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateErrorInfo); + } + + effect.StoreStatus(ref outStatus, isAudioRendererActive); + } + + int currentOutputSize = _output.Length; + + OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.EffectsSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + + return ResultCode.Success; + } + + public ResultCode UpdateSplitter(SplitterContext context) + { + if (context.Update(_input.Span, out int consumedSize)) + { + _input = _input.Slice(consumedSize); + + return ResultCode.Success; + } + else + { + return ResultCode.InvalidUpdateInfo; + } + } + + private bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan parameters) + { + uint maxMixStateCount = mixContext.GetCount(); + uint totalRequiredMixBufferCount = 0; + + for (int i = 0; i < inputMixCount; i++) + { + if (parameters[i].IsUsed) + { + if (parameters[i].DestinationMixId != RendererConstants.UnusedMixId && + parameters[i].DestinationMixId > maxMixStateCount && + parameters[i].MixId != RendererConstants.FinalMixId) + { + return true; + } + + totalRequiredMixBufferCount += parameters[i].BufferCount; + } + } + + return totalRequiredMixBufferCount > mixBufferCount; + } + + public ResultCode UpdateMixes(MixContext mixContext, uint mixBufferCount, EffectContext effectContext, SplitterContext splitterContext) + { + uint mixCount; + uint inputMixSize; + uint inputSize = 0; + + if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) + { + MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast(_input.Span)[0]; + + mixCount = parameter.MixCount; + + inputSize += (uint)Unsafe.SizeOf(); + } + else + { + mixCount = mixContext.GetCount(); + } + + inputMixSize = mixCount * (uint)Unsafe.SizeOf(); + + inputSize += inputMixSize; + + if (inputSize != _inputHeader.MixesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) + { + _input = _input.Slice(Unsafe.SizeOf()); + } + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Span.Slice(0, (int)inputMixSize)); + + _input = _input.Slice((int)inputMixSize); + + if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters)) + { + return ResultCode.InvalidUpdateInfo; + } + + bool isMixContextDirty = false; + + for (int i = 0; i < parameters.Length; i++) + { + MixParameter parameter = parameters[i]; + + int mixId = i; + + if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) + { + mixId = parameter.MixId; + } + + ref MixState mix = ref mixContext.GetState(mixId); + + if (parameter.IsUsed != mix.IsUsed) + { + mix.IsUsed = parameter.IsUsed; + + if (parameter.IsUsed) + { + mix.ClearEffectProcessingOrder(); + } + + isMixContextDirty = true; + } + + if (mix.IsUsed) + { + isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext); + } + } + + if (isMixContextDirty) + { + if (_behaviourContext.IsSplitterSupported() && splitterContext.UsingSplitter()) + { + if (!mixContext.Sort(splitterContext)) + { + return ResultCode.InvalidMixSorting; + } + } + else + { + mixContext.Sort(); + } + } + + return ResultCode.Success; + } + + private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter) + { + sink.CleanUp(); + + switch (parameter.Type) + { + case SinkType.Invalid: + sink = new BaseSink(); + break; + case SinkType.CircularBuffer: + sink = new CircularBufferSink(); + break; + case SinkType.Device: + sink = new DeviceSink(); + break; + default: + throw new NotImplementedException($"SinkType {parameter.Type} not implemented!"); + } + } + + public ResultCode UpdateSinks(SinkContext context, Memory memoryPools) + { + PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.SinksSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.SinksSize).Span); + + _input = _input.Slice((int)_inputHeader.SinksSize); + + for (int i = 0; i < context.GetCount(); i++) + { + SinkInParameter parameter = parameters[i]; + ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + ref BaseSink sink = ref context.GetSink(i); + + if (!sink.IsTypeValid(ref parameter)) + { + ResetSink(ref sink, ref parameter); + } + + sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper); + + if (updateErrorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateErrorInfo); + } + } + + int currentOutputSize = _output.Length; + + OutputHeader.SinksSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.SinksSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize); + + return ResultCode.Success; + } + + public ResultCode UpdatePerformanceBuffer(PerformanceManager manager, Span performanceOutput) + { + if (Unsafe.SizeOf() != _inputHeader.PerformanceBufferSize) + { + return ResultCode.InvalidUpdateInfo; + } + + PerformanceInParameter parameter = SpanIOHelper.Read(ref _input); + + ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + if (manager != null) + { + outStatus.HistorySize = manager.CopyHistories(performanceOutput); + + manager.SetTargetNodeId(parameter.TargetNodeId); + } + else + { + outStatus.HistorySize = 0; + } + + OutputHeader.PerformanceBufferSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.PerformanceBufferSize; + + return ResultCode.Success; + } + + public ResultCode UpdateErrorInfo() + { + ref BehaviourErrorInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + _behaviourContext.CopyErrorInfo(outStatus.ErrorInfos.ToSpan(), out outStatus.ErrorInfosCount); + + OutputHeader.BehaviourSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.BehaviourSize; + + return ResultCode.Success; + } + + public ResultCode UpdateRendererInfo(ulong elapsedFrameCount) + { + ref RendererInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + outStatus.ElapsedFrameCount = elapsedFrameCount; + + OutputHeader.RenderInfoSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.RenderInfoSize; + + return ResultCode.Success; + } + + public ResultCode CheckConsumedSize() + { + int consumedInputSize = _inputOrigin.Length - _input.Length; + int consumedOutputSize = _outputOrigin.Length - _output.Length; + + if (consumedInputSize != _inputHeader.TotalSize) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed input size mismatch (got {consumedInputSize} expected {_inputHeader.TotalSize})"); + + return ResultCode.InvalidUpdateInfo; + } + + if (consumedOutputSize != OutputHeader.TotalSize) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed output size mismatch (got {consumedOutputSize} expected {OutputHeader.TotalSize})"); + + return ResultCode.InvalidUpdateInfo; + } + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Types/AudioRendererExecutionMode.cs b/Ryujinx.Audio.Renderer/Server/Types/AudioRendererExecutionMode.cs new file mode 100644 index 00000000..6a386bd4 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Types/AudioRendererExecutionMode.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The execution mode of an . + /// + public enum AudioRendererExecutionMode : byte + { + /// + /// Automatically send commands to the DSP at a fixed rate (see + /// + Auto, + + /// + /// Audio renderer operation needs to be done manually via ExecuteAudioRenderer. + /// + /// This is not supported on the DSP and is as such stubbed. + Manual + } +} \ No newline at end of file diff --git a/Ryujinx.Audio.Renderer/Server/Types/AudioRendererRenderingDevice.cs b/Ryujinx.Audio.Renderer/Server/Types/AudioRendererRenderingDevice.cs new file mode 100644 index 00000000..c317ba94 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Types/AudioRendererRenderingDevice.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The rendering device of an . + /// + public enum AudioRendererRenderingDevice : byte + { + /// + /// Rendering is performed on the DSP. + /// + /// + /// Only supports . + /// + Dsp, + + /// + /// Rendering is performed on the CPU. + /// + /// + /// Only supports . + /// + Cpu + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Types/PlayState.cs b/Ryujinx.Audio.Renderer/Server/Types/PlayState.cs new file mode 100644 index 00000000..4f224f75 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Types/PlayState.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The internal play state of a + /// + public enum PlayState + { + /// + /// The voice has been started and is playing. + /// + Started, + + /// + /// The voice has been stopped. + /// + /// + /// This cannot be directly set by user. + /// See for correct usage. + /// + Stopped, + + /// + /// The user asked the voice to be stopped. + /// + /// + /// This is changed to the state after command generation. + /// + /// + Stopping, + + /// + /// The voice has been paused by user request. + /// + /// + /// The user can resume to the state. + /// + Paused + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerManager.cs b/Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerManager.cs new file mode 100644 index 00000000..26218932 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerManager.cs @@ -0,0 +1,101 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + /// + /// Upsampler manager. + /// + public class UpsamplerManager + { + /// + /// Work buffer for upsampler. + /// + private Memory _upSamplerWorkBuffer; + + /// + /// Global lock of the object. + /// + private object Lock = new object(); + + /// + /// The upsamplers instances. + /// + private UpsamplerState[] _upsamplers; + + /// + /// The count of upsamplers. + /// + private uint _count; + + /// + /// Create a new . + /// + /// Work buffer for upsampler. + /// The count of upsamplers. + public UpsamplerManager(Memory upSamplerWorkBuffer, uint count) + { + _upSamplerWorkBuffer = upSamplerWorkBuffer; + _count = count; + + _upsamplers = new UpsamplerState[_count]; + } + + /// + /// Allocate a new . + /// + /// A new or null if out of memory. + public UpsamplerState Allocate() + { + int workBufferOffset = 0; + + lock (Lock) + { + for (int i = 0; i < _count; i++) + { + if (_upsamplers[i] == null) + { + _upsamplers[i] = new UpsamplerState(this, i, _upSamplerWorkBuffer.Slice(workBufferOffset, RendererConstants.UpSampleEntrySize), RendererConstants.TargetSampleCount); + + return _upsamplers[i]; + } + + workBufferOffset += RendererConstants.UpSampleEntrySize; + } + } + + return null; + } + + /// + /// Free a at the given index. + /// + /// The index of the to free. + public void Free(int index) + { + lock (Lock) + { + Debug.Assert(_upsamplers[index] != null); + + _upsamplers[index] = null; + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerState.cs b/Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerState.cs new file mode 100644 index 00000000..19ba7626 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerState.cs @@ -0,0 +1,80 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + /// + /// Server state for a upsampling. + /// + public class UpsamplerState + { + /// + /// The output buffer containing the target samples. + /// + public Memory OutputBuffer { get; } + + /// + /// The target sample count. + /// + public uint SampleCount { get; } + + /// + /// The index of the . (used to free it) + /// + private int _index; + + /// + /// The . + /// + private UpsamplerManager _manager; + + /// + /// The source sample count. + /// + public uint SourceSampleCount; + + /// + /// The input buffer indices of the buffers holding the samples that need upsampling. + /// + public ushort[] InputBufferIndices; + + /// + /// Create a new . + /// + /// The upsampler manager. + /// The index of the . (used to free it) + /// The output buffer used to contain the target samples. + /// The target sample count. + public UpsamplerState(UpsamplerManager manager, int index, Memory outputBuffer, uint sampleCount) + { + _manager = manager; + _index = index; + OutputBuffer = outputBuffer; + SampleCount = sampleCount; + } + + /// + /// Release the . + /// + public void Release() + { + _manager.Free(_index); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Voice/VoiceChannelResource.cs b/Ryujinx.Audio.Renderer/Server/Voice/VoiceChannelResource.cs new file mode 100644 index 00000000..6e6e7e76 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Voice/VoiceChannelResource.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// Server state for a voice channel resource. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xD0, Pack = Alignment)] + public struct VoiceChannelResource + { + public const int Alignment = 0x10; + + /// + /// Mix volumes for the resource. + /// + public Array24 Mix; + + /// + /// Previous mix volumes for resource. + /// + public Array24 PreviousMix; + + /// + /// The id of the resource. + /// + public uint Id; + + /// + /// Indicate if the resource is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + public void UpdateState() + { + Mix.ToSpan().CopyTo(PreviousMix.ToSpan()); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Voice/VoiceContext.cs b/Ryujinx.Audio.Renderer/Server/Voice/VoiceContext.cs new file mode 100644 index 00000000..694bf1ae --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Voice/VoiceContext.cs @@ -0,0 +1,166 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// Voice context. + /// + public class VoiceContext + { + /// + /// Storage of the sorted indices to . + /// + private Memory _sortedVoices; + + /// + /// Storage for . + /// + private Memory _voices; + + /// + /// Storage for . + /// + private Memory _voiceChannelResources; + + /// + /// Storage for that are used during audio renderer server updates. + /// + private Memory _voiceUpdateStatesCpu; + + /// + /// Storage for for the . + /// + private Memory _voiceUpdateStatesDsp; + + /// + /// The total voice count. + /// + private uint _voiceCount; + + public void Initialize(Memory sortedVoices, Memory voices, Memory voiceChannelResources, Memory voiceUpdateStatesCpu, Memory voiceUpdateStatesDsp, uint voiceCount) + { + _sortedVoices = sortedVoices; + _voices = voices; + _voiceChannelResources = voiceChannelResources; + _voiceUpdateStatesCpu = voiceUpdateStatesCpu; + _voiceUpdateStatesDsp = voiceUpdateStatesDsp; + _voiceCount = voiceCount; + } + + /// + /// Get the total voice count. + /// + /// The total voice count. + public uint GetCount() + { + return _voiceCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref VoiceChannelResource GetChannelResource(int id) + { + return ref SpanIOHelper.GetFromMemory(_voiceChannelResources, id, _voiceCount); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + /// The returned should only be used when updating the server state. + public Memory GetUpdateStateForCpu(int id) + { + return SpanIOHelper.GetMemory(_voiceUpdateStatesCpu, id, _voiceCount); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + /// The returned should only be used in the context of processing on the . + public Memory GetUpdateStateForDsp(int id) + { + return SpanIOHelper.GetMemory(_voiceUpdateStatesDsp, id, _voiceCount); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref VoiceState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_voices, id, _voiceCount); + } + + public ref VoiceState GetSortedState(int id) + { + Debug.Assert(id >= 0 && id < _voiceCount); + + return ref GetState(_sortedVoices.Span[id]); + } + + /// + /// Update internal state during command generation. + /// + public void UpdateForCommandGeneration() + { + _voiceUpdateStatesDsp.CopyTo(_voiceUpdateStatesCpu); + } + + /// + /// Sort the internal voices by priority and sorting order (if the priorities match). + /// + public void Sort() + { + for (int i = 0; i < _voiceCount; i++) + { + _sortedVoices.Span[i] = i; + } + + int[] sortedVoicesTemp = _sortedVoices.Slice(0, (int)GetCount()).ToArray(); + + Array.Sort(sortedVoicesTemp, (a, b) => + { + ref VoiceState aState = ref GetState(a); + ref VoiceState bState = ref GetState(b); + + int result = aState.Priority.CompareTo(bState.Priority); + + if (result == 0) + { + return aState.SortingOrder.CompareTo(bState.SortingOrder); + } + + return result; + }); + + sortedVoicesTemp.AsSpan().CopyTo(_sortedVoices.Span); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs b/Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs new file mode 100644 index 00000000..94e39d0f --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs @@ -0,0 +1,715 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + [StructLayout(LayoutKind.Sequential, Pack = Alignment)] + public struct VoiceState + { + public const int Alignment = 0x10; + + /// + /// Set to true if the voice is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool InUse; + + /// + /// Set to true if the voice is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + [MarshalAs(UnmanagedType.I1)] + public bool WasPlaying; + + /// + /// The of the voice. + /// + public SampleFormat SampleFormat; + + /// + /// The sample rate of the voice. + /// + public uint SampleRate; + + /// + /// The total channel count used. + /// + public uint ChannelsCount; + + /// + /// Id of the voice. + /// + public int Id; + + /// + /// Node id of the voice. + /// + public int NodeId; + + /// + /// The target mix id of the voice. + /// + public int MixId; + + /// + /// The current voice . + /// + public Types.PlayState PlayState; + + /// + /// The previous voice . + /// + public Types.PlayState PreviousPlayState; + + /// + /// The priority of the voice. + /// + public uint Priority; + + /// + /// Target sorting position of the voice. (used to sort voice with the same ) + /// + public uint SortingOrder; + + /// + /// The pitch used on the voice. + /// + public float Pitch; + + /// + /// The output volume of the voice. + /// + public float Volume; + + /// + /// The previous output volume of the voice. + /// + public float PreviousVolume; + + /// + /// Biquad filters to apply to the output of the voice. + /// + public Array2 BiquadFilters; + + /// + /// Total count of of the voice. + /// + public uint WaveBuffersCount; + + /// + /// Current playing of the voice. + /// + public uint WaveBuffersIndex; + + /// + /// Change the behaviour of the voice. + /// + /// This was added on REV5. + public DecodingBehaviour DecodingBehaviour; + + /// + /// User state required by the data source. + /// + /// Only used for as the GC-ADPCM coefficients. + public AddressInfo DataSourceStateAddressInfo; + + /// + /// The wavebuffers of this voice. + /// + public Array4 WaveBuffers; + + /// + /// The channel resource ids associated to the voice. + /// + public Array6 ChannelResourceIds; + + /// + /// The target splitter id of the voice. + /// + public uint SplitterId; + + /// + /// Change the Sample Rate Conversion (SRC) quality of the voice. + /// + /// This was added on REV8. + public SampleRateConversionQuality SrcQuality; + + /// + /// If set to true, the voice was dropped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropFlag; + + /// + /// Set to true if the data source state work buffer wasn't mapped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool DataSourceStateUnmapped; + + /// + /// Set to true if any of the work buffer wasn't mapped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool BufferInfoUnmapped; + + /// + /// The biquad filter initialization state storage. + /// + private BiquadFilterNeedInitializationArrayStruct _biquadFilterNeedInitialization; + + /// + /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played. + /// + /// This was added on REV5. + public byte FlushWaveBufferCount; + + [StructLayout(LayoutKind.Sequential, Size = RendererConstants.VoiceBiquadFilterCount)] + private struct BiquadFilterNeedInitializationArrayStruct { } + + /// + /// The biquad filter initialization state array. + /// + public Span BiquadFilterNeedInitialization => SpanHelpers.AsSpan(ref _biquadFilterNeedInitialization); + + /// + /// Initialize the . + /// + public void Initialize() + { + IsNew = false; + VoiceDropFlag = false; + DataSourceStateUnmapped = false; + BufferInfoUnmapped = false; + FlushWaveBufferCount = 0; + PlayState = Types.PlayState.Stopped; + Priority = RendererConstants.VoiceLowestPriority; + Id = 0; + NodeId = 0; + SampleRate = 0; + SampleFormat = SampleFormat.Invalid; + ChannelsCount = 0; + Pitch = 0.0f; + Volume= 0.0f; + PreviousVolume = 0.0f; + BiquadFilters.ToSpan().Fill(new BiquadFilterParameter()); + WaveBuffersCount = 0; + WaveBuffersIndex = 0; + MixId = RendererConstants.UnusedMixId; + SplitterId = RendererConstants.UnusedSplitterId; + DataSourceStateAddressInfo.Setup(0, 0); + + InitializeWaveBuffers(); + } + + /// + /// Initialize the in this . + /// + private void InitializeWaveBuffers() + { + for (int i = 0; i < WaveBuffers.Length; i++) + { + WaveBuffers[i].StartSampleOffset = 0; + WaveBuffers[i].EndSampleOffset = 0; + WaveBuffers[i].ShouldLoop = false; + WaveBuffers[i].IsEndOfStream = false; + WaveBuffers[i].BufferAddressInfo.Setup(0, 0); + WaveBuffers[i].ContextAddressInfo.Setup(0, 0); + WaveBuffers[i].IsSendToAudioProcessor = true; + } + } + + /// + /// Check if the voice needs to be skipped. + /// + /// Returns true if the voice needs to be skipped. + public bool ShouldSkip() + { + return !InUse || WaveBuffersCount == 0 || DataSourceStateUnmapped || BufferInfoUnmapped || VoiceDropFlag; + } + + /// + /// Return true if the mix has any destinations. + /// + /// True if the mix has any destinations. + public bool HasAnyDestination() + { + return MixId != RendererConstants.UnusedMixId || SplitterId != RendererConstants.UnusedSplitterId; + } + + /// + /// Indicate if the server voice information needs to be updated. + /// + /// The user parameter. + /// Return true, if the server voice information needs to be updated. + private bool ShouldUpdateParameters(ref VoiceInParameter parameter) + { + if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress) + { + return DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize; + } + + return DataSourceStateAddressInfo.CpuAddress != parameter.DataSourceStateAddress || + DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize || + DataSourceStateUnmapped; + } + + /// + /// Update the internal state from a user parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + /// The behaviour context. + public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext) + { + InUse = parameter.InUse; + Id = parameter.Id; + NodeId = parameter.NodeId; + + UpdatePlayState(parameter.PlayState); + + SrcQuality = parameter.SrcQuality; + + Priority = parameter.Priority; + SortingOrder = parameter.SortingOrder; + SampleRate = parameter.SampleRate; + SampleFormat = parameter.SampleFormat; + ChannelsCount = parameter.ChannelCount; + Pitch = parameter.Pitch; + Volume = parameter.Volume; + parameter.BiquadFilters.ToSpan().CopyTo(BiquadFilters.ToSpan()); + WaveBuffersCount = parameter.WaveBuffersCount; + WaveBuffersIndex = parameter.WaveBuffersIndex; + + if (behaviourContext.IsFlushVoiceWaveBuffersSupported()) + { + FlushWaveBufferCount += parameter.FlushWaveBufferCount; + } + + MixId = parameter.MixId; + + if (behaviourContext.IsSplitterSupported()) + { + SplitterId = parameter.SplitterId; + } + else + { + SplitterId = RendererConstants.UnusedSplitterId; + } + + parameter.ChannelResourceIds.ToSpan().CopyTo(ChannelResourceIds.ToSpan()); + + DecodingBehaviour behaviour = DecodingBehaviour.Default; + + if (behaviourContext.IsDecodingBehaviourFlagSupported()) + { + behaviour = parameter.DecodingBehaviourFlags; + } + + DecodingBehaviour = behaviour; + + if (parameter.ResetVoiceDropFlag) + { + VoiceDropFlag = false; + } + + if (ShouldUpdateParameters(ref parameter)) + { + DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize); + } + else + { + outErrorInfo = new ErrorInfo(); + } + } + + /// + /// Update the internal play state from user play state. + /// + /// The target user play state. + public void UpdatePlayState(PlayState userPlayState) + { + Types.PlayState oldServerPlayState = PlayState; + + PreviousPlayState = oldServerPlayState; + + Types.PlayState newServerPlayState; + + switch (userPlayState) + { + case Common.PlayState.Start: + newServerPlayState = Types.PlayState.Started; + break; + + case Common.PlayState.Stop: + if (oldServerPlayState == Types.PlayState.Stopped) + { + return; + } + + newServerPlayState = Types.PlayState.Stopping; + break; + + case Common.PlayState.Pause: + newServerPlayState = Types.PlayState.Paused; + break; + + default: + throw new NotImplementedException($"Unhandled PlayState.{userPlayState}"); + } + + PlayState = newServerPlayState; + } + + /// + /// Write the status of the voice to the given user output. + /// + /// The given user output. + /// The user parameter. + /// The voice states associated to the . + public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, Memory[] voiceUpdateStates) + { +#if DEBUG + // Sanity check in debug mode of the internal state + if (!parameter.IsNew && !IsNew) + { + for (int i = 1; i < ChannelsCount; i++) + { + ref VoiceUpdateState stateA = ref voiceUpdateStates[i - 1].Span[0]; + ref VoiceUpdateState stateB = ref voiceUpdateStates[i].Span[0]; + + Debug.Assert(stateA.WaveBufferConsumed == stateB.WaveBufferConsumed); + Debug.Assert(stateA.PlayedSampleCount == stateB.PlayedSampleCount); + Debug.Assert(stateA.Offset == stateB.Offset); + Debug.Assert(stateA.WaveBufferIndex == stateB.WaveBufferIndex); + Debug.Assert(stateA.Fraction == stateB.Fraction); + Debug.Assert(stateA.IsWaveBufferValid.SequenceEqual(stateB.IsWaveBufferValid)); + } + } +#endif + if (parameter.IsNew || IsNew) + { + IsNew = true; + + outStatus.VoiceDropFlag = false; + outStatus.PlayedWaveBuffersCount = 0; + outStatus.PlayedSampleCount = 0; + } + else + { + ref VoiceUpdateState state = ref voiceUpdateStates[0].Span[0]; + + outStatus.VoiceDropFlag = VoiceDropFlag; + outStatus.PlayedWaveBuffersCount = state.WaveBufferConsumed; + outStatus.PlayedSampleCount = state.PlayedSampleCount; + } + } + + /// + /// Update the internal state of all the of the . + /// + /// An array of used to report errors when mapping any of the . + /// The user parameter. + /// The voice states associated to the . + /// The mapper to use. + /// The behaviour context. + public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, Memory[] voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + { + errorInfos = new ErrorInfo[RendererConstants.VoiceWaveBufferCount * 2]; + + if (parameter.IsNew) + { + InitializeWaveBuffers(); + + for (int i = 0; i < parameter.ChannelCount; i++) + { + voiceUpdateStates[i].Span[0].IsWaveBufferValid.Fill(false); + } + } + + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[0].Span[0]; + + for (int i = 0; i < RendererConstants.VoiceWaveBufferCount; i++) + { + UpdateWaveBuffer(errorInfos.AsSpan().Slice(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext); + } + } + + /// + /// Update the internal state of one of the of the . + /// + /// A used to report errors when mapping the . + /// The to update. + /// The from the user input. + /// The from the user input. + /// If set to true, the server side wavebuffer is considered valid. + /// The mapper to use. + /// The behaviour context. + private void UpdateWaveBuffer(Span errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + { + if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0) + { + mapper.ForceUnmap(ref waveBuffer.BufferAddressInfo); + waveBuffer.BufferAddressInfo.Setup(0, 0); + } + + if (!inputWaveBuffer.SentToServer || BufferInfoUnmapped) + { + if (inputWaveBuffer.IsSampleOffsetValid(sampleFormat)) + { + Debug.Assert(waveBuffer.IsSendToAudioProcessor); + + waveBuffer.IsSendToAudioProcessor = false; + waveBuffer.StartSampleOffset = inputWaveBuffer.StartSampleOffset; + waveBuffer.EndSampleOffset = inputWaveBuffer.EndSampleOffset; + waveBuffer.ShouldLoop = inputWaveBuffer.ShouldLoop; + waveBuffer.IsEndOfStream = inputWaveBuffer.IsEndOfStream; + waveBuffer.LoopStartSampleOffset = inputWaveBuffer.LoopFirstSampleOffset; + waveBuffer.LoopEndSampleOffset = inputWaveBuffer.LoopLastSampleOffset; + waveBuffer.LoopCount = inputWaveBuffer.LoopCount; + + BufferInfoUnmapped = !mapper.TryAttachBuffer(out ErrorInfo bufferInfoError, ref waveBuffer.BufferAddressInfo, inputWaveBuffer.Address, inputWaveBuffer.Size); + + errorInfos[0] = bufferInfoError; + + if (sampleFormat == SampleFormat.Adpcm && behaviourContext.IsAdpcmLoopContextBugFixed() && inputWaveBuffer.ContextAddress != 0) + { + bool adpcmLoopContextMapped = mapper.TryAttachBuffer(out ErrorInfo adpcmLoopContextInfoError, + ref waveBuffer.ContextAddressInfo, + inputWaveBuffer.ContextAddress, + inputWaveBuffer.ContextSize); + + errorInfos[1] = adpcmLoopContextInfoError; + + if (adpcmLoopContextMapped) + { + BufferInfoUnmapped = DataSourceStateUnmapped; + } + else + { + BufferInfoUnmapped = true; + } + } + else + { + waveBuffer.ContextAddressInfo.Setup(0, 0); + } + } + else + { + errorInfos[0].ErrorCode = ResultCode.InvalidAddressInfo; + errorInfos[0].ExtraErrorInfo = inputWaveBuffer.Address; + } + } + } + + /// + /// Reset the resources associated to this . + /// + /// The voice context. + private void ResetResources(VoiceContext context) + { + for (int i = 0; i < ChannelsCount; i++) + { + int channelResourceId = ChannelResourceIds[i]; + + ref VoiceChannelResource voiceChannelResource = ref context.GetChannelResource(channelResourceId); + + Debug.Assert(voiceChannelResource.IsUsed); + + Memory dspSharedState = context.GetUpdateStateForDsp(channelResourceId); + + MemoryMarshal.Cast(dspSharedState.Span).Fill(0); + + voiceChannelResource.UpdateState(); + } + } + + /// + /// Flush a certain amount of . + /// + /// The amount of wavebuffer to flush. + /// The voice states associated to the . + /// The channel count from user input. + private void FlushWaveBuffers(uint waveBufferCount, Memory[] voiceUpdateStates, uint channelCount) + { + uint waveBufferIndex = WaveBuffersIndex; + + for (int i = 0; i < waveBufferCount; i++) + { + WaveBuffers[(int)waveBufferIndex].IsSendToAudioProcessor = true; + + for (int j = 0; j < channelCount; j++) + { + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0]; + + voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % RendererConstants.VoiceWaveBufferCount; + voiceUpdateState.WaveBufferConsumed++; + voiceUpdateState.IsWaveBufferValid[(int)waveBufferIndex] = false; + } + + waveBufferIndex = (waveBufferIndex + 1) % RendererConstants.VoiceWaveBufferCount; + } + } + + /// + /// Update the internal parameters for command generation. + /// + /// The voice states associated to the . + /// Return true if this voice should be played. + public bool UpdateParametersForCommandGeneration(Memory[] voiceUpdateStates) + { + if (FlushWaveBufferCount != 0) + { + FlushWaveBuffers(FlushWaveBufferCount, voiceUpdateStates, ChannelsCount); + + FlushWaveBufferCount = 0; + } + + switch (PlayState) + { + case Types.PlayState.Started: + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref WaveBuffer wavebuffer = ref WaveBuffers[i]; + + if (!wavebuffer.IsSendToAudioProcessor) + { + for (int y = 0; y < ChannelsCount; y++) + { + Debug.Assert(!voiceUpdateStates[y].Span[0].IsWaveBufferValid[i]); + + voiceUpdateStates[y].Span[0].IsWaveBufferValid[i] = true; + } + + wavebuffer.IsSendToAudioProcessor = true; + } + } + + WasPlaying = false; + + ref VoiceUpdateState primaryVoiceUpdateState = ref voiceUpdateStates[0].Span[0]; + + for (int i = 0; i < primaryVoiceUpdateState.IsWaveBufferValid.Length; i++) + { + if (primaryVoiceUpdateState.IsWaveBufferValid[i]) + { + return true; + } + } + + return false; + + case Types.PlayState.Stopping: + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref WaveBuffer wavebuffer = ref WaveBuffers[i]; + + wavebuffer.IsSendToAudioProcessor = true; + + for (int j = 0; j < ChannelsCount; j++) + { + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0]; + + if (voiceUpdateState.IsWaveBufferValid[i]) + { + voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % RendererConstants.VoiceWaveBufferCount; + voiceUpdateState.WaveBufferConsumed++; + } + + voiceUpdateState.IsWaveBufferValid[i] = false; + } + } + + for (int i = 0; i < ChannelsCount; i++) + { + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[i].Span[0]; + + voiceUpdateState.Offset = 0; + voiceUpdateState.PlayedSampleCount = 0; + voiceUpdateState.Pitch.ToSpan().Fill(0); + voiceUpdateState.Fraction = 0; + voiceUpdateState.LoopContext = new Dsp.State.AdpcmLoopContext(); + } + + PlayState = Types.PlayState.Stopped; + WasPlaying = PreviousPlayState == Types.PlayState.Started; + + return WasPlaying; + + case Types.PlayState.Stopped: + case Types.PlayState.Paused: + foreach (ref WaveBuffer wavebuffer in WaveBuffers.ToSpan()) + { + wavebuffer.BufferAddressInfo.GetReference(true); + wavebuffer.ContextAddressInfo.GetReference(true); + } + + if (SampleFormat == SampleFormat.Adpcm) + { + if (DataSourceStateAddressInfo.CpuAddress != 0) + { + DataSourceStateAddressInfo.GetReference(true); + } + } + + WasPlaying = PreviousPlayState == Types.PlayState.Started; + + return WasPlaying; + default: + throw new NotImplementedException($"{PlayState}"); + } + } + + /// + /// Update the internal state for command generation. + /// + /// The voice context. + /// Return true if this voice should be played. + public bool UpdateForCommandGeneration(VoiceContext context) + { + if (IsNew) + { + ResetResources(context); + PreviousVolume = Volume; + IsNew = false; + } + + Memory[] voiceUpdateStates = new Memory[RendererConstants.VoiceChannelCountMax]; + + for (int i = 0; i < ChannelsCount; i++) + { + voiceUpdateStates[i] = context.GetUpdateStateForDsp(ChannelResourceIds[i]); + } + + return UpdateParametersForCommandGeneration(voiceUpdateStates); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Voice/WaveBuffer.cs b/Ryujinx.Audio.Renderer/Server/Voice/WaveBuffer.cs new file mode 100644 index 00000000..e709639b --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Voice/WaveBuffer.cs @@ -0,0 +1,121 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// A wavebuffer used for server update. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 1)] + public struct WaveBuffer + { + /// + /// The of the sample data of the wavebuffer. + /// + public AddressInfo BufferAddressInfo; + + /// + /// The of the context of the wavebuffer. + /// + /// Only used by . + public AddressInfo ContextAddressInfo; + + + /// + /// First sample to play of the wavebuffer. + /// + public uint StartSampleOffset; + + /// + /// Last sample to play of the wavebuffer. + /// + public uint EndSampleOffset; + + /// + /// Set to true if the wavebuffer is looping. + /// + [MarshalAs(UnmanagedType.I1)] + public bool ShouldLoop; + + /// + /// Set to true if the wavebuffer is the end of stream. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Set to true if the wavebuffer wasn't sent to the . + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsSendToAudioProcessor; + + /// + /// First sample to play when looping the wavebuffer. + /// + public uint LoopStartSampleOffset; + + /// + /// Last sample to play when looping the wavebuffer. + /// + public uint LoopEndSampleOffset; + + /// + /// The max loop count. + /// + public int LoopCount; + + /// + /// Create a new for use by the . + /// + /// The target version of the wavebuffer. + /// A new for use by the . + public Common.WaveBuffer ToCommon(int version) + { + Common.WaveBuffer waveBuffer = new Common.WaveBuffer(); + + waveBuffer.Buffer = BufferAddressInfo.GetReference(true); + waveBuffer.BufferSize = (uint)BufferAddressInfo.Size; + + if (ContextAddressInfo.CpuAddress != 0) + { + waveBuffer.Context = ContextAddressInfo.GetReference(true); + waveBuffer.ContextSize = (uint)ContextAddressInfo.Size; + } + + waveBuffer.StartSampleOffset = StartSampleOffset; + waveBuffer.EndSampleOffset = EndSampleOffset; + waveBuffer.Looping = ShouldLoop; + waveBuffer.IsEndOfStream = IsEndOfStream; + + if (version == 2) + { + waveBuffer.LoopCount = LoopCount; + waveBuffer.LoopStartSampleOffset = LoopStartSampleOffset; + waveBuffer.LoopEndSampleOffset = LoopEndSampleOffset; + } + else + { + waveBuffer.LoopCount = -1; + } + + return waveBuffer; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Utils/AudioProcessorMemoryManager.cs b/Ryujinx.Audio.Renderer/Utils/AudioProcessorMemoryManager.cs new file mode 100644 index 00000000..4a0e51c8 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Utils/AudioProcessorMemoryManager.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.CompilerServices; + +using DspAddress = System.UInt64; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// The memory management + /// + /// This is stub for the most part but is kept to permit LLE if wanted. + static class AudioProcessorMemoryManager + { + /// + /// Map the given to the address space. + /// + /// The process owning the CPU memory. + /// The to map. + /// The size of the CPU memory region to map. + /// The address on the address space. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DspAddress Map(uint processHandle, CpuAddress cpuAddress, ulong size) + { + return cpuAddress; + } + + /// + /// Unmap the given from the address space. + /// + /// The process owning the CPU memory. + /// The to unmap. + /// The size of the CPU memory region to unmap. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Unmap(uint processHandle, CpuAddress cpuAddress, ulong size) + { + } + + /// + /// Invalidate the data cache at the given address. + /// + /// The base DSP address to invalidate + /// The size of the DSP memory region to invalidate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidateDspCache(DspAddress address, ulong size) + { + } + + /// + /// Invalidate the CPU data cache at the given address. + /// + /// The base to invalidate + /// The size of the CPU memory region to invalidate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidateDataCache(CpuAddress address, ulong size) + { + } + } +} diff --git a/Ryujinx.Audio.Renderer/Utils/BitArray.cs b/Ryujinx.Audio.Renderer/Utils/BitArray.cs new file mode 100644 index 00000000..743ece24 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Utils/BitArray.cs @@ -0,0 +1,120 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A simple bit array implementation backed by a . + /// + public class BitArray + { + /// + /// The backing storage of the . + /// + private Memory _storage; + + /// + /// Create a new from . + /// + /// The backing storage of the . + public BitArray(Memory storage) + { + _storage = storage; + } + + /// + /// Get the byte position of a given bit index. + /// + /// A bit index. + /// The byte position of a given bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ToPosition(int index) => index / 8; + + /// + /// Get the bit position of a given bit index inside a byte. + /// + /// A bit index. + /// The bit position of a given bit index inside a byte. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ToBitPosition(int index) => index % 8; + + /// + /// Test if the bit at the given index is set. + /// + /// A bit index. + /// Return true if the bit at the given index is set + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Test(int index) + { + ulong mask = 1ul << ToBitPosition(index); + + return (_storage.Span[ToPosition(index)] & mask) == mask; + } + + /// + /// Set the bit at the given index. + /// + /// A bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(int index) + { + Set(index, true); + } + + /// + /// Reset the bit at the given index. + /// + /// A bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset(int index) + { + Set(index, false); + } + + /// + /// Set a bit value at the given index. + /// + /// A bit index. + /// The new bit value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Set(int index, bool value) + { + byte mask = (byte)(1 << ToBitPosition(index)); + + if (value) + { + _storage.Span[ToPosition(index)] |= mask; + } + else + { + _storage.Span[ToPosition(index)] &= (byte)~mask; + } + } + + /// + /// Reset all bits in the storage. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + _storage.Span.Fill(0); + } + } +} diff --git a/Ryujinx.Audio.Renderer/Utils/FileHardwareDevice.cs b/Ryujinx.Audio.Renderer/Utils/FileHardwareDevice.cs new file mode 100644 index 00000000..38e72c7a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Utils/FileHardwareDevice.cs @@ -0,0 +1,105 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Integration; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A that outputs to a wav file. + /// + public class FileHardwareDevice : HardwareDevice + { + private FileStream _stream; + private uint _channelCount; + private uint _sampleRate; + + private const int HeaderSize = 44; + + public FileHardwareDevice(string path, uint channelCount, uint sampleRate) + { + _stream = File.OpenWrite(path); + _channelCount = channelCount; + _sampleRate = sampleRate; + + _stream.Seek(HeaderSize, SeekOrigin.Begin); + } + + private void UpdateHeader() + { + var writer = new BinaryWriter(_stream); + + long currentPos = writer.Seek(0, SeekOrigin.Current); + + writer.Seek(0, SeekOrigin.Begin); + + writer.Write(Encoding.ASCII.GetBytes("RIFF")); + writer.Write((int)(writer.BaseStream.Length - 8)); + writer.Write(Encoding.ASCII.GetBytes("WAVE")); + writer.Write(Encoding.ASCII.GetBytes("fmt ")); + writer.Write(16); + writer.Write((short)1); + writer.Write((short)GetChannelCount()); + writer.Write(GetSampleRate()); + writer.Write(GetSampleRate() * GetChannelCount() * sizeof(short)); + writer.Write((short)(GetChannelCount() * sizeof(short))); + writer.Write((short)(sizeof(short) * 8)); + writer.Write(Encoding.ASCII.GetBytes("data")); + writer.Write((int)(writer.BaseStream.Length - HeaderSize)); + + writer.Seek((int)currentPos, SeekOrigin.Begin); + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + _stream.Write(MemoryMarshal.Cast(data)); + + UpdateHeader(); + _stream.Flush(); + } + + public uint GetChannelCount() + { + return _channelCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _stream?.Flush(); + _stream?.Dispose(); + + _stream = null; + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Utils/Mailbox.cs b/Ryujinx.Audio.Renderer/Utils/Mailbox.cs new file mode 100644 index 00000000..1084bc60 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Utils/Mailbox.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A simple generic message queue for unmanaged types. + /// + /// The target unmanaged type used + public class Mailbox : IDisposable where T : unmanaged + { + private BlockingCollection _messageQueue; + private BlockingCollection _responseQueue; + + public Mailbox() + { + _messageQueue = new BlockingCollection(1); + _responseQueue = new BlockingCollection(1); + } + + public void SendMessage(T data) + { + _messageQueue.Add(data); + } + + public void SendResponse(T data) + { + _responseQueue.Add(data); + } + + public T ReceiveMessage() + { + return _messageQueue.Take(); + } + + public T ReceiveResponse() + { + return _responseQueue.Take(); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _messageQueue.Dispose(); + _responseQueue.Dispose(); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Utils/SpanIOHelper.cs b/Ryujinx.Audio.Renderer/Utils/SpanIOHelper.cs new file mode 100644 index 00000000..214927bc --- /dev/null +++ b/Ryujinx.Audio.Renderer/Utils/SpanIOHelper.cs @@ -0,0 +1,188 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// Helper for IO operations on and . + /// + public static class SpanIOHelper + { + /// + /// Write the given data to the given backing and move cursor after the written data. + /// + /// The data type. + /// The backing to store the data. + /// The data to write to the backing . + public static void Write(ref Memory backingMemory, ref T data) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + MemoryMarshal.Write(backingMemory.Span.Slice(0, size), ref data); + + backingMemory = backingMemory.Slice(size); + } + + /// + /// Write the given data to the given backing and move cursor after the written data. + /// + /// The data type. + /// The backing to store the data. + /// The data to write to the backing . + public static void Write(ref Span backingMemory, ref T data) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + MemoryMarshal.Write(backingMemory.Slice(0, size), ref data); + + backingMemory = backingMemory.Slice(size); + } + + /// + /// Get a out of a and move cursor after T size. + /// + /// The data type. + /// The backing to get a from. + /// A from backing . + public static Span GetWriteRef(ref Memory backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + Span result = MemoryMarshal.Cast(backingMemory.Span.Slice(0, size)); + + backingMemory = backingMemory.Slice(size); + + return result; + } + + /// + /// Get a out of a backingMemory and move cursor after T size. + /// + /// The data type. + /// The backing to get a from. + /// A from backing . + public static Span GetWriteRef(ref Span backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + Span result = MemoryMarshal.Cast(backingMemory.Slice(0, size)); + + backingMemory = backingMemory.Slice(size); + + return result; + } + + /// + /// Read data from the given backing and move cursor after the read data. + /// + /// The data type. + /// The backing to read data from. + /// Return the read data. + public static T Read(ref ReadOnlyMemory backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + T result = MemoryMarshal.Read(backingMemory.Span.Slice(0, size)); + + backingMemory = backingMemory.Slice(size); + + return result; + } + + /// + /// Read data from the given backing and move cursor after the read data. + /// + /// The data type. + /// The backing to read data from. + /// Return the read data. + public static T Read(ref ReadOnlySpan backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + T result = MemoryMarshal.Read(backingMemory.Slice(0, size)); + + backingMemory = backingMemory.Slice(size); + + return result; + } + + /// + /// Extract a at the given index. + /// + /// The data type. + /// The to extract the data from. + /// The id in the provided memory. + /// The max allowed count. (for bound checking of the id in debug mode) + /// a at the given id. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory GetMemory(Memory memory, int id, uint count) where T : unmanaged + { + Debug.Assert(id >= 0 && id < count); + + return memory.Slice(id, 1); + } + + /// + /// Extract a ref T at the given index. + /// + /// The data type. + /// The to extract the data from. + /// The id in the provided memory. + /// The max allowed count. (for bound checking of the id in debug mode) + /// a ref T at the given id. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetFromMemory(Memory memory, int id, uint count) where T : unmanaged + { + return ref GetMemory(memory, id, count).Span[0]; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Utils/SpanMemoryManager.cs b/Ryujinx.Audio.Renderer/Utils/SpanMemoryManager.cs new file mode 100644 index 00000000..f6cfce0c --- /dev/null +++ b/Ryujinx.Audio.Renderer/Utils/SpanMemoryManager.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + public sealed unsafe class SpanMemoryManager : MemoryManager + where T : unmanaged + { + private readonly T* _pointer; + private readonly int _length; + + public SpanMemoryManager(Span span) + { + fixed (T* ptr = &MemoryMarshal.GetReference(span)) + { + _pointer = ptr; + _length = span.Length; + } + } + + public override Span GetSpan() => new Span(_pointer, _length); + + public override MemoryHandle Pin(int elementIndex = 0) + { + if (elementIndex < 0 || elementIndex >= _length) + { + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + + return new MemoryHandle(_pointer + elementIndex); + } + + public override void Unpin() { } + + protected override void Dispose(bool disposing) { } + + public static Memory Cast(Memory memory) where TFrom : unmanaged + { + return new SpanMemoryManager(MemoryMarshal.Cast(memory.Span)).Memory; + } + } +} diff --git a/Ryujinx.Audio.Renderer/Utils/SplitterHardwareDevice.cs b/Ryujinx.Audio.Renderer/Utils/SplitterHardwareDevice.cs new file mode 100644 index 00000000..7309540b --- /dev/null +++ b/Ryujinx.Audio.Renderer/Utils/SplitterHardwareDevice.cs @@ -0,0 +1,64 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Renderer.Integration; +using System; + +namespace Ryujinx.Audio.Renderer.Utils +{ + public class SplitterHardwareDevice : HardwareDevice + { + private HardwareDevice _baseDevice; + private HardwareDevice _secondaryDevice; + + public SplitterHardwareDevice(HardwareDevice baseDevice, HardwareDevice secondaryDevice) + { + _baseDevice = baseDevice; + _secondaryDevice = secondaryDevice; + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + _baseDevice.AppendBuffer(data, channelCount); + _secondaryDevice?.AppendBuffer(data, channelCount); + } + + public uint GetChannelCount() + { + return _baseDevice.GetChannelCount(); + } + + public uint GetSampleRate() + { + return _baseDevice.GetSampleRate(); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _baseDevice.Dispose(); + _secondaryDevice?.Dispose(); + } + } + } +} diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs index 1ea69b05..5d7077e1 100644 --- a/Ryujinx.Common/Logging/LogClass.cs +++ b/Ryujinx.Common/Logging/LogClass.cs @@ -4,6 +4,7 @@ namespace Ryujinx.Common.Logging { Application, Audio, + AudioRenderer, Cpu, Font, Emulation, diff --git a/Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs b/Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs new file mode 100644 index 00000000..fccda247 --- /dev/null +++ b/Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs @@ -0,0 +1,111 @@ +using Ryujinx.Common.Logging; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.System +{ + /// + /// Handle Windows Multimedia timer resolution. + /// + public class WindowsMultimediaTimerResolution : IDisposable + { + [StructLayout(LayoutKind.Sequential)] + public struct TimeCaps + { + public uint wPeriodMin; + public uint wPeriodMax; + }; + + [DllImport("winmm.dll", SetLastError = true)] + private static extern uint timeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps); + + [DllImport("winmm.dll")] + private static extern uint timeBeginPeriod(uint uMilliseconds); + + [DllImport("winmm.dll")] + private static extern uint timeEndPeriod(uint uMilliseconds); + + private uint _targetResolutionInMilliseconds; + private bool _isActive; + + /// + /// Create a new and activate the given resolution. + /// + /// + public WindowsMultimediaTimerResolution(uint targetResolutionInMilliseconds) + { + _targetResolutionInMilliseconds = targetResolutionInMilliseconds; + + EnsureResolutionSupport(); + Activate(); + } + + private void EnsureResolutionSupport() + { + TimeCaps timeCaps = default; + + uint result = timeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf()); + + if (result != 0) + { + Logger.Notice.Print(LogClass.Application, $"timeGetDevCaps failed with result: {result}"); + } + else + { + uint supportedTargetResolutionInMilliseconds = Math.Min(Math.Max(timeCaps.wPeriodMin, _targetResolutionInMilliseconds), timeCaps.wPeriodMax); + + if (supportedTargetResolutionInMilliseconds != _targetResolutionInMilliseconds) + { + Logger.Notice.Print(LogClass.Application, $"Target resolution isn't supported by OS, using closest resolution: {supportedTargetResolutionInMilliseconds}ms"); + + _targetResolutionInMilliseconds = supportedTargetResolutionInMilliseconds; + } + } + } + + private void Activate() + { + uint result = timeBeginPeriod(_targetResolutionInMilliseconds); + + if (result != 0) + { + Logger.Notice.Print(LogClass.Application, $"timeBeginPeriod failed with result: {result}"); + } + else + { + _isActive = true; + } + } + + private void Disable() + { + if (_isActive) + { + uint result = timeEndPeriod(_targetResolutionInMilliseconds); + + if (result != 0) + { + Logger.Notice.Print(LogClass.Application, $"timeEndPeriod failed with result: {result}"); + } + else + { + _isActive = false; + } + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Disable(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index f302e98a..22152e77 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -2,6 +2,10 @@ using LibHac; using LibHac.Bcat; using LibHac.Fs; using LibHac.FsSystem; +using Ryujinx.Audio.Renderer; +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Renderer.Server; using Ryujinx.Common; using Ryujinx.Configuration; using Ryujinx.HLE.FileSystem.Content; @@ -12,6 +16,7 @@ using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; using Ryujinx.HLE.HOS.Services.Arp; +using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Nv; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; @@ -43,6 +48,8 @@ namespace Ryujinx.HLE.HOS internal Switch Device { get; private set; } internal SurfaceFlinger SurfaceFlinger { get; private set; } + internal AudioRendererManager AudioRendererManager { get; private set; } + internal VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; private set; } public SystemStateMgr State { get; private set; } @@ -182,6 +189,34 @@ namespace Ryujinx.HLE.HOS ConfigurationState.Instance.System.EnableDockedMode.Event += OnDockedModeChange; InitLibHacHorizon(); + InitializeAudioRenderer(); + } + + private void InitializeAudioRenderer() + { + AudioRendererManager = new AudioRendererManager(); + AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(); + + IWritableEvent[] writableEvents = new IWritableEvent[RendererConstants.AudioRendererSessionCountMax]; + + for (int i = 0; i < writableEvents.Length; i++) + { + KEvent systemEvent = new KEvent(KernelContext); + + writableEvents[i] = new AudioKernelEvent(systemEvent); + } + + HardwareDevice[] devices = new HardwareDevice[RendererConstants.AudioRendererSessionCountMax]; + + // TODO: don't hardcode those values. + // TODO: keep the device somewhere and dispose it when exiting. + // TODO: This is kind of wrong, we should have an high level API for that and mix all buffers between them. + for (int i = 0; i < devices.Length; i++) + { + devices[i] = new AalHardwareDevice(i, Device.AudioOut, 2, RendererConstants.TargetSampleRate); + } + + AudioRendererManager.Initialize(writableEvents, devices); } public void LoadKip(string kipPath) @@ -292,6 +327,8 @@ namespace Ryujinx.HLE.HOS KernelContext.ThreadCounter.Signal(); KernelContext.ThreadCounter.Wait(); + AudioRendererManager.Dispose(); + KernelContext.Dispose(); } } diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs new file mode 100644 index 00000000..fdc23604 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs @@ -0,0 +1,98 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Renderer; +using Ryujinx.Audio.Renderer.Integration; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + public class AalHardwareDevice : HardwareDevice + { + private IAalOutput _output; + private int _trackId; + private int _bufferTag; + private int _nextTag; + private AutoResetEvent _releaseEvent; + + private uint _channelCount; + private uint _sampleRate; + + private short[] _buffer; + + private Queue _releasedTags; + + public AalHardwareDevice(int bufferTag, IAalOutput output, uint channelCount, uint sampleRate) + { + _bufferTag = bufferTag; + _channelCount = channelCount; + _sampleRate = sampleRate; + _output = output; + _releaseEvent = new AutoResetEvent(true); + _trackId = _output.OpenTrack((int)sampleRate, (int)channelCount, AudioCallback); + _releasedTags = new Queue(); + + _buffer = new short[RendererConstants.TargetSampleCount * channelCount]; + + _output.Start(_trackId); + } + + private void AudioCallback() + { + long[] released = _output.GetReleasedBuffers(_trackId, int.MaxValue); + + lock (_releasedTags) + { + foreach (long tag in released) + { + _releasedTags.Enqueue(tag); + } + } + } + + private long GetReleasedTag() + { + lock (_releasedTags) + { + if (_releasedTags.Count > 0) + { + return _releasedTags.Dequeue(); + } + + return (_bufferTag << 16) | (_nextTag++); + } + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + data.CopyTo(_buffer.AsSpan()); + + _output.AppendBuffer(_trackId, GetReleasedTag(), _buffer); + } + + public uint GetChannelCount() + { + return _channelCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _output.Stop(_trackId); + _output.CloseTrack(_trackId); + _releaseEvent.Dispose(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs new file mode 100644 index 00000000..8ba10946 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs @@ -0,0 +1,158 @@ +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + class AudioDevice : IAudioDevice + { + private VirtualDeviceSession[] _sessions; + private ulong _appletResourceId; + private int _revision; + private bool _isUsbDeviceSupported; + + private VirtualDeviceSessionRegistry _registry; + private KEvent _systemEvent; + + public AudioDevice(VirtualDeviceSessionRegistry registry, KernelContext context, ulong appletResourceId, int revision) + { + _registry = registry; + _appletResourceId = appletResourceId; + _revision = revision; + + BehaviourContext behaviourContext = new BehaviourContext(); + behaviourContext.SetUserRevision(revision); + + _isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported(); + _sessions = _registry.GetSessionByAppletResourceId(appletResourceId); + + // TODO: support the 3 different events correctly when we will have hot plugable audio devices. + _systemEvent = new KEvent(context); + _systemEvent.ReadableEvent.Signal(); + } + + private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false) + { + result = null; + + foreach (VirtualDeviceSession session in _sessions) + { + if (session.Device.Name.Equals(name)) + { + if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice()) + { + return false; + } + + result = session; + + return true; + } + } + + return false; + } + + public string GetActiveAudioDeviceName() + { + VirtualDevice device = _registry.ActiveDevice; + + if (!_isUsbDeviceSupported && device.IsUsbDevice()) + { + device = _registry.DefaultDevice; + } + + return device.Name; + } + + public uint GetActiveChannelCount() + { + VirtualDevice device = _registry.ActiveDevice; + + if (!_isUsbDeviceSupported && device.IsUsbDevice()) + { + device = _registry.DefaultDevice; + } + + return device.ChannelCount; + } + + public ResultCode GetAudioDeviceOutputVolume(string name, out float volume) + { + if (TryGetDeviceByName(out VirtualDeviceSession result, name)) + { + volume = result.Volume; + } + else + { + volume = 0.0f; + } + + return ResultCode.Success; + } + + public ResultCode SetAudioDeviceOutputVolume(string name, float volume) + { + if (TryGetDeviceByName(out VirtualDeviceSession result, name, true)) + { + if (!_isUsbDeviceSupported && result.Device.IsUsbDevice()) + { + result = _sessions[0]; + } + + result.Volume = volume; + } + + return ResultCode.Success; + } + + public ResultCode GetAudioSystemMasterVolumeSetting(string name, out float systemMasterVolume) + { + if (TryGetDeviceByName(out VirtualDeviceSession result, name, true)) + { + systemMasterVolume = result.Device.MasterVolume; + } + else + { + systemMasterVolume = 0.0f; + } + + return ResultCode.Success; + } + + public string[] ListAudioDeviceName() + { + int deviceCount = _sessions.Length; + + if (!_isUsbDeviceSupported) + { + deviceCount--; + } + + string[] result = new string[deviceCount]; + + for (int i = 0; i < deviceCount; i++) + { + result[i] = _sessions[i].Device.Name; + } + + return result; + } + + public KEvent QueryAudioDeviceInputEvent() + { + return _systemEvent; + } + + public KEvent QueryAudioDeviceOutputEvent() + { + return _systemEvent; + } + + public KEvent QueryAudioDeviceSystemEvent() + { + return _systemEvent; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs similarity index 52% rename from Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioDevice.cs rename to Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs index 1cc263b6..1cf6741e 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioDevice.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs @@ -1,41 +1,40 @@ -using Ryujinx.Common.Logging; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.SystemState; using System; using System.Text; -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer { - class IAudioDevice : IpcService + class AudioDeviceServer : IpcService { - private KEvent _systemEvent; + private const int AudioDeviceNameSize = 0x100; - public IAudioDevice(Horizon system) + private IAudioDevice _impl; + + public AudioDeviceServer(IAudioDevice impl) { - _systemEvent = new KEvent(system.KernelContext); - - // TODO: We shouldn't be signaling this here. - _systemEvent.ReadableEvent.Signal(); + _impl = impl; } [Command(0)] // ListAudioDeviceName() -> (u32, buffer) public ResultCode ListAudioDeviceName(ServiceCtx context) { - string[] deviceNames = SystemStateMgr.AudioOutputs; - - context.ResponseData.Write(deviceNames.Length); + string[] deviceNames = _impl.ListAudioDeviceName(); long position = context.Request.ReceiveBuff[0].Position; - long size = context.Request.ReceiveBuff[0].Size; + long size = context.Request.ReceiveBuff[0].Size; long basePosition = position; + int count = 0; + foreach (string name in deviceNames) { - byte[] buffer = Encoding.ASCII.GetBytes(name + "\0"); + byte[] buffer = Encoding.ASCII.GetBytes(name); if ((position - basePosition) + buffer.Length > size) { @@ -45,41 +44,58 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager } context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioDeviceNameSize - buffer.Length); - position += buffer.Length; + position += AudioDeviceNameSize; + count++; } + context.ResponseData.Write(count); + return ResultCode.Success; } [Command(1)] - // SetAudioDeviceOutputVolume(u32, buffer) + // SetAudioDeviceOutputVolume(f32 volume, buffer name) public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context) { float volume = context.RequestData.ReadSingle(); long position = context.Request.SendBuff[0].Position; - long size = context.Request.SendBuff[0].Size; + long size = context.Request.SendBuff[0].Size; - byte[] deviceNameBuffer = new byte[size]; - - context.Memory.Read((ulong)position, deviceNameBuffer); + string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size); - string deviceName = Encoding.ASCII.GetString(deviceNameBuffer); + return _impl.SetAudioDeviceOutputVolume(deviceName, volume); + } - Logger.Stub?.PrintStub(LogClass.ServiceAudio); + [Command(2)] + // GetAudioDeviceOutputVolume(buffer name) -> f32 volume + public ResultCode GetAudioDeviceOutputVolume(ServiceCtx context) + { + long position = context.Request.SendBuff[0].Position; + long size = context.Request.SendBuff[0].Size; - return ResultCode.Success; + string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size); + + ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(volume); + } + + return result; } [Command(3)] // GetActiveAudioDeviceName() -> buffer public ResultCode GetActiveAudioDeviceName(ServiceCtx context) { - string name = context.Device.System.State.ActiveAudioOutput; + string name = _impl.GetActiveAudioDeviceName(); long position = context.Request.ReceiveBuff[0].Position; - long size = context.Request.ReceiveBuff[0].Size; + long size = context.Request.ReceiveBuff[0].Size; byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0"); @@ -99,7 +115,9 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager // QueryAudioDeviceSystemEvent() -> handle public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context) { - if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success) + KEvent deviceSystemEvent = _impl.QueryAudioDeviceSystemEvent(); + + if (context.Process.HandleTable.GenerateHandle(deviceSystemEvent.ReadableEvent, out int handle) != KernelResult.Success) { throw new InvalidOperationException("Out of handles!"); } @@ -115,28 +133,28 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager // GetActiveChannelCount() -> u32 public ResultCode GetActiveChannelCount(ServiceCtx context) { - context.ResponseData.Write(2); + context.ResponseData.Write(_impl.GetActiveChannelCount()); Logger.Stub?.PrintStub(LogClass.ServiceAudio); return ResultCode.Success; } - [Command(6)] + [Command(6)] // 3.0.0+ // ListAudioDeviceNameAuto() -> (u32, buffer) public ResultCode ListAudioDeviceNameAuto(ServiceCtx context) { - string[] deviceNames = SystemStateMgr.AudioOutputs; - - context.ResponseData.Write(deviceNames.Length); + string[] deviceNames = _impl.ListAudioDeviceName(); (long position, long size) = context.Request.GetBufferType0x22(); long basePosition = position; + int count = 0; + foreach (string name in deviceNames) { - byte[] buffer = Encoding.UTF8.GetBytes(name + '\0'); + byte[] buffer = Encoding.ASCII.GetBytes(name); if ((position - basePosition) + buffer.Length > size) { @@ -146,48 +164,53 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager } context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioDeviceNameSize - buffer.Length); - position += buffer.Length; + position += AudioDeviceNameSize; + count++; } + context.ResponseData.Write(count); + return ResultCode.Success; } - [Command(7)] - // SetAudioDeviceOutputVolumeAuto(u32, buffer) + [Command(7)] // 3.0.0+ + // SetAudioDeviceOutputVolumeAuto(f32 volume, buffer name) public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context) { float volume = context.RequestData.ReadSingle(); (long position, long size) = context.Request.GetBufferType0x21(); - byte[] deviceNameBuffer = new byte[size]; + string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size); - context.Memory.Read((ulong)position, deviceNameBuffer); - - string deviceName = Encoding.UTF8.GetString(deviceNameBuffer); - - Logger.Stub?.PrintStub(LogClass.ServiceAudio); - - return ResultCode.Success; + return _impl.SetAudioDeviceOutputVolume(deviceName, volume); } - [Command(8)] - // GetAudioDeviceOutputVolumeAuto(buffer) -> u32 + [Command(8)] // 3.0.0+ + // GetAudioDeviceOutputVolumeAuto(buffer name) -> f32 public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context) { - context.ResponseData.Write(1f); + (long position, long size) = context.Request.GetBufferType0x21(); - Logger.Stub?.PrintStub(LogClass.ServiceAudio); + string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size); + + ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(volume); + } return ResultCode.Success; } - [Command(10)] + [Command(10)] // 3.0.0+ // GetActiveAudioDeviceNameAuto() -> buffer public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context) { - string name = context.Device.System.State.ActiveAudioOutput; + string name = _impl.GetActiveAudioDeviceName(); (long position, long size) = context.Request.GetBufferType0x22(); @@ -205,11 +228,13 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager return ResultCode.Success; } - [Command(11)] + [Command(11)] // 3.0.0+ // QueryAudioDeviceInputEvent() -> handle public ResultCode QueryAudioDeviceInputEvent(ServiceCtx context) { - if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success) + KEvent deviceInputEvent = _impl.QueryAudioDeviceInputEvent(); + + if (context.Process.HandleTable.GenerateHandle(deviceInputEvent.ReadableEvent, out int handle) != KernelResult.Success) { throw new InvalidOperationException("Out of handles!"); } @@ -221,11 +246,13 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager return ResultCode.Success; } - [Command(12)] + [Command(12)] // 3.0.0+ // QueryAudioDeviceOutputEvent() -> handle public ResultCode QueryAudioDeviceOutputEvent(ServiceCtx context) { - if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success) + KEvent deviceOutputEvent = _impl.QueryAudioDeviceOutputEvent(); + + if (context.Process.HandleTable.GenerateHandle(deviceOutputEvent.ReadableEvent, out int handle) != KernelResult.Success) { throw new InvalidOperationException("Out of handles!"); } @@ -236,5 +263,24 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager return ResultCode.Success; } + + [Command(13)] + // GetAudioSystemMasterVolumeSetting(buffer name) -> f32 + public ResultCode GetAudioSystemMasterVolumeSetting(ServiceCtx context) + { + long position = context.Request.SendBuff[0].Position; + long size = context.Request.SendBuff[0].Size; + + string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size); + + ResultCode result = _impl.GetAudioSystemMasterVolumeSetting(deviceName, out float systemMasterVolume); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(systemMasterVolume); + } + + return result; + } } -} \ No newline at end of file +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs new file mode 100644 index 00000000..1a132b91 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs @@ -0,0 +1,25 @@ +using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + class AudioKernelEvent : IWritableEvent + { + public KEvent Event { get; } + + public AudioKernelEvent(KEvent evnt) + { + Event = evnt; + } + + public void Clear() + { + Event.WritableEvent.Clear(); + } + + public void Signal() + { + Event.WritableEvent.Signal(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs new file mode 100644 index 00000000..702648dd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs @@ -0,0 +1,112 @@ +using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + class AudioRenderer : IAudioRenderer + { + private AudioRenderSystem _impl; + + public AudioRenderer(AudioRenderSystem impl) + { + _impl = impl; + } + + public ResultCode ExecuteAudioRendererRendering() + { + throw new NotImplementedException(); + } + + public uint GetMixBufferCount() + { + return _impl.GetMixBufferCount(); + } + + public uint GetRenderingTimeLimit() + { + return _impl.GetRenderingTimeLimit(); + } + + public uint GetSampleCount() + { + return _impl.GetSampleCount(); + } + + public uint GetSampleRate() + { + return _impl.GetSampleRate(); + } + + public int GetState() + { + if (_impl.IsActive()) + { + return 0; + } + + return 1; + } + + public ResultCode QuerySystemEvent(out KEvent systemEvent) + { + ResultCode resultCode = (ResultCode)_impl.QuerySystemEvent(out IWritableEvent outEvent); + + if (resultCode == ResultCode.Success) + { + if (outEvent is AudioKernelEvent) + { + systemEvent = ((AudioKernelEvent)outEvent).Event; + } + else + { + throw new NotImplementedException(); + } + } + else + { + systemEvent = null; + } + + return resultCode; + } + + public ResultCode RequestUpdate(Memory output, Memory performanceOutput, ReadOnlyMemory input) + { + return (ResultCode)_impl.Update(output, performanceOutput, input); + } + + public void SetRenderingTimeLimit(uint percent) + { + _impl.SetRenderingTimeLimitPercent(percent); + } + + public ResultCode Start() + { + _impl.Start(); + + return ResultCode.Success; + } + + public ResultCode Stop() + { + _impl.Stop(); + + return ResultCode.Success; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs new file mode 100644 index 00000000..5ff5ddb3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs @@ -0,0 +1,188 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Buffers; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + class AudioRendererServer : IpcService, IDisposable + { + private IAudioRenderer _impl; + + public AudioRendererServer(IAudioRenderer impl) + { + _impl = impl; + } + + [Command(0)] + // GetSampleRate() -> u32 + public ResultCode GetSampleRate(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetSampleRate()); + + return ResultCode.Success; + } + + [Command(1)] + // GetSampleCount() -> u32 + public ResultCode GetSampleCount(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetSampleCount()); + + return ResultCode.Success; + } + + [Command(2)] + // GetMixBufferCount() -> u32 + public ResultCode GetMixBufferCount(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetMixBufferCount()); + + return ResultCode.Success; + } + + [Command(3)] + // GetState() -> u32 + public ResultCode GetState(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetState()); + + return ResultCode.Success; + } + + [Command(4)] + // RequestUpdate(buffer input) + // -> (buffer output, buffer performanceOutput) + public ResultCode RequestUpdate(ServiceCtx context) + { + long inputPosition = context.Request.SendBuff[0].Position; + long inputSize = context.Request.SendBuff[0].Size; + + long outputPosition = context.Request.ReceiveBuff[0].Position; + long outputSize = context.Request.ReceiveBuff[0].Size; + + long performanceOutputPosition = context.Request.ReceiveBuff[1].Position; + long performanceOutputSize = context.Request.ReceiveBuff[1].Size; + + ReadOnlyMemory input = context.Memory.GetSpan((ulong)inputPosition, (int)inputSize).ToArray(); + + Memory output = new byte[outputSize]; + Memory performanceOutput = new byte[performanceOutputSize]; + + using MemoryHandle outputHandle = output.Pin(); + using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); + + ResultCode result = _impl.RequestUpdate(output, performanceOutput, input); + + if (result == ResultCode.Success) + { + context.Memory.Write((ulong)outputPosition, output.Span); + context.Memory.Write((ulong)performanceOutputPosition, performanceOutput.Span); + } + else + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{result}"); + } + + return result; + } + + [Command(5)] + // Start() + public ResultCode Start(ServiceCtx context) + { + return _impl.Start(); + } + + [Command(6)] + // Stop() + public ResultCode Stop(ServiceCtx context) + { + return _impl.Stop(); + } + + [Command(7)] + // QuerySystemEvent() -> handle + public ResultCode QuerySystemEvent(ServiceCtx context) + { + ResultCode result = _impl.QuerySystemEvent(out KEvent systemEvent); + + if (result == ResultCode.Success) + { + if (context.Process.HandleTable.GenerateHandle(systemEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + } + + return result; + } + + [Command(8)] + // SetAudioRendererRenderingTimeLimit(u32 limit) + public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context) + { + uint limit = context.RequestData.ReadUInt32(); + + _impl.SetRenderingTimeLimit(limit); + + return ResultCode.Success; + } + + [Command(9)] + // GetAudioRendererRenderingTimeLimit() -> u32 limit + public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context) + { + uint limit = _impl.GetRenderingTimeLimit(); + + context.ResponseData.Write(limit); + + return ResultCode.Success; + } + + [Command(10)] // 3.0.0+ + // RequestUpdateAuto(buffer input) + // -> (buffer output, buffer performanceOutput) + public ResultCode RequestUpdateAuto(ServiceCtx context) + { + (long inputPosition, long inputSize) = context.Request.GetBufferType0x21(); + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(0); + (long performanceOutputPosition, long performanceOutputSize) = context.Request.GetBufferType0x22(1); + + ReadOnlyMemory input = context.Memory.GetSpan((ulong)inputPosition, (int)inputSize).ToArray(); + + Memory output = new byte[outputSize]; + Memory performanceOutput = new byte[performanceOutputSize]; + + using MemoryHandle outputHandle = output.Pin(); + using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); + + ResultCode result = _impl.RequestUpdate(output, performanceOutput, input); + + if (result == ResultCode.Success) + { + context.Memory.Write((ulong)outputPosition, output.Span); + context.Memory.Write((ulong)performanceOutputPosition, performanceOutput.Span); + } + + return result; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs new file mode 100644 index 00000000..42ea727f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs @@ -0,0 +1,17 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + interface IAudioDevice + { + string[] ListAudioDeviceName(); + ResultCode SetAudioDeviceOutputVolume(string name, float volume); + ResultCode GetAudioDeviceOutputVolume(string name, out float volume); + string GetActiveAudioDeviceName(); + KEvent QueryAudioDeviceSystemEvent(); + uint GetActiveChannelCount(); + KEvent QueryAudioDeviceInputEvent(); + KEvent QueryAudioDeviceOutputEvent(); + ResultCode GetAudioSystemMasterVolumeSetting(string name, out float systemMasterVolume); + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs new file mode 100644 index 00000000..a59c94e9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + interface IAudioRenderer : IDisposable + { + uint GetSampleRate(); + uint GetSampleCount(); + uint GetMixBufferCount(); + int GetState(); + ResultCode RequestUpdate(Memory output, Memory performanceOutput, ReadOnlyMemory input); + ResultCode Start(); + ResultCode Stop(); + ResultCode QuerySystemEvent(out KEvent systemEvent); + void SetRenderingTimeLimit(uint percent); + uint GetRenderingTimeLimit(); + ResultCode ExecuteAudioRendererRendering(); + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs new file mode 100644 index 00000000..e12a9919 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs @@ -0,0 +1,50 @@ +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; + +using AudioRendererManagerImpl = Ryujinx.Audio.Renderer.Server.AudioRendererManager; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + class AudioRendererManager : IAudioRendererManager + { + private AudioRendererManagerImpl _impl; + private VirtualDeviceSessionRegistry _registry; + + public AudioRendererManager(AudioRendererManagerImpl impl, VirtualDeviceSessionRegistry registry) + { + _impl = impl; + _registry = registry; + } + + public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId) + { + outObject = new AudioDevice(_registry, context.Device.System.KernelContext, appletResourceUserId, revision); + + return ResultCode.Success; + } + + public ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) + { + return AudioRendererManagerImpl.GetWorkBufferSize(ref parameter); + } + + public ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle) + { + ResultCode result = (ResultCode)_impl.OpenAudioRenderer(out AudioRenderSystem renderer, context.Memory, ref parameter, appletResourceUserId, workBufferTransferMemory.Address, workBufferTransferMemory.Size, processHandle); + + if (result == ResultCode.Success) + { + obj = new AudioRenderer.AudioRenderer(renderer); + } + else + { + obj = null; + } + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/AudioRendererCommon.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/AudioRendererCommon.cs deleted file mode 100644 index c884b465..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/AudioRendererCommon.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - static class AudioRendererCommon - { - public static bool CheckValidRevision(AudioRendererParameter parameters) => GetRevisionVersion(parameters.Revision) <= AudioRendererConsts.Revision; - public static bool CheckFeatureSupported(int revision, int supportedRevision) => revision >= supportedRevision; - public static int GetRevisionVersion(int revision) => (revision - AudioRendererConsts.Rev0Magic) >> 24; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/BehaviorInfo.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/BehaviorInfo.cs deleted file mode 100644 index 461e4337..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/BehaviorInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - class BehaviorInfo - { - private const int _revision = AudioRendererConsts.Revision; - - private int _userRevision = 0; - - public BehaviorInfo() - { - /* TODO: this class got a size of 0xC0 - 0x00 - uint - Internal Revision - 0x04 - uint - User Revision - 0x08 - ... unknown ... - */ - } - - public bool IsSplitterSupported() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.Splitter); - public bool IsSplitterBugFixed() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.SplitterBugFix); - public bool IsVariadicCommandBufferSizeSupported() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.VariadicCommandBufferSize); - public bool IsElapsedFrameCountSupported() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.ElapsedFrameCount); - - public int GetPerformanceMetricsDataFormat() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.PerformanceMetricsDataFormatVersion2) ? 2 : 1; - - public void SetUserLibRevision(int revision) - { - _userRevision = AudioRendererCommon.GetRevisionVersion(revision); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/CommandGenerator.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/CommandGenerator.cs deleted file mode 100644 index b09b990b..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/CommandGenerator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - static class CommandGenerator - { - public static long CalculateCommandBufferSize(AudioRendererParameter parameters) - { - return parameters.EffectCount * 0x840 + - parameters.SubMixCount * 0x5A38 + - parameters.SinkCount * 0x148 + - parameters.SplitterDestinationDataCount * 0x540 + - (parameters.SplitterCount * 0x68 + 0x2E0) * parameters.VoiceCount + - ((parameters.VoiceCount + parameters.SubMixCount + parameters.EffectCount + parameters.SinkCount + 0x65) << 6) + - 0x3F8; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EdgeMatrix.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EdgeMatrix.cs deleted file mode 100644 index 3f87ae04..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EdgeMatrix.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Ryujinx.Common; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - static class EdgeMatrix - { - public static int GetWorkBufferSize(int totalMixCount) - { - int size = BitUtils.AlignUp(totalMixCount * totalMixCount, AudioRendererConsts.BufferAlignment); - - if (size < 0) - { - size |= 7; - } - - return size / 8; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EffectContext.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EffectContext.cs deleted file mode 100644 index 88f087ed..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EffectContext.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - class EffectContext - { - public EffectOut OutStatus; - } -} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs deleted file mode 100644 index b5802d2d..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs +++ /dev/null @@ -1,452 +0,0 @@ -using Ryujinx.Audio; -using Ryujinx.Audio.Adpcm; -using Ryujinx.Common.Logging; -using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel.Common; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.Utilities; -using System; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - class IAudioRenderer : IpcService, IDisposable - { - // This is the amount of samples that are going to be appended - // each time that RequestUpdateAudioRenderer is called. Ideally, - // this value shouldn't be neither too small (to avoid the player - // starving due to running out of samples) or too large (to avoid - // high latency). - private const int MixBufferSamplesCount = 960; - - private KEvent _updateEvent; - - private MemoryManager _memory; - - private IAalOutput _audioOut; - - private AudioRendererParameter _params; - - private MemoryPoolContext[] _memoryPools; - - private VoiceContext[] _voices; - - private EffectContext[] _effects; - - private int _track; - - private PlayState _playState; - - private ulong _elapsedFrameCount; - - public IAudioRenderer( - Horizon system, - MemoryManager memory, - IAalOutput audioOut, - AudioRendererParameter rendererParams) - { - _updateEvent = new KEvent(system.KernelContext); - - _memory = memory; - _audioOut = audioOut; - _params = rendererParams; - - _track = audioOut.OpenTrack( - AudioRendererConsts.HostSampleRate, - AudioRendererConsts.HostChannelsCount, - AudioCallback); - - _memoryPools = CreateArray(rendererParams.EffectCount + rendererParams.VoiceCount * 4); - - _voices = CreateArray(rendererParams.VoiceCount); - - _effects = CreateArray(rendererParams.EffectCount); - - _elapsedFrameCount = 0; - - InitializeAudioOut(); - - _playState = PlayState.Stopped; - } - - [Command(0)] - // GetSampleRate() -> u32 - public ResultCode GetSampleRate(ServiceCtx context) - { - context.ResponseData.Write(_params.SampleRate); - - return ResultCode.Success; - } - - [Command(1)] - // GetSampleCount() -> u32 - public ResultCode GetSampleCount(ServiceCtx context) - { - context.ResponseData.Write(_params.SampleCount); - - return ResultCode.Success; - } - - [Command(2)] - // GetMixBufferCount() -> u32 - public ResultCode GetMixBufferCount(ServiceCtx context) - { - context.ResponseData.Write(_params.SubMixCount); - - return ResultCode.Success; - } - - [Command(3)] - // GetState() -> u32 - public ResultCode GetState(ServiceCtx context) - { - context.ResponseData.Write((int)_playState); - - Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { State = Enum.GetName(typeof(PlayState), _playState) }); - - return ResultCode.Success; - } - - private void AudioCallback() - { - _updateEvent.ReadableEvent.Signal(); - } - - private static T[] CreateArray(int size) where T : new() - { - T[] output = new T[size]; - - for (int index = 0; index < size; index++) - { - output[index] = new T(); - } - - return output; - } - - private void InitializeAudioOut() - { - AppendMixedBuffer(0); - AppendMixedBuffer(1); - AppendMixedBuffer(2); - - _audioOut.Start(_track); - } - - [Command(4)] - // RequestUpdateAudioRenderer(buffer) - // -> (buffer, buffer) - public ResultCode RequestUpdateAudioRenderer(ServiceCtx context) - { - long outputPosition = context.Request.ReceiveBuff[0].Position; - long outputSize = context.Request.ReceiveBuff[0].Size; - - MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); - - long inputPosition = context.Request.SendBuff[0].Position; - - StructReader reader = new StructReader(context.Memory, inputPosition); - StructWriter writer = new StructWriter(context.Memory, outputPosition); - - UpdateDataHeader inputHeader = reader.Read(); - - BehaviorInfo behaviorInfo = new BehaviorInfo(); - - behaviorInfo.SetUserLibRevision(inputHeader.Revision); - - reader.Read(inputHeader.BehaviorSize); - - MemoryPoolIn[] memoryPoolsIn = reader.Read(inputHeader.MemoryPoolSize); - - for (int index = 0; index < memoryPoolsIn.Length; index++) - { - MemoryPoolIn memoryPool = memoryPoolsIn[index]; - - if (memoryPool.State == MemoryPoolState.RequestAttach) - { - _memoryPools[index].OutStatus.State = MemoryPoolState.Attached; - } - else if (memoryPool.State == MemoryPoolState.RequestDetach) - { - _memoryPools[index].OutStatus.State = MemoryPoolState.Detached; - } - } - - reader.Read(inputHeader.VoiceResourceSize); - - VoiceIn[] voicesIn = reader.Read(inputHeader.VoiceSize); - - for (int index = 0; index < voicesIn.Length; index++) - { - VoiceIn voice = voicesIn[index]; - - VoiceContext voiceCtx = _voices[index]; - - voiceCtx.SetAcquireState(voice.Acquired != 0); - - if (voice.Acquired == 0) - { - continue; - } - - if (voice.FirstUpdate != 0) - { - voiceCtx.AdpcmCtx = GetAdpcmDecoderContext( - voice.AdpcmCoeffsPosition, - voice.AdpcmCoeffsSize); - - voiceCtx.SampleFormat = voice.SampleFormat; - voiceCtx.SampleRate = voice.SampleRate; - voiceCtx.ChannelsCount = voice.ChannelsCount; - - voiceCtx.SetBufferIndex(voice.BaseWaveBufferIndex); - } - - voiceCtx.WaveBuffers[0] = voice.WaveBuffer0; - voiceCtx.WaveBuffers[1] = voice.WaveBuffer1; - voiceCtx.WaveBuffers[2] = voice.WaveBuffer2; - voiceCtx.WaveBuffers[3] = voice.WaveBuffer3; - voiceCtx.Volume = voice.Volume; - voiceCtx.PlayState = voice.PlayState; - } - - EffectIn[] effectsIn = reader.Read(inputHeader.EffectSize); - - for (int index = 0; index < effectsIn.Length; index++) - { - if (effectsIn[index].IsNew != 0) - { - _effects[index].OutStatus.State = EffectState.New; - } - } - - UpdateAudio(); - - UpdateDataHeader outputHeader = new UpdateDataHeader(); - - int updateHeaderSize = Marshal.SizeOf(); - - outputHeader.Revision = AudioRendererConsts.RevMagic; - outputHeader.BehaviorSize = 0xb0; - outputHeader.MemoryPoolSize = (_params.EffectCount + _params.VoiceCount * 4) * 0x10; - outputHeader.VoiceSize = _params.VoiceCount * 0x10; - outputHeader.EffectSize = _params.EffectCount * 0x10; - outputHeader.SinkSize = _params.SinkCount * 0x20; - outputHeader.PerformanceManagerSize = 0x10; - - if (behaviorInfo.IsElapsedFrameCountSupported()) - { - outputHeader.ElapsedFrameCountInfoSize = 0x10; - } - - outputHeader.TotalSize = updateHeaderSize + - outputHeader.BehaviorSize + - outputHeader.MemoryPoolSize + - outputHeader.VoiceSize + - outputHeader.EffectSize + - outputHeader.SinkSize + - outputHeader.PerformanceManagerSize + - outputHeader.ElapsedFrameCountInfoSize; - - writer.Write(outputHeader); - - foreach (MemoryPoolContext memoryPool in _memoryPools) - { - writer.Write(memoryPool.OutStatus); - } - - foreach (VoiceContext voice in _voices) - { - writer.Write(voice.OutStatus); - } - - foreach (EffectContext effect in _effects) - { - writer.Write(effect.OutStatus); - } - - writer.SkipBytes(_params.SinkCount * 0x20); - writer.SkipBytes(outputHeader.PerformanceManagerSize); - writer.SkipBytes(outputHeader.BehaviorSize); - - if (behaviorInfo.IsElapsedFrameCountSupported()) - { - writer.Write(new RendererInfoOut - { - ElapsedFrameCount = _elapsedFrameCount - }); - } - - return ResultCode.Success; - } - - [Command(5)] - // Start() - public ResultCode StartAudioRenderer(ServiceCtx context) - { - Logger.Stub?.PrintStub(LogClass.ServiceAudio); - - _playState = PlayState.Playing; - - return ResultCode.Success; - } - - [Command(6)] - // Stop() - public ResultCode StopAudioRenderer(ServiceCtx context) - { - Logger.Stub?.PrintStub(LogClass.ServiceAudio); - - _playState = PlayState.Stopped; - - return ResultCode.Success; - } - - [Command(7)] - // QuerySystemEvent() -> handle - public ResultCode QuerySystemEvent(ServiceCtx context) - { - if (context.Process.HandleTable.GenerateHandle(_updateEvent.ReadableEvent, out int handle) != KernelResult.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); - - return ResultCode.Success; - } - - private AdpcmDecoderContext GetAdpcmDecoderContext(long position, long size) - { - if (size == 0) - { - return null; - } - - AdpcmDecoderContext context = new AdpcmDecoderContext - { - Coefficients = new short[size >> 1] - }; - - for (int offset = 0; offset < size; offset += 2) - { - context.Coefficients[offset >> 1] = _memory.Read((ulong)(position + offset)); - } - - return context; - } - - private void UpdateAudio() - { - long[] released = _audioOut.GetReleasedBuffers(_track, 2); - - for (int index = 0; index < released.Length; index++) - { - AppendMixedBuffer(released[index]); - } - - _elapsedFrameCount++; - } - - private void AppendMixedBuffer(long tag) - { - int[] mixBuffer = new int[MixBufferSamplesCount * AudioRendererConsts.HostChannelsCount]; - - foreach (VoiceContext voice in _voices) - { - if (!voice.Playing || voice.CurrentWaveBuffer.Size == 0) - { - continue; - } - - int outOffset = 0; - int pendingSamples = MixBufferSamplesCount; - - while (pendingSamples > 0) - { - int[] samples = voice.GetBufferData(_memory, pendingSamples, out int returnedSamples); - - if (returnedSamples == 0) - { - break; - } - - pendingSamples -= returnedSamples; - - for (int offset = 0; offset < samples.Length; offset++) - { - mixBuffer[outOffset++] += (int)(samples[offset] * voice.Volume); - } - } - } - - _audioOut.AppendBuffer(_track, tag, GetFinalBuffer(mixBuffer)); - } - - private unsafe static short[] GetFinalBuffer(int[] buffer) - { - short[] output = new short[buffer.Length]; - - int offset = 0; - - // Perform Saturation using SSE2 if supported - if (Sse2.IsSupported) - { - fixed (int* inptr = buffer) - fixed (short* outptr = output) - { - for (; offset + 32 <= buffer.Length; offset += 32) - { - // Unroll the loop a little to ensure the CPU pipeline - // is always full. - Vector128 block1A = Sse2.LoadVector128(inptr + offset + 0); - Vector128 block1B = Sse2.LoadVector128(inptr + offset + 4); - - Vector128 block2A = Sse2.LoadVector128(inptr + offset + 8); - Vector128 block2B = Sse2.LoadVector128(inptr + offset + 12); - - Vector128 block3A = Sse2.LoadVector128(inptr + offset + 16); - Vector128 block3B = Sse2.LoadVector128(inptr + offset + 20); - - Vector128 block4A = Sse2.LoadVector128(inptr + offset + 24); - Vector128 block4B = Sse2.LoadVector128(inptr + offset + 28); - - Vector128 output1 = Sse2.PackSignedSaturate(block1A, block1B); - Vector128 output2 = Sse2.PackSignedSaturate(block2A, block2B); - Vector128 output3 = Sse2.PackSignedSaturate(block3A, block3B); - Vector128 output4 = Sse2.PackSignedSaturate(block4A, block4B); - - Sse2.Store(outptr + offset + 0, output1); - Sse2.Store(outptr + offset + 8, output2); - Sse2.Store(outptr + offset + 16, output3); - Sse2.Store(outptr + offset + 24, output4); - } - } - } - - // Process left overs - for (; offset < buffer.Length; offset++) - { - output[offset] = DspUtils.Saturate(buffer[offset]); - } - - return output; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _audioOut.CloseTrack(_track); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs deleted file mode 100644 index 3f48114c..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - class MemoryPoolContext - { - public MemoryPoolOut OutStatus; - - public MemoryPoolContext() - { - OutStatus.State = MemoryPoolState.Detached; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/NodeStates.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/NodeStates.cs deleted file mode 100644 index 7ae9aeea..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/NodeStates.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Ryujinx.Common; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - static class NodeStates - { - public static long GetWorkBufferSize(int totalMixCount) - { - int size = BitUtils.AlignUp(totalMixCount, AudioRendererConsts.BufferAlignment); - - if (size < 0) - { - size |= 7; - } - - return 4 * (totalMixCount * totalMixCount) + 12 * totalMixCount + 2 * (size / 8); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/PerformanceManager.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/PerformanceManager.cs deleted file mode 100644 index a5b3d79f..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/PerformanceManager.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Ryujinx.Common.Logging; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - static class PerformanceManager - { - public static long GetRequiredBufferSizeForPerformanceMetricsPerFrame(BehaviorInfo behaviorInfo, AudioRendererParameter parameters) - { - int performanceMetricsDataFormat = behaviorInfo.GetPerformanceMetricsDataFormat(); - - if (performanceMetricsDataFormat == 2) - { - return 24 * (parameters.VoiceCount + - parameters.EffectCount + - parameters.SubMixCount + - parameters.SinkCount + 1) + 0x990; - } - - if (performanceMetricsDataFormat != 1) - { - Logger.Warning?.Print(LogClass.ServiceAudio, $"PerformanceMetricsDataFormat: {performanceMetricsDataFormat} is not supported!"); - } - - return (((parameters.VoiceCount + - parameters.EffectCount + - parameters.SubMixCount + - parameters.SinkCount + 1) << 32) >> 0x1C) + 0x658; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs deleted file mode 100644 index 936e7f50..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - static class Resampler - { -#region "LookUp Tables" - private static short[] _curveLut0 = new short[] - { - 6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22, - 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48, - 5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77, - 5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109, - 4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147, - 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190, - 3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241, - 3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300, - 3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369, - 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449, - 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541, - 2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647, - 2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766, - 1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901, - 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052, - 1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221, - 1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407, - 1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613, - 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838, - 766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083, - 647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348, - 541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635, - 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942, - 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269, - 300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618, - 241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987, - 190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377, - 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785, - 109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213, - 77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659, - 48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121, - 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600 - }; - - private static short[] _curveLut1 = new short[] - { - -68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36, - -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80, - -990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128, - -1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180, - -1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240, - -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307, - -1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382, - -2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468, - -2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563, - -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668, - -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783, - -2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907, - -1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039, - -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176, - -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317, - -1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459, - -1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599, - -1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732, - -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855, - -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962, - -907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049, - -783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111, - -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141, - -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133, - -468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083, - -382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984, - -307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830, - -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617, - -180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338, - -128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990, - -80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568, - -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68 - }; - - private static short[] _curveLut2 = new short[] - { - 3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42, - 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58, - 2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76, - 1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98, - 1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121, - 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146, - 829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174, - 583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202, - 371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230, - 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258, - 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283, - -71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306, - -163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325, - -234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337, - -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341, - -317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336, - -336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317, - -341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284, - -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234, - -325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163, - -306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71, - -283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47, - -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194, - -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371, - -202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583, - -174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829, - -146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115, - -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442, - -98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813, - -76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227, - -58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688, - -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195 - }; -#endregion - - public static int[] Resample2Ch( - int[] buffer, - int srcSampleRate, - int dstSampleRate, - int samplesCount, - ref int fracPart) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (srcSampleRate <= 0) - { - throw new ArgumentOutOfRangeException(nameof(srcSampleRate)); - } - - if (dstSampleRate <= 0) - { - throw new ArgumentOutOfRangeException(nameof(dstSampleRate)); - } - - double ratio = (double)srcSampleRate / dstSampleRate; - - int newSamplesCount = (int)(samplesCount / ratio); - - int step = (int)(ratio * 0x8000); - - int[] output = new int[newSamplesCount * 2]; - - short[] lut; - - if (step > 0xaaaa) - { - lut = _curveLut0; - } - else if (step <= 0x8000) - { - lut = _curveLut1; - } - else - { - lut = _curveLut2; - } - - int inOffs = 0; - - for (int outOffs = 0; outOffs < output.Length; outOffs += 2) - { - int lutIndex = (fracPart >> 8) * 4; - - int sample0 = buffer[(inOffs + 0) * 2 + 0] * lut[lutIndex + 0] + - buffer[(inOffs + 1) * 2 + 0] * lut[lutIndex + 1] + - buffer[(inOffs + 2) * 2 + 0] * lut[lutIndex + 2] + - buffer[(inOffs + 3) * 2 + 0] * lut[lutIndex + 3]; - - int sample1 = buffer[(inOffs + 0) * 2 + 1] * lut[lutIndex + 0] + - buffer[(inOffs + 1) * 2 + 1] * lut[lutIndex + 1] + - buffer[(inOffs + 2) * 2 + 1] * lut[lutIndex + 2] + - buffer[(inOffs + 3) * 2 + 1] * lut[lutIndex + 3]; - - int newOffset = fracPart + step; - - inOffs += newOffset >> 15; - - fracPart = newOffset & 0x7fff; - - output[outOffs + 0] = sample0 >> 15; - output[outOffs + 1] = sample1 >> 15; - } - - return output; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/SplitterContext.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/SplitterContext.cs deleted file mode 100644 index e46af443..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/SplitterContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Ryujinx.Common; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - class SplitterContext - { - public static long CalcWorkBufferSize(BehaviorInfo behaviorInfo, AudioRendererParameter parameters) - { - if (!behaviorInfo.IsSplitterSupported()) - { - return 0; - } - - long size = parameters.SplitterDestinationDataCount * 0xE0 + - parameters.SplitterCount * 0x20; - - if (!behaviorInfo.IsSplitterBugFixed()) - { - size += BitUtils.AlignUp(4 * parameters.SplitterDestinationDataCount, 16); - } - - return size; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererConsts.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererConsts.cs deleted file mode 100644 index 864bda0d..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererConsts.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - static class AudioRendererConsts - { - // Revision Consts - public const int Revision = 8; - public const int Rev0Magic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24); - public const int RevMagic = Rev0Magic + (Revision << 24); - - // Misc Consts - public const int BufferAlignment = 0x40; - - // Host Consts - public const int HostSampleRate = 48000; - public const int HostChannelsCount = 2; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs deleted file mode 100644 index 2cfbc4b7..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential)] - struct AudioRendererParameter - { - public int SampleRate; - public int SampleCount; - public int MixBufferCount; - public int SubMixCount; - public int VoiceCount; - public int SinkCount; - public int EffectCount; - public int PerformanceManagerCount; - public int VoiceDropEnable; - public int SplitterCount; - public int SplitterDestinationDataCount; - public int Unknown2C; - public int Revision; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs deleted file mode 100644 index 953b4ce3..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)] - struct BehaviorIn - { - public long Unknown0; - public long Unknown8; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs deleted file mode 100644 index d0d8ed9b..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0xc, Pack = 1)] - struct BiquadFilter - { - public byte Enable; - public byte Padding; - public short B0; - public short B1; - public short B2; - public short A1; - public short A2; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectIn.cs deleted file mode 100644 index 03520475..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectIn.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0xc0, Pack = 1)] - unsafe struct EffectIn - { - public byte Unknown0x0; - public byte IsNew; - public fixed byte Unknown[0xbe]; - } -} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectOut.cs deleted file mode 100644 index 5106ad9e..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectOut.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 1)] - unsafe struct EffectOut - { - public EffectState State; - public fixed byte Reserved[15]; - } -} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectState.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectState.cs deleted file mode 100644 index ed676684..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/EffectState.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - enum EffectState : byte - { - None = 0, - New = 1 - } -} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs deleted file mode 100644 index 8dc53929..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 4)] - struct MemoryPoolIn - { - public long Address; - public long Size; - public MemoryPoolState State; - public int Unknown14; - public long Unknown18; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs deleted file mode 100644 index 7581e8a7..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)] - struct MemoryPoolOut - { - public MemoryPoolState State; - public int Unknown14; - public long Unknown18; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs deleted file mode 100644 index a82747b8..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - enum MemoryPoolState - { - Invalid = 0, - Unknown = 1, - RequestDetach = 2, - Detached = 3, - RequestAttach = 4, - Attached = 5, - Released = 6 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs deleted file mode 100644 index d63df971..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - enum PlayState : byte - { - Playing = 0, - Stopped = 1, - Paused = 2 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/RendererInfoOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/RendererInfoOut.cs deleted file mode 100644 index 0ea89384..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/RendererInfoOut.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)] - struct RendererInfoOut - { - public ulong ElapsedFrameCount; - public ulong Reserved; - } -} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/SupportTags.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/SupportTags.cs deleted file mode 100644 index a418d89e..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/SupportTags.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - static class SupportTags - { - public const int Splitter = 2; - public const int SplitterBugFix = 5; - public const int PerformanceMetricsDataFormatVersion2 = 5; - public const int VariadicCommandBufferSize = 5; - public const int ElapsedFrameCount = 5; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs deleted file mode 100644 index 6adb874c..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - struct UpdateDataHeader - { -#pragma warning disable CS0649 - public int Revision; - public int BehaviorSize; - public int MemoryPoolSize; - public int VoiceSize; - public int VoiceResourceSize; - public int EffectSize; - public int MixSize; - public int SinkSize; - public int PerformanceManagerSize; - public int Unknown24; - public int ElapsedFrameCountInfoSize; - public int Unknown2C; - public int Unknown30; - public int Unknown34; - public int Unknown38; - public int TotalSize; -#pragma warning restore CS0649 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs deleted file mode 100644 index 4871713e..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)] - struct VoiceChannelResourceIn - { - // ??? - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs deleted file mode 100644 index dbcd5558..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)] - struct VoiceIn - { - public int VoiceSlot; - public int NodeId; - - public byte FirstUpdate; - public byte Acquired; - - public PlayState PlayState; - - public SampleFormat SampleFormat; - - public int SampleRate; - - public int Priority; - - public int Unknown14; - - public int ChannelsCount; - - public float Pitch; - public float Volume; - - public BiquadFilter BiquadFilter0; - public BiquadFilter BiquadFilter1; - - public int AppendedWaveBuffersCount; - - public int BaseWaveBufferIndex; - - public int Unknown44; - - public long AdpcmCoeffsPosition; - public long AdpcmCoeffsSize; - - public int VoiceDestination; - public int Padding; - - public WaveBuffer WaveBuffer0; - public WaveBuffer WaveBuffer1; - public WaveBuffer WaveBuffer2; - public WaveBuffer WaveBuffer3; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs deleted file mode 100644 index 3a295971..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)] - struct VoiceOut - { - public long PlayedSamplesCount; - public int PlayedWaveBuffersCount; - public int VoiceDropsCount; //? - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs deleted file mode 100644 index 1c0d5630..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - [StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)] - struct WaveBuffer - { - public long Position; - public long Size; - public int FirstSampleOffset; - public int LastSampleOffset; - public byte Looping; - public byte LastBuffer; - public short Unknown1A; - public int Unknown1C; - public long AdpcmLoopContextPosition; - public long AdpcmLoopContextSize; - public long Unknown30; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs deleted file mode 100644 index c14c46b3..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs +++ /dev/null @@ -1,201 +0,0 @@ -using Ryujinx.Audio.Adpcm; -using Ryujinx.Cpu; -using System; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager -{ - class VoiceContext - { - private bool _acquired; - private bool _bufferReload; - - private int _resamplerFracPart; - - private int _bufferIndex; - private int _offset; - - public int SampleRate { get; set; } - public int ChannelsCount { get; set; } - - public float Volume { get; set; } - - public PlayState PlayState { get; set; } - - public SampleFormat SampleFormat { get; set; } - - public AdpcmDecoderContext AdpcmCtx { get; set; } - - public WaveBuffer[] WaveBuffers { get; } - - public WaveBuffer CurrentWaveBuffer => WaveBuffers[_bufferIndex]; - - private VoiceOut _outStatus; - - public VoiceOut OutStatus => _outStatus; - - private int[] _samples; - - public bool Playing => _acquired && PlayState == PlayState.Playing; - - public VoiceContext() - { - WaveBuffers = new WaveBuffer[4]; - } - - public void SetAcquireState(bool newState) - { - if (_acquired && !newState) - { - // Release. - Reset(); - } - - _acquired = newState; - } - - private void Reset() - { - _bufferReload = true; - - _bufferIndex = 0; - _offset = 0; - - _outStatus.PlayedSamplesCount = 0; - _outStatus.PlayedWaveBuffersCount = 0; - _outStatus.VoiceDropsCount = 0; - } - - public int[] GetBufferData(MemoryManager memory, int maxSamples, out int samplesCount) - { - if (!Playing) - { - samplesCount = 0; - - return null; - } - - if (_bufferReload) - { - _bufferReload = false; - - UpdateBuffer(memory); - } - - WaveBuffer wb = WaveBuffers[_bufferIndex]; - - int maxSize = _samples.Length - _offset; - - int size = maxSamples * AudioRendererConsts.HostChannelsCount; - - if (size > maxSize) - { - size = maxSize; - } - - int[] output = new int[size]; - - Array.Copy(_samples, _offset, output, 0, size); - - samplesCount = size / AudioRendererConsts.HostChannelsCount; - - _outStatus.PlayedSamplesCount += samplesCount; - - _offset += size; - - if (_offset == _samples.Length) - { - _offset = 0; - - if (wb.Looping == 0) - { - SetBufferIndex(_bufferIndex + 1); - } - - _outStatus.PlayedWaveBuffersCount++; - - if (wb.LastBuffer != 0) - { - PlayState = PlayState.Paused; - } - } - - return output; - } - - private void UpdateBuffer(MemoryManager memory) - { - // TODO: Implement conversion for formats other - // than interleaved stereo (2 channels). - // As of now, it assumes that HostChannelsCount == 2. - WaveBuffer wb = WaveBuffers[_bufferIndex]; - - if (wb.Position == 0) - { - _samples = new int[0]; - - return; - } - - if (SampleFormat == SampleFormat.PcmInt16) - { - int samplesCount = (int)(wb.Size / (sizeof(short) * ChannelsCount)); - - _samples = new int[samplesCount * AudioRendererConsts.HostChannelsCount]; - - if (ChannelsCount == 1) - { - for (int index = 0; index < samplesCount; index++) - { - short sample = memory.Read((ulong)(wb.Position + index * 2)); - - _samples[index * 2 + 0] = sample; - _samples[index * 2 + 1] = sample; - } - } - else - { - for (int index = 0; index < samplesCount * 2; index++) - { - _samples[index] = memory.Read((ulong)(wb.Position + index * 2)); - } - } - } - else if (SampleFormat == SampleFormat.Adpcm) - { - byte[] buffer = new byte[wb.Size]; - - memory.Read((ulong)wb.Position, buffer); - - _samples = AdpcmDecoder.Decode(buffer, AdpcmCtx); - } - else - { - throw new InvalidOperationException(); - } - - if (SampleRate != AudioRendererConsts.HostSampleRate) - { - // TODO: We should keep the frames being discarded (see the 4 below) - // on a buffer and include it on the next samples buffer, to allow - // the resampler to do seamless interpolation between wave buffers. - int samplesCount = _samples.Length / AudioRendererConsts.HostChannelsCount; - - samplesCount = Math.Max(samplesCount - 4, 0); - - _samples = Resampler.Resample2Ch( - _samples, - SampleRate, - AudioRendererConsts.HostSampleRate, - samplesCount, - ref _resamplerFracPart); - } - } - - public void SetBufferIndex(int index) - { - _bufferIndex = index & 3; - - _bufferReload = true; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs new file mode 100644 index 00000000..94972dbf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs @@ -0,0 +1,105 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audren:u")] + class AudioRendererManagerServer : IpcService + { + private const int InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24); + + private IAudioRendererManager _impl; + + public AudioRendererManagerServer(ServiceCtx context) : this(new AudioRendererManager(context.Device.System.AudioRendererManager, context.Device.System.AudioDeviceSessionRegistry)) { } + + public AudioRendererManagerServer(IAudioRendererManager impl) + { + _impl = impl; + } + + [Command(0)] + // OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal parameter, u64 workBufferSize, nn::applet::AppletResourceUserId appletResourceId, pid, handle workBuffer, handle processHandle) + // -> object + public ResultCode OpenAudioRenderer(ServiceCtx context) + { + AudioRendererConfiguration parameter = context.RequestData.ReadStruct(); + ulong workBufferSize = context.RequestData.ReadUInt64(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + KTransferMemory workBufferTransferMemory = context.Process.HandleTable.GetObject(context.Request.HandleDesc.ToCopy[0]); + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[1]; + + ResultCode result = _impl.OpenAudioRenderer(context, out IAudioRenderer renderer, ref parameter, workBufferSize, appletResourceUserId, workBufferTransferMemory, processHandle); + + if (result == ResultCode.Success) + { + MakeObject(context, new AudioRendererServer(renderer)); + } + + return result; + } + + [Command(1)] + // GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal parameter) -> u64 workBufferSize + public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context) + { + AudioRendererConfiguration parameter = context.RequestData.ReadStruct(); + + if (BehaviourContext.CheckValidRevision(parameter.Revision)) + { + ulong size = _impl.GetWorkBufferSize(ref parameter); + + context.ResponseData.Write(size); + + Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}."); + + return ResultCode.Success; + } + else + { + context.ResponseData.Write(0L); + + Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Revision)} is not supported!"); + + return ResultCode.UnsupportedRevision; + } + } + + [Command(2)] + // GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object + public ResultCode GetAudioDeviceService(ServiceCtx context) + { + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, InitialRevision, appletResourceUserId); + + if (result == ResultCode.Success) + { + MakeObject(context, new AudioDeviceServer(device)); + } + + return result; + } + + [Command(4)] // 4.0.0+ + // GetAudioDeviceServiceWithRevisionInfo(s32 revision, nn::applet::AppletResourceUserId appletResourceId) -> object + public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context) + { + int revision = context.RequestData.ReadInt32(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, revision, appletResourceUserId); + + if (result == ResultCode.Success) + { + MakeObject(context, new AudioDeviceServer(device)); + } + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs index 7e770a78..642e2525 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs @@ -1,148 +1,19 @@ -using Ryujinx.Audio; -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; namespace Ryujinx.HLE.HOS.Services.Audio { - [Service("audren:u")] - class IAudioRendererManager : IpcService + interface IAudioRendererManager { - public IAudioRendererManager(ServiceCtx context) { } + // TODO: Remove ServiceCtx argument + // BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend. + ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId); - [Command(0)] - // OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal, u64, nn::applet::AppletResourceUserId, pid, handle, handle) - // -> object - public ResultCode OpenAudioRenderer(ServiceCtx context) - { - IAalOutput audioOut = context.Device.AudioOut; + // TODO: Remove ServiceCtx argument + // BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend. + ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle); - AudioRendererParameter Params = GetAudioRendererParameter(context); - - MakeObject(context, new IAudioRenderer( - context.Device.System, - context.Memory, - audioOut, - Params)); - - return ResultCode.Success; - } - - [Command(1)] - // GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal) -> u64 - public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context) - { - AudioRendererParameter parameters = GetAudioRendererParameter(context); - - if (AudioRendererCommon.CheckValidRevision(parameters)) - { - BehaviorInfo behaviorInfo = new BehaviorInfo(); - - behaviorInfo.SetUserLibRevision(parameters.Revision); - - long size; - - int totalMixCount = parameters.SubMixCount + 1; - - size = BitUtils.AlignUp(parameters.MixBufferCount * 4, AudioRendererConsts.BufferAlignment) + - parameters.SubMixCount * 0x400 + - totalMixCount * 0x940 + - parameters.VoiceCount * 0x3F0 + - BitUtils.AlignUp(totalMixCount * 8, 16) + - BitUtils.AlignUp(parameters.VoiceCount * 8, 16) + - BitUtils.AlignUp(((parameters.SinkCount + parameters.SubMixCount) * 0x3C0 + parameters.SampleCount * 4) * - (parameters.MixBufferCount + 6), AudioRendererConsts.BufferAlignment) + - (parameters.SinkCount + parameters.SubMixCount) * 0x2C0 + - (parameters.EffectCount + parameters.VoiceCount * 4) * 0x30 + - 0x50; - - if (behaviorInfo.IsSplitterSupported()) - { - size += BitUtils.AlignUp(NodeStates.GetWorkBufferSize(totalMixCount) + EdgeMatrix.GetWorkBufferSize(totalMixCount), 16); - } - - size = parameters.SinkCount * 0x170 + - (parameters.SinkCount + parameters.SubMixCount) * 0x280 + - parameters.EffectCount * 0x4C0 + - ((size + SplitterContext.CalcWorkBufferSize(behaviorInfo, parameters) + 0x30 * parameters.EffectCount + (4 * parameters.VoiceCount) + 0x8F) & ~0x3FL) + - ((parameters.VoiceCount << 8) | 0x40); - - if (parameters.PerformanceManagerCount >= 1) - { - size += (PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(behaviorInfo, parameters) * - (parameters.PerformanceManagerCount + 1) + 0xFF) & ~0x3FL; - } - - if (behaviorInfo.IsVariadicCommandBufferSizeSupported()) - { - size += CommandGenerator.CalculateCommandBufferSize(parameters) + 0x7E; - } - else - { - size += 0x1807E; - } - - size = BitUtils.AlignUp(size, 0x1000); - - context.ResponseData.Write(size); - - Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}."); - - return ResultCode.Success; - } - else - { - context.ResponseData.Write(0L); - - Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{AudioRendererCommon.GetRevisionVersion(parameters.Revision)} is not supported!"); - - return ResultCode.UnsupportedRevision; - } - } - - private AudioRendererParameter GetAudioRendererParameter(ServiceCtx context) - { - AudioRendererParameter Params = new AudioRendererParameter - { - SampleRate = context.RequestData.ReadInt32(), - SampleCount = context.RequestData.ReadInt32(), - MixBufferCount = context.RequestData.ReadInt32(), - SubMixCount = context.RequestData.ReadInt32(), - VoiceCount = context.RequestData.ReadInt32(), - SinkCount = context.RequestData.ReadInt32(), - EffectCount = context.RequestData.ReadInt32(), - PerformanceManagerCount = context.RequestData.ReadInt32(), - VoiceDropEnable = context.RequestData.ReadInt32(), - SplitterCount = context.RequestData.ReadInt32(), - SplitterDestinationDataCount = context.RequestData.ReadInt32(), - Unknown2C = context.RequestData.ReadInt32(), - Revision = context.RequestData.ReadInt32() - }; - - return Params; - } - - [Command(2)] - // GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object - public ResultCode GetAudioDeviceService(ServiceCtx context) - { - long appletResourceUserId = context.RequestData.ReadInt64(); - - MakeObject(context, new IAudioDevice(context.Device.System)); - - return ResultCode.Success; - } - - [Command(4)] // 4.0.0+ - // GetAudioDeviceServiceWithRevisionInfo(u32 revision_info, nn::applet::AppletResourceUserId) -> object - public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context) - { - int revisionInfo = context.RequestData.ReadInt32(); - long appletResourceUserId = context.RequestData.ReadInt64(); - - Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { appletResourceUserId, revisionInfo }); - - return GetAudioDeviceService(context); - } + ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter); } } diff --git a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs index 1abfc8c3..0fe73ebb 100644 --- a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs +++ b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs @@ -28,13 +28,6 @@ namespace Ryujinx.HLE.HOS.SystemState "zh-Hant" }; - internal static string[] AudioOutputs = new string[] - { - "AudioTvOutput", - "AudioStereoJackOutput", - "AudioBuiltInSpeakerOutput" - }; - internal long DesiredKeyboardLayout { get; private set; } internal SystemLanguage DesiredSystemLanguage { get; private set; } @@ -57,8 +50,6 @@ namespace Ryujinx.HLE.HOS.SystemState public SystemStateMgr() { - SetAudioOutputAsBuiltInSpeaker(); - Account = new AccountUtils(); Account.AddUser(DefaultUserId, "Player"); @@ -94,21 +85,6 @@ namespace Ryujinx.HLE.HOS.SystemState DesiredRegionCode = (uint)region; } - public void SetAudioOutputAsTv() - { - ActiveAudioOutput = AudioOutputs[0]; - } - - public void SetAudioOutputAsStereoJack() - { - ActiveAudioOutput = AudioOutputs[1]; - } - - public void SetAudioOutputAsBuiltInSpeaker() - { - ActiveAudioOutput = AudioOutputs[2]; - } - internal static long GetLanguageCode(int index) { if ((uint)index >= LanguageCodes.Length) diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index bc599c05..0c5855a1 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -43,6 +43,7 @@ + diff --git a/Ryujinx.Tests/Audio/Renderer/AudioRendererConfigurationTests.cs b/Ryujinx.Tests/Audio/Renderer/AudioRendererConfigurationTests.cs new file mode 100644 index 00000000..6467bdf3 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/AudioRendererConfigurationTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class AudioRendererConfigurationTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x34, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/BehaviourParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/BehaviourParameterTests.cs new file mode 100644 index 00000000..cf87e15c --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/BehaviourParameterTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class BehaviourParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/BiquadFilterParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/BiquadFilterParameterTests.cs new file mode 100644 index 00000000..0f6e3833 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/BiquadFilterParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class BiquadFilterParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0xC, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Common/UpdateDataHeaderTests.cs b/Ryujinx.Tests/Audio/Renderer/Common/UpdateDataHeaderTests.cs new file mode 100644 index 00000000..f6572e55 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Common/UpdateDataHeaderTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Common +{ + class UpdateDataHeaderTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x40, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Common/VoiceUpdateStateTests.cs b/Ryujinx.Tests/Audio/Renderer/Common/VoiceUpdateStateTests.cs new file mode 100644 index 00000000..fe935cb6 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Common/VoiceUpdateStateTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Common +{ + class VoiceUpdateStateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.LessOrEqual(Unsafe.SizeOf(), 0x100); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Common/WaveBufferTests.cs b/Ryujinx.Tests/Audio/Renderer/Common/WaveBufferTests.cs new file mode 100644 index 00000000..f7411e71 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Common/WaveBufferTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Common +{ + class WaveBufferTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x30, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs new file mode 100644 index 00000000..2b482cdb --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class EffectInfoParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0xC0, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs b/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs new file mode 100644 index 00000000..199bcf8a --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class EffectOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/MemoryPoolParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/MemoryPoolParameterTests.cs new file mode 100644 index 00000000..2850fd35 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/MemoryPoolParameterTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class MemoryPoolParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/BehaviourErrorInfoOutStatusTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/BehaviourErrorInfoOutStatusTests.cs new file mode 100644 index 00000000..7323e720 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/BehaviourErrorInfoOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class BehaviourErrorInfoOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0xB0, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/AuxParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/AuxParameterTests.cs new file mode 100644 index 00000000..101bd348 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/AuxParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class AuxParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x6C, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameterTests.cs new file mode 100644 index 00000000..6cb7d93a --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class BiquadFilterEffectParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x18, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BufferMixerParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BufferMixerParameterTests.cs new file mode 100644 index 00000000..49ff509c --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BufferMixerParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class BufferMixerParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x94, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/DelayParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/DelayParameterTests.cs new file mode 100644 index 00000000..b11e3f47 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/DelayParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class DelayParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x35, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/Reverb3dParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/Reverb3dParameterTests.cs new file mode 100644 index 00000000..694ed55d --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/Reverb3dParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class Reverb3dParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x49, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/ReverbParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/ReverbParameterTests.cs new file mode 100644 index 00000000..ef9f3457 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/ReverbParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class ReverbParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x41, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdateTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdateTests.cs new file mode 100644 index 00000000..03184fdf --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdateTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class MixInParameterDirtyOnlyUpdateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/MixParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/MixParameterTests.cs new file mode 100644 index 00000000..4fb2bb77 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/MixParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class MixParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x930, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceInParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceInParameterTests.cs new file mode 100644 index 00000000..a81fa146 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceInParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Performance; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class PerformanceInParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceOutStatusTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceOutStatusTests.cs new file mode 100644 index 00000000..e61dea2e --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Performance; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class PerformanceOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/RendererInfoOutStatusTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/RendererInfoOutStatusTests.cs new file mode 100644 index 00000000..6b60db29 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/RendererInfoOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class RendererInfoOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/CircularBufferParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/CircularBufferParameterTests.cs new file mode 100644 index 00000000..8bc37c1c --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/CircularBufferParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Sink +{ + class CircularBufferParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x24, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/DeviceParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/DeviceParameterTests.cs new file mode 100644 index 00000000..27ae2b47 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/DeviceParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Sink +{ + class DeviceParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x11C, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/SinkInParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/SinkInParameterTests.cs new file mode 100644 index 00000000..21a17889 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/SinkInParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class SinkInParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x140, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/SinkOutStatusTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/SinkOutStatusTests.cs new file mode 100644 index 00000000..828248af --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/SinkOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class SinkOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Parameter/SplitterInParamHeaderTests.cs b/Ryujinx.Tests/Audio/Renderer/Parameter/SplitterInParamHeaderTests.cs new file mode 100644 index 00000000..fc966e4c --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Parameter/SplitterInParamHeaderTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class SplitterInParamHeaderTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Server/AddressInfoTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/AddressInfoTests.cs new file mode 100644 index 00000000..7c6b6526 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Server/AddressInfoTests.cs @@ -0,0 +1,35 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class AddressInfoTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + } + + [Test] + public void TestGetReference() + { + MemoryPoolState[] memoryPoolState = new MemoryPoolState[1]; + memoryPoolState[0] = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + memoryPoolState[0].SetCpuAddress(0x1000000, 0x10000); + memoryPoolState[0].DspAddress = 0x4000000; + + AddressInfo addressInfo = AddressInfo.Create(0x1000000, 0x1000); + + addressInfo.ForceMappedDspAddress = 0x2000000; + + Assert.AreEqual(0x2000000, addressInfo.GetReference(true)); + + addressInfo.SetupMemoryPool(memoryPoolState.AsSpan()); + + Assert.AreEqual(0x4000000, addressInfo.GetReference(true)); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs new file mode 100644 index 00000000..016e02a2 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs @@ -0,0 +1,228 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + public class BehaviourContextTests + { + [Test] + public void TestCheckFeature() + { + int latestRevision = BehaviourContext.BaseRevisionMagic + BehaviourContext.LastRevision; + int previousRevision = BehaviourContext.BaseRevisionMagic + (BehaviourContext.LastRevision - 1); + int invalidRevision = BehaviourContext.BaseRevisionMagic + (BehaviourContext.LastRevision + 1); + + Assert.IsTrue(BehaviourContext.CheckFeatureSupported(latestRevision, latestRevision)); + Assert.IsFalse(BehaviourContext.CheckFeatureSupported(previousRevision, latestRevision)); + Assert.IsTrue(BehaviourContext.CheckFeatureSupported(latestRevision, previousRevision)); + // In case we get an invalid revision, this is supposed to auto default to REV1 internally.. idk what the hell Nintendo was thinking here.. + Assert.IsTrue(BehaviourContext.CheckFeatureSupported(invalidRevision, latestRevision)); + } + + [Test] + public void TestsMemoryPoolForceMappingEnabled() + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision1); + + Assert.IsFalse(behaviourContext.IsMemoryPoolForceMappingEnabled()); + + behaviourContext.UpdateFlags(0x1); + + Assert.IsTrue(behaviourContext.IsMemoryPoolForceMappingEnabled()); + } + + [Test] + public void TestRevision1() + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision1); + + Assert.IsFalse(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsFalse(behaviourContext.IsSplitterSupported()); + Assert.IsFalse(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsFalse(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsFalse(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsFalse(behaviourContext.IsSplitterBugFixed()); + Assert.IsFalse(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsFalse(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + + Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(1, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision2() + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision2); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsFalse(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsFalse(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsFalse(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsFalse(behaviourContext.IsSplitterBugFixed()); + Assert.IsFalse(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsFalse(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + + Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(1, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision3() + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision3); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsFalse(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsFalse(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsFalse(behaviourContext.IsSplitterBugFixed()); + Assert.IsFalse(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsFalse(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + + Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(1, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision4() + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision4); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsFalse(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsFalse(behaviourContext.IsSplitterBugFixed()); + Assert.IsFalse(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsFalse(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + + Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(1, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision5() + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision5); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision6() + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision6); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision7() + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision7); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision8() + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision8); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Tests/Audio/Renderer/Server/MemoryPoolStateTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/MemoryPoolStateTests.cs new file mode 100644 index 00000000..94dc6906 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Server/MemoryPoolStateTests.cs @@ -0,0 +1,62 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class MemoryPoolStateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(Unsafe.SizeOf(), 0x20); + } + + [Test] + public void TestContains() + { + MemoryPoolState memoryPool = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + memoryPool.SetCpuAddress(0x1000000, 0x1000); + + memoryPool.DspAddress = 0x2000000; + + Assert.IsTrue(memoryPool.Contains(0x1000000, 0x10)); + Assert.IsTrue(memoryPool.Contains(0x1000FE0, 0x10)); + Assert.IsTrue(memoryPool.Contains(0x1000FFF, 0x1)); + Assert.IsFalse(memoryPool.Contains(0x1000FFF, 0x2)); + Assert.IsFalse(memoryPool.Contains(0x1001000, 0x10)); + Assert.IsFalse(memoryPool.Contains(0x2000000, 0x10)); + } + + [Test] + public void TestTranslate() + { + MemoryPoolState memoryPool = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + memoryPool.SetCpuAddress(0x1000000, 0x1000); + + memoryPool.DspAddress = 0x2000000; + + Assert.AreEqual(0x2000FE0, memoryPool.Translate(0x1000FE0, 0x10)); + Assert.AreEqual(0x2000FFF, memoryPool.Translate(0x1000FFF, 0x1)); + Assert.AreEqual(0x0, memoryPool.Translate(0x1000FFF, 0x2)); + Assert.AreEqual(0x0, memoryPool.Translate(0x1001000, 0x10)); + Assert.AreEqual(0x0, memoryPool.Translate(0x2000000, 0x10)); + } + + [Test] + public void TestIsMapped() + { + MemoryPoolState memoryPool = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + memoryPool.SetCpuAddress(0x1000000, 0x1000); + + Assert.IsFalse(memoryPool.IsMapped()); + + memoryPool.DspAddress = 0x2000000; + + Assert.IsTrue(memoryPool.IsMapped()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Server/MixStateTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/MixStateTests.cs new file mode 100644 index 00000000..d1ddf64d --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Server/MixStateTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Mix; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class MixStateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x940, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs new file mode 100644 index 00000000..e743252d --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs @@ -0,0 +1,135 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class PoolMapperTests + { + private const uint DummyProcessHandle = 0xCAFEBABE; + + [Test] + public void TestInitializeSystemPool() + { + PoolMapper poolMapper = new PoolMapper(DummyProcessHandle, true); + MemoryPoolState memoryPoolDsp = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); + MemoryPoolState memoryPoolCpu = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + const CpuAddress CpuAddress = 0x20000; + const DspAddress DspAddress = CpuAddress; // TODO: DSP LLE + const ulong CpuSize = 0x1000; + + Assert.IsFalse(poolMapper.InitializeSystemPool(ref memoryPoolCpu, CpuAddress, CpuSize)); + Assert.IsTrue(poolMapper.InitializeSystemPool(ref memoryPoolDsp, CpuAddress, CpuSize)); + + Assert.AreEqual(CpuAddress, memoryPoolDsp.CpuAddress); + Assert.AreEqual(CpuSize, memoryPoolDsp.Size); + Assert.AreEqual(DspAddress, memoryPoolDsp.DspAddress); + } + + [Test] + public void TestGetProcessHandle() + { + PoolMapper poolMapper = new PoolMapper(DummyProcessHandle, true); + MemoryPoolState memoryPoolDsp = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); + MemoryPoolState memoryPoolCpu = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + Assert.AreEqual(0xFFFF8001, poolMapper.GetProcessHandle(ref memoryPoolCpu)); + Assert.AreEqual(DummyProcessHandle, poolMapper.GetProcessHandle(ref memoryPoolDsp)); + } + + [Test] + public void TestMappings() + { + PoolMapper poolMapper = new PoolMapper(DummyProcessHandle, true); + MemoryPoolState memoryPoolDsp = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); + MemoryPoolState memoryPoolCpu = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + const CpuAddress CpuAddress = 0x20000; + const DspAddress DspAddress = CpuAddress; // TODO: DSP LLE + const ulong CpuSize = 0x1000; + + memoryPoolDsp.SetCpuAddress(CpuAddress, CpuSize); + memoryPoolCpu.SetCpuAddress(CpuAddress, CpuSize); + + Assert.AreEqual(DspAddress, poolMapper.Map(ref memoryPoolCpu)); + Assert.AreEqual(DspAddress, poolMapper.Map(ref memoryPoolDsp)); + Assert.AreEqual(DspAddress, memoryPoolDsp.DspAddress); + Assert.IsTrue(poolMapper.Unmap(ref memoryPoolCpu)); + + memoryPoolDsp.IsUsed = true; + Assert.IsFalse(poolMapper.Unmap(ref memoryPoolDsp)); + memoryPoolDsp.IsUsed = false; + Assert.IsTrue(poolMapper.Unmap(ref memoryPoolDsp)); + } + + [Test] + public void TestTryAttachBuffer() + { + const CpuAddress CpuAddress = 0x20000; + const DspAddress DspAddress = CpuAddress; // TODO: DSP LLE + const ulong CpuSize = 0x1000; + + const int MemoryPoolStateArraySize = 0x10; + const CpuAddress CpuAddressRegionEnding = CpuAddress * MemoryPoolStateArraySize; + + MemoryPoolState[] memoryPoolStateArray = new MemoryPoolState[MemoryPoolStateArraySize]; + + for (int i = 0; i < memoryPoolStateArray.Length; i++) + { + memoryPoolStateArray[i] = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + memoryPoolStateArray[i].SetCpuAddress(CpuAddress + (ulong)i * CpuSize, CpuSize); + } + + ErrorInfo errorInfo; + + AddressInfo addressInfo = AddressInfo.Create(); + + PoolMapper poolMapper = new PoolMapper(DummyProcessHandle, true); + + Assert.IsTrue(poolMapper.TryAttachBuffer(out errorInfo, ref addressInfo, 0, 0)); + + Assert.AreEqual(ResultCode.InvalidAddressInfo, errorInfo.ErrorCode); + Assert.AreEqual(0, errorInfo.ExtraErrorInfo); + Assert.AreEqual(0, addressInfo.ForceMappedDspAddress); + + Assert.IsTrue(poolMapper.TryAttachBuffer(out errorInfo, ref addressInfo, CpuAddress, CpuSize)); + + Assert.AreEqual(ResultCode.InvalidAddressInfo, errorInfo.ErrorCode); + Assert.AreEqual(CpuAddress, errorInfo.ExtraErrorInfo); + Assert.AreEqual(DspAddress, addressInfo.ForceMappedDspAddress); + + poolMapper = new PoolMapper(DummyProcessHandle, false); + + Assert.IsFalse(poolMapper.TryAttachBuffer(out errorInfo, ref addressInfo, 0, 0)); + + addressInfo.ForceMappedDspAddress = 0; + + Assert.IsFalse(poolMapper.TryAttachBuffer(out errorInfo, ref addressInfo, CpuAddress, CpuSize)); + + Assert.AreEqual(ResultCode.InvalidAddressInfo, errorInfo.ErrorCode); + Assert.AreEqual(CpuAddress, errorInfo.ExtraErrorInfo); + Assert.AreEqual(0, addressInfo.ForceMappedDspAddress); + + poolMapper = new PoolMapper(DummyProcessHandle, memoryPoolStateArray.AsMemory(), false); + + Assert.IsFalse(poolMapper.TryAttachBuffer(out errorInfo, ref addressInfo, CpuAddressRegionEnding, CpuSize)); + + Assert.AreEqual(ResultCode.InvalidAddressInfo, errorInfo.ErrorCode); + Assert.AreEqual(CpuAddressRegionEnding, errorInfo.ExtraErrorInfo); + Assert.AreEqual(0, addressInfo.ForceMappedDspAddress); + Assert.IsFalse(addressInfo.HasMemoryPoolState); + + Assert.IsTrue(poolMapper.TryAttachBuffer(out errorInfo, ref addressInfo, CpuAddress, CpuSize)); + + Assert.AreEqual(ResultCode.Success, errorInfo.ErrorCode); + Assert.AreEqual(0, errorInfo.ExtraErrorInfo); + Assert.AreEqual(0, addressInfo.ForceMappedDspAddress); + Assert.IsTrue(addressInfo.HasMemoryPoolState); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs new file mode 100644 index 00000000..51362954 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Splitter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class SplitterDestinationTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0xE0, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Server/SplitterStateTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/SplitterStateTests.cs new file mode 100644 index 00000000..2486f8c0 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Server/SplitterStateTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Splitter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class SplitterStateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Server/VoiceChannelResourceTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/VoiceChannelResourceTests.cs new file mode 100644 index 00000000..0b867286 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Server/VoiceChannelResourceTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Voice; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class VoiceChannelResourceTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0xD0, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Server/VoiceStateTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/VoiceStateTests.cs new file mode 100644 index 00000000..ddd05bc4 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Server/VoiceStateTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Voice; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class VoiceStateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.LessOrEqual(Unsafe.SizeOf(), 0x220); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/Server/WaveBufferTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/WaveBufferTests.cs new file mode 100644 index 00000000..9f3889c0 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/Server/WaveBufferTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Voice; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class WaveBufferTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x58, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/VoiceChannelResourceInParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/VoiceChannelResourceInParameterTests.cs new file mode 100644 index 00000000..b04f505e --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/VoiceChannelResourceInParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class VoiceChannelResourceInParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x70, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/VoiceInParameterTests.cs b/Ryujinx.Tests/Audio/Renderer/VoiceInParameterTests.cs new file mode 100644 index 00000000..9770189c --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/VoiceInParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class VoiceInParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x170, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Audio/Renderer/VoiceOutStatusTests.cs b/Ryujinx.Tests/Audio/Renderer/VoiceOutStatusTests.cs new file mode 100644 index 00000000..abef0646 --- /dev/null +++ b/Ryujinx.Tests/Audio/Renderer/VoiceOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class VoiceOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Tests/Ryujinx.Tests.csproj b/Ryujinx.Tests/Ryujinx.Tests.csproj index ff8e855e..3047f050 100644 --- a/Ryujinx.Tests/Ryujinx.Tests.csproj +++ b/Ryujinx.Tests/Ryujinx.Tests.csproj @@ -34,6 +34,7 @@ + diff --git a/Ryujinx.sln b/Ryujinx.sln index d5e85c2a..40ee9382 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec", "Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Renderer", "Ryujinx.Audio.Renderer\Ryujinx.Audio.Renderer.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Debugger", "Ryujinx.Debugger\Ryujinx.Debugger.csproj", "{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}" @@ -176,6 +178,14 @@ Global {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Release|Any CPU.Build.0 = Release|Any CPU {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.ActiveCfg = Release|Any CPU {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.Build.0 = Release|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|Any CPU.Build.0 = Debug|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Profile Release|Any CPU.ActiveCfg = Release|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Profile Release|Any CPU.Build.0 = Release|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|Any CPU.ActiveCfg = Release|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|Any CPU.Build.0 = Release|Any CPU {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Debug|Any CPU.Build.0 = Debug|Any CPU {79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index c6a83cf7..b458c5f4 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -104,6 +104,10 @@ Always + + Always + LICENSE-Ryujinx.Audio.Renderer.txt + diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 9a6d14f9..f077e9bd 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -4,6 +4,7 @@ using LibHac.Common; using LibHac.Ns; using Ryujinx.Audio; using Ryujinx.Common.Logging; +using Ryujinx.Common.System; using Ryujinx.Configuration; using Ryujinx.Configuration.System; using Ryujinx.Debugger.Profiler; @@ -29,6 +30,7 @@ namespace Ryujinx.Ui private static VirtualFileSystem _virtualFileSystem; private static ContentManager _contentManager; + private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; private static HLE.Switch _emulationContext; private static GlRenderer _glWidget; @@ -500,6 +502,11 @@ namespace Ryujinx.Ui private void CreateGameWindow(HLE.Switch device) { + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); + } + device.Hid.Npads.AddControllers(ConfigurationState.Instance.Hid.InputConfig.Value.Select(inputConfig => new HLE.HOS.Services.Hid.ControllerConfig { @@ -552,6 +559,8 @@ namespace Ryujinx.Ui } _glWidget.Dispose(); + _windowsMultimediaTimerResolution?.Dispose(); + _windowsMultimediaTimerResolution = null; _viewBox.Add(_gameTableWindow);