Compare commits

..

4 commits

Author SHA1 Message Date
d8a728c735
flake: make module & overlay arch-agnostic
Some checks failed
Release job / Release for linux-arm64 (push) Failing after 1s
Release job / Release for linux-x64 (push) Failing after 1s
Release job / Release MacOS universal (push) Failing after 1s
Release job / Create tag (push) Has been cancelled
Release job / Release for win-x64 (push) Has been cancelled
Release job / flatpak_release (push) Has been cancelled
2024-10-02 14:52:31 +02:00
2b0291c57c
chore: specify archs on nixosModules 2024-10-02 14:17:14 +02:00
08173845dd
add flake 2024-10-02 14:09:36 +02:00
gdkchan
a2c0035013
Update audio renderer to REV13: Add support for compressor statistics and volume reset (#7372)
* Update audio renderer to REV13: Add support for compressor statistics and volume reset

* XML docs

* Disable stats reset

* Wrong comment

* Fix more XML docs

* PR feedback
2024-10-01 11:30:57 +01:00
27 changed files with 836 additions and 124 deletions

4
.gitignore vendored
View file

@ -173,3 +173,7 @@ PublishProfiles/
# Glade backup files # Glade backup files
*.glade~ *.glade~
# nix
/result
/result-*

View file

@ -3,18 +3,18 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia" Version="11.1.3" /> <PackageVersion Include="Avalonia" Version="11.0.10" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.1.3" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.10" />
<PackageVersion Include="Avalonia.Desktop" Version="11.1.3" /> <PackageVersion Include="Avalonia.Desktop" Version="11.0.10" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.1.3" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.0.10" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.1.3" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
<PackageVersion Include="Avalonia.Svg" Version="11.1.0.1" /> <PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.1.0.1" /> <PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="2.2.0" /> <PackageVersion Include="Concentus" Version="2.2.0" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="9.0.4" /> <PackageVersion Include="DynamicData" Version="9.0.4" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.1.0" /> <PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" /> <PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" /> <PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="LibHac" Version="0.19.0" /> <PackageVersion Include="LibHac" Version="0.19.0" />
@ -42,7 +42,7 @@
<PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.8" /> <PackageVersion Include="SkiaSharp" Version="2.88.7" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
<PackageVersion Include="SPB" Version="0.0.4-build32" /> <PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />

View file

@ -1,6 +1,51 @@
> [!WARNING]
> Ryujinx has ceased upstream development. This repository exists for historical purposes only, as well as to provide a Nix flake that can be easily installed on other systems.
To run Ryujinx on a system with Nix installed and flakes enabled, simply run:
```shell
nix run github:Naxdy/Ryujinx#default
# -or-
nix run git+https://git.naxdy.org/NaxdyOrg/Ryujinx.git#default
```
This will download, build, and run Ryujinx on your machine. For a more permanent installation, you can either install it imperatively via:
```shell
nix profile install github:Naxdy/Ryujinx#default
# -or-
nix profile install git+https://git.naxdy.org/NaxdyOrg/Ryujinx.git#default
```
Or install it declaratively by adding the following to your `flake.nix` (either NixOS or home manager):
```nix
{
inputs.ryujinx.url = "github:Naxdy/Ryujinx";
# -or-
inputs.ryujinx.url = "git+https://git.naxdy.org/NaxdyOrg/Ryujinx.git";
}
```
The Ryujinx flake provides `nixosModules.default` and `nixosModules.default` which will add the Ryujinx `overlay` (also available standalone via `overlays.default`), providing the `ryujinx` package to your installation, which can then be pulled in as usual, via e.g.:
```nix
environment.systemPackages = [
pkgs.ryujinx
];
```
---
<h1 align="center"> <h1 align="center">
<br> <br>
<a href="https://ryujinx.org/"><img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx/master/distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a> <a href="https://ryujinx.org/"><img src="./distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a>
<br> <br>
<b>Ryujinx</b> <b>Ryujinx</b>
<br> <br>
@ -12,28 +57,10 @@
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#. Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds. This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
It was written from scratch and development on the project began in September 2017. It was written from scratch and development on the project began in September 2017.
Ryujinx is available on Github under the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>. Ryujinx is available on Github under the <a href="./LICENSE.txt" target="_blank">MIT license</a>.
<br /> <br />
</p> </p>
<p align="center">
<a href="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml">
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
alt="">
</a>
<a href="https://crwd.in/ryujinx">
<img src="https://badges.crowdin.net/ryujinx/localized.svg"
alt="">
</a>
<a href="https://discord.com/invite/VkQYXAZ">
<img src="https://img.shields.io/discord/410208534861447168?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
alt="Discord">
</a>
<br>
<br>
<img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx-Website/master/public/assets/images/shell.png">
</p>
## Compatibility ## Compatibility
As of May 2024, Ryujinx has been tested on approximately 4,300 titles; As of May 2024, Ryujinx has been tested on approximately 4,300 titles;
@ -53,7 +80,7 @@ failing to meet this requirement may result in a poor gameplay experience or une
See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide) on how to set up the emulator. See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide) on how to set up the emulator.
For our Local Wireless (LDN) builds, see our [Multiplayer: Local Play/Local Wireless Guide For our Local Wireless (LDN) builds, see our [Multiplayer: Local Play/Local Wireless Guide
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide). ](<https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide>).
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information. Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
@ -77,7 +104,7 @@ If you wish to build the emulator yourself, follow these steps:
### Step 1 ### Step 1
Install the [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0). Install the [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
Make sure your SDK version is higher or equal to the required version specified in [global.json](global.json). Make sure your SDK version is higher or equal to the required version specified in [global.json](global.json).
### Step 2 ### Step 2
@ -106,7 +133,7 @@ This folder is located in the user folder, which can be accessed by clicking `Op
It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code. It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code.
There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster). There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster).
The fastest option (host, unchecked) is set by default. The fastest option (host, unchecked) is set by default.
Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads. Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads.
The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game. The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game.
NOTE: This feature is enabled by default in the Options menu > System tab. NOTE: This feature is enabled by default in the Options menu > System tab.
You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch! You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch!
@ -149,10 +176,11 @@ If you'd like to support the project financially, Ryujinx has an active Patreon
</a> </a>
All developers working on the project do so in their free time, but the project has several expenses: All developers working on the project do so in their free time, but the project has several expenses:
* Hackable Nintendo Switch consoles to reverse-engineer the hardware
* Additional computer hardware for testing purposes (e.g. GPUs to diagnose graphical bugs, etc.) - Hackable Nintendo Switch consoles to reverse-engineer the hardware
* Licenses for various software development tools (e.g. Jetbrains, IDA) - Additional computer hardware for testing purposes (e.g. GPUs to diagnose graphical bugs, etc.)
* Web hosting and infrastructure maintenance (e.g. LDN servers) - Licenses for various software development tools (e.g. Jetbrains, IDA)
- Web hosting and infrastructure maintenance (e.g. LDN servers)
All funds received through Patreon are considered a donation to support the project. Patrons receive early access to progress reports and exclusive access to developer interviews. All funds received through Patreon are considered a donation to support the project. Patrons receive early access to progress reports and exclusive access to developer interviews.

206
deps.nix Normal file
View file

@ -0,0 +1,206 @@
# This file was automatically generated by passthru.fetch-deps.
# Please dont edit it manually, your changes might get overwritten!
{ fetchNuGet }: [
(fetchNuGet { pname = "Avalonia"; version = "11.0.10"; hash = "sha256-FuGl5admJ9AeRNrg/faGfqx8pwxGxdkmbnth9Jxhelc="; })
(fetchNuGet { pname = "Avalonia.Angle.Windows.Natives"; version = "2.1.0.2023020321"; hash = "sha256-TWop9cvak6cMv2vrA/GlpuYBxS8Fuj5UmupGIV7Q5Ks="; })
(fetchNuGet { pname = "Avalonia.BuildServices"; version = "0.0.29"; hash = "sha256-WPHRMNowRnYSCh88DWNBCltWsLPyOfzXGzBqLYE7tRY="; })
(fetchNuGet { pname = "Avalonia.Controls.ColorPicker"; version = "11.0.10"; hash = "sha256-G0ooIjNRW5YHKvQ6qPxe5gaE3HPwGfiCQUo34PSxXGg="; })
(fetchNuGet { pname = "Avalonia.Controls.ColorPicker"; version = "11.0.4"; hash = "sha256-Jp0j/58RF9Qooc7ATtq80FtX3TVLhi54JfgrbKdiDes="; })
(fetchNuGet { pname = "Avalonia.Controls.DataGrid"; version = "11.0.10"; hash = "sha256-0v4evkV0jbLffwfQG/QO/RQbHXlCBmFv8A2pBZjS5Y0="; })
(fetchNuGet { pname = "Avalonia.Controls.ItemsRepeater"; version = "11.0.4"; hash = "sha256-P8MfWKkDQrsk6x/vraNxxdYSMHytS8U3fMY2o8b49dw="; })
(fetchNuGet { pname = "Avalonia.Desktop"; version = "11.0.10"; hash = "sha256-fIty7TfiTC+OX9gCH4tA8Fs9dF4+G7Mhs9XnZadUR2g="; })
(fetchNuGet { pname = "Avalonia.Diagnostics"; version = "11.0.10"; hash = "sha256-itnN+LIZ2S+1CjD0ZS/woKtpgWbC/srMYzbYfX3a8LA="; })
(fetchNuGet { pname = "Avalonia.FreeDesktop"; version = "11.0.10"; hash = "sha256-uVNOOVTQIqQ2pDgSsz5saI7+fMvps40vJlMp1/XdyaE="; })
(fetchNuGet { pname = "Avalonia.Markup.Xaml.Loader"; version = "11.0.10"; hash = "sha256-Xv6L8U34QEiH6r3SQWLwuVFk9N9REmCUHa9hEbv2m24="; })
(fetchNuGet { pname = "Avalonia.Native"; version = "11.0.10"; hash = "sha256-P9j01FDXDpixo8wBVH3XK0Am6UBhG52HDrzt1ZqD8Ro="; })
(fetchNuGet { pname = "Avalonia.Remote.Protocol"; version = "11.0.10"; hash = "sha256-qtvlczTg2yUZWyyqXkkboB8lK9aYv+STbfDvSKb55Vw="; })
(fetchNuGet { pname = "Avalonia.Remote.Protocol"; version = "11.0.4"; hash = "sha256-13qXDjlWElmwQ0sb00+ny9gOgKuDOHKvALuQB6EZxCQ="; })
(fetchNuGet { pname = "Avalonia.Skia"; version = "11.0.0"; hash = "sha256-A01nrs3Ij1eTo6tPmu7++T1K+Wo/H/9LvpeuOUGbQeU="; })
(fetchNuGet { pname = "Avalonia.Skia"; version = "11.0.10"; hash = "sha256-1SU17j43Fiw4LbEEgqx10zE/iIVPfb8G1JVbfD2RhXA="; })
(fetchNuGet { pname = "Avalonia.Skia"; version = "11.0.4"; hash = "sha256-1XfPTcAaNj1926uYkvo7P62++ds5M2gHvkv1hRzBVfs="; })
(fetchNuGet { pname = "Avalonia.Svg"; version = "11.0.0.18"; hash = "sha256-U4bafxxxFE0tKmKVxguxH+doFrTHlM6DjFP8wz6Xm9U="; })
(fetchNuGet { pname = "Avalonia.Svg.Skia"; version = "11.0.0.18"; hash = "sha256-M4AQkL42efqOSck4Lf7C1naIRjEPMlnxi3OVC8zLJaQ="; })
(fetchNuGet { pname = "Avalonia.Themes.Simple"; version = "11.0.10"; hash = "sha256-54fc2g1yvM7pPRaF062lSjXaQDe2i61xQRM8m81vWm8="; })
(fetchNuGet { pname = "Avalonia.Win32"; version = "11.0.10"; hash = "sha256-+o228ElrBJBxBkZKGbo3x8/52wKpnAk/x6Yon5pyA74="; })
(fetchNuGet { pname = "Avalonia.X11"; version = "11.0.10"; hash = "sha256-/CRivMxpcbW1FnIuwZbF2ucdcbn4TCyjKzLXgdGtCfQ="; })
(fetchNuGet { pname = "CommandLineParser"; version = "2.9.1"; hash = "sha256-ApU9y1yX60daSjPk3KYDBeJ7XZByKW8hse9NRZGcjeo="; })
(fetchNuGet { pname = "Concentus"; version = "2.2.0"; hash = "sha256-7wbB76WoTd2CISIODGhmEiPIrydI0dqDMZGf4gdkogM="; })
(fetchNuGet { pname = "DiscordRichPresence"; version = "1.2.1.24"; hash = "sha256-oRNrlF1/yK0QvrW2+48RsmSg9h9/pDIfA56/bpoHXFU="; })
(fetchNuGet { pname = "DynamicData"; version = "9.0.4"; hash = "sha256-3pyiJeWRwfaT7p1ArsoR13aI78Jo13aHOEw3BelTS9g="; })
(fetchNuGet { pname = "ExCSS"; version = "4.2.3"; hash = "sha256-M/H6P5p7qqdFz/fgAI2MMBWQ7neN/GIieYSSxxjsM9I="; })
(fetchNuGet { pname = "FluentAvaloniaUI"; version = "2.0.5"; hash = "sha256-EaJ6qR2yn+7p8lf62yx2vL3sGhnPOfbP5jBjR+pGY7o="; })
(fetchNuGet { pname = "FSharp.Core"; version = "7.0.200"; hash = "sha256-680VgvYbZbztPQosO17r5y8vxg/Y/4Vmr5K3iLIJKMo="; })
(fetchNuGet { pname = "GtkSharp.Dependencies"; version = "1.1.1"; hash = "sha256-/IpSj5JnUTREfQsdA3XW+eqNhqApTds65DQoNpjl3jk="; })
(fetchNuGet { pname = "HarfBuzzSharp"; version = "2.8.2.3"; hash = "sha256-4tbdgUabPjlkBm3aUFeocj4Fdslmms2olDFpzOLyqoQ="; })
(fetchNuGet { pname = "HarfBuzzSharp"; version = "7.3.0"; hash = "sha256-LlPQO/NYgIMWicvLOtWsQzCp512QpIImYDP9/n2rDOc="; })
(fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.Linux"; version = "2.8.2.3"; hash = "sha256-3xwVfNfKTkuLdnT+e3bfG9tNTdEmar7ByzY+NTlUKLg="; })
(fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.Linux"; version = "7.3.0"; hash = "sha256-AEHjgqX0o+Fob0SeZ6EikGKoEe6rRxess5fVJ31UL0U="; })
(fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.macOS"; version = "2.8.2.3"; hash = "sha256-ZohUEaovj/sRB4rjuJIOq6S9eim3m+qMlpHIebNDTRQ="; })
(fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.macOS"; version = "7.3.0"; hash = "sha256-6oFcdKb17UX5wyAUeCCKXGvzkf0w3MNdZOVMvs54tqw="; })
(fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.WebAssembly"; version = "2.8.2.3"; hash = "sha256-ZsiBGpXfODHUHPgU/50k9QR/j6Klo7rsB0SUt8zYcBA="; })
(fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.WebAssembly"; version = "7.3.0"; hash = "sha256-9VI0xCavuuIIStuQ7ipBfWu5HrAt+Kk/F2j57C1llTU="; })
(fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.Win32"; version = "2.8.2.3"; hash = "sha256-5GSzM5IUoOwK+zJg0d74WlT3n1VZly8pKlyjiqVocCI="; })
(fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.Win32"; version = "7.3.0"; hash = "sha256-WnB7l73hneU9Kpbm8S9zEYbZHjFre24vWz0vl8+v28M="; })
(fetchNuGet { pname = "LibHac"; version = "0.19.0"; hash = "sha256-FDEmeGHbX/aCFjFbFk8QwO2rTfFizt9UKb+KFDt23hk="; })
(fetchNuGet { pname = "MicroCom.CodeGenerator.MSBuild"; version = "0.11.0"; hash = "sha256-TsxziX9V8T3qRrEA3o9wY84ocDcUUUBzvARi5QZW23o="; })
(fetchNuGet { pname = "MicroCom.Runtime"; version = "0.11.0"; hash = "sha256-VdwpP5fsclvNqJuppaOvwEwv2ofnAI5ZSz2V+UEdLF0="; })
(fetchNuGet { pname = "Microsoft.CodeAnalysis.Analyzers"; version = "3.0.0"; hash = "sha256-KDbCfsBWSJ5ohEXUKp1s1LX9xA2NPvXE/xVzj68EdC0="; })
(fetchNuGet { pname = "Microsoft.CodeAnalysis.Analyzers"; version = "3.3.4"; hash = "sha256-qDzTfZBSCvAUu9gzq2k+LOvh6/eRvJ9++VCNck/ZpnE="; })
(fetchNuGet { pname = "Microsoft.CodeAnalysis.Common"; version = "3.8.0"; hash = "sha256-3G9vSc/gHH7FWgOySLTut1+eEaf3H66qcPOvNPLOx4o="; })
(fetchNuGet { pname = "Microsoft.CodeAnalysis.Common"; version = "4.9.2"; hash = "sha256-QU/nyiJWpdPQGHBdaOEVc+AghnGHcKBFBX0oyhRZ9CQ="; })
(fetchNuGet { pname = "Microsoft.CodeAnalysis.CSharp"; version = "3.8.0"; hash = "sha256-i/r3V/No/VzqmJlWxpGoirvlbJDbBPa/ONZtzYrxuc4="; })
(fetchNuGet { pname = "Microsoft.CodeAnalysis.CSharp"; version = "4.9.2"; hash = "sha256-j06Q4A9E65075SBXdXVCMRgeLxA63Rv1vxarydmmVAA="; })
(fetchNuGet { pname = "Microsoft.CodeAnalysis.CSharp.Scripting"; version = "3.8.0"; hash = "sha256-fA9Qu+vTyMZ9REzxJ4aMg/SHCDRk4q9k4ZGUdynoHnA="; })
(fetchNuGet { pname = "Microsoft.CodeAnalysis.Scripting.Common"; version = "3.8.0"; hash = "sha256-866jMHp8kbc1FYpKuUWnd7ViU6kGJTAxPcL/IjXrT0I="; })
(fetchNuGet { pname = "Microsoft.CodeCoverage"; version = "17.9.0"; hash = "sha256-OaGa4+jRPHs+T+p/oekm2Miluqfd2IX8Rt+BmUx8kr4="; })
(fetchNuGet { pname = "Microsoft.CSharp"; version = "4.3.0"; hash = "sha256-a3dAiPaVuky0wpcHmpTVtAQJNGZ2v91/oArA+dpJgj8="; })
(fetchNuGet { pname = "Microsoft.CSharp"; version = "4.7.0"; hash = "sha256-Enknv2RsFF68lEPdrf5M+BpV1kHoLTVRApKUwuk/pj0="; })
(fetchNuGet { pname = "Microsoft.DotNet.PlatformAbstractions"; version = "3.1.6"; hash = "sha256-RfM2qXiqdiamPkXr4IDkNc0IZSF9iTZv4uou/E7zNS0="; })
(fetchNuGet { pname = "Microsoft.Extensions.DependencyModel"; version = "8.0.0"; hash = "sha256-qkCdwemqdZY/yIW5Xmh7Exv74XuE39T8aHGHCofoVgo="; })
(fetchNuGet { pname = "Microsoft.IdentityModel.Abstractions"; version = "8.0.1"; hash = "sha256-zPWUKTCfGm4MWcYPU037NzezsFE1g8tEijjQkw5iooI="; })
(fetchNuGet { pname = "Microsoft.IdentityModel.JsonWebTokens"; version = "8.0.1"; hash = "sha256-Xv9MUnjb66U3xeR9drOcSX5n2DjOCIJZPMNSKjWHo9Y="; })
(fetchNuGet { pname = "Microsoft.IdentityModel.Logging"; version = "8.0.1"; hash = "sha256-FfwrH/2eLT521Kqw+RBIoVfzlTNyYMqlWP3z+T6Wy2Y="; })
(fetchNuGet { pname = "Microsoft.IdentityModel.Tokens"; version = "8.0.1"; hash = "sha256-beVbbVQy874HlXkTKarPTT5/r7XR1NGHA/50ywWp7YA="; })
(fetchNuGet { pname = "Microsoft.IO.RecyclableMemoryStream"; version = "3.0.1"; hash = "sha256-unFg/5EcU/XKJbob4GtQC43Ydgi5VjeBGs7hfhj4EYo="; })
(fetchNuGet { pname = "Microsoft.NET.Test.Sdk"; version = "17.9.0"; hash = "sha256-q/1AJ7eNlk02wvN76qvjl2xBx5iJ+h5ssiE/4akLmtI="; })
(fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "1.1.0"; hash = "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM="; })
(fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "2.0.0"; hash = "sha256-IEvBk6wUXSdyCnkj6tHahOJv290tVVT8tyemYcR0Yro="; })
(fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "2.1.2"; hash = "sha256-gYQQO7zsqG+OtN4ywYQyfsiggS2zmxw4+cPXlK+FB5Q="; })
(fetchNuGet { pname = "Microsoft.NETCore.Targets"; version = "1.1.0"; hash = "sha256-0AqQ2gMS8iNlYkrD+BxtIg7cXMnr9xZHtKAuN4bjfaQ="; })
(fetchNuGet { pname = "Microsoft.TestPlatform.ObjectModel"; version = "17.9.0"; hash = "sha256-iiXUFzpvT8OWdzMj9FGJDqanwHx40s1TXVY9l3ii+s0="; })
(fetchNuGet { pname = "Microsoft.TestPlatform.TestHost"; version = "17.9.0"; hash = "sha256-1BZIY1z+C9TROgdTV/tq4zsPy7Q71GQksr/LoMKAzqU="; })
(fetchNuGet { pname = "Microsoft.Win32.Registry"; version = "4.5.0"; hash = "sha256-WMBXsIb0DgPFPaFkNVxY9b9vcMxPqtgFgijKYMJfV/0="; })
(fetchNuGet { pname = "MsgPack.Cli"; version = "1.0.1"; hash = "sha256-Gf0Ed9XHH4oFpJIkzhg/xhDVpenunSol65qa8IZeYrY="; })
(fetchNuGet { pname = "NetCoreServer"; version = "8.0.7"; hash = "sha256-RUYic8uAgJGdhUCrMJQULKlHB6xvw9H1lnNGU1axNZw="; })
(fetchNuGet { pname = "NETStandard.Library"; version = "2.0.0"; hash = "sha256-Pp7fRylai8JrE1O+9TGfIEJrAOmnWTJRLWE+qJBahK0="; })
(fetchNuGet { pname = "NETStandard.Library"; version = "2.0.3"; hash = "sha256-Prh2RPebz/s8AzHb2sPHg3Jl8s31inv9k+Qxd293ybo="; })
(fetchNuGet { pname = "Newtonsoft.Json"; version = "13.0.1"; hash = "sha256-K2tSVW4n4beRPzPu3rlVaBEMdGvWSv/3Q1fxaDh4Mjo="; })
(fetchNuGet { pname = "NUnit"; version = "3.13.3"; hash = "sha256-Zn+sJIF7ieNqu/t2RwJx6WPFb1jl9UuNHidb/Px0v3E="; })
(fetchNuGet { pname = "NUnit3TestAdapter"; version = "4.1.0"; hash = "sha256-nDPiYdTFulqozEJrujr8/cqjG7m15Vkd/Frqem0Jr/w="; })
(fetchNuGet { pname = "OpenTK.Audio.OpenAL"; version = "4.8.2"; hash = "sha256-i5KRiTYTNMB4Y5Qd5xewaYrb9sBbnXMDu2QXbM3RCeU="; })
(fetchNuGet { pname = "OpenTK.Core"; version = "4.8.2"; hash = "sha256-59S4Vj13y8HtZT6RZTwO6ZZbk1GUNDcYx1rMdv5jr4I="; })
(fetchNuGet { pname = "OpenTK.Graphics"; version = "4.8.2"; hash = "sha256-DNpXqtM9Oj6wDGYSF2FD4A4ueWG892Wk6uGWffNspo0="; })
(fetchNuGet { pname = "OpenTK.Mathematics"; version = "4.8.2"; hash = "sha256-TPsts443n6iEajfH2EuYTKtubrWuTLiCrTB1F4FndIo="; })
(fetchNuGet { pname = "OpenTK.redist.glfw"; version = "3.3.8.39"; hash = "sha256-bg8bGfoDDqmZ/efLFVm8l5etQajIVvOcQ/Nv+yKD4Bc="; })
(fetchNuGet { pname = "OpenTK.Windowing.GraphicsLibraryFramework"; version = "4.8.2"; hash = "sha256-a1MGtU+27pBNns55g8mOsxXpZxfEr6M62zLkIkkJTIY="; })
(fetchNuGet { pname = "runtime.any.System.Collections"; version = "4.3.0"; hash = "sha256-4PGZqyWhZ6/HCTF2KddDsbmTTjxs2oW79YfkberDZS8="; })
(fetchNuGet { pname = "runtime.any.System.Globalization"; version = "4.3.0"; hash = "sha256-PaiITTFI2FfPylTEk7DwzfKeiA/g/aooSU1pDcdwWLU="; })
(fetchNuGet { pname = "runtime.any.System.IO"; version = "4.3.0"; hash = "sha256-vej7ySRhyvM3pYh/ITMdC25ivSd0WLZAaIQbYj/6HVE="; })
(fetchNuGet { pname = "runtime.any.System.Reflection"; version = "4.3.0"; hash = "sha256-ns6f++lSA+bi1xXgmW1JkWFb2NaMD+w+YNTfMvyAiQk="; })
(fetchNuGet { pname = "runtime.any.System.Reflection.Extensions"; version = "4.3.0"; hash = "sha256-Y2AnhOcJwJVYv7Rp6Jz6ma0fpITFqJW+8rsw106K2X8="; })
(fetchNuGet { pname = "runtime.any.System.Reflection.Primitives"; version = "4.3.0"; hash = "sha256-LkPXtiDQM3BcdYkAm5uSNOiz3uF4J45qpxn5aBiqNXQ="; })
(fetchNuGet { pname = "runtime.any.System.Resources.ResourceManager"; version = "4.3.0"; hash = "sha256-9EvnmZslLgLLhJ00o5MWaPuJQlbUFcUF8itGQNVkcQ4="; })
(fetchNuGet { pname = "runtime.any.System.Runtime"; version = "4.3.0"; hash = "sha256-qwhNXBaJ1DtDkuRacgHwnZmOZ1u9q7N8j0cWOLYOELM="; })
(fetchNuGet { pname = "runtime.any.System.Runtime.Handles"; version = "4.3.0"; hash = "sha256-PQRACwnSUuxgVySO1840KvqCC9F8iI9iTzxNW0RcBS4="; })
(fetchNuGet { pname = "runtime.any.System.Runtime.InteropServices"; version = "4.3.0"; hash = "sha256-Kaw5PnLYIiqWbsoF3VKJhy7pkpoGsUwn4ZDCKscbbzA="; })
(fetchNuGet { pname = "runtime.any.System.Text.Encoding"; version = "4.3.0"; hash = "sha256-Q18B9q26MkWZx68exUfQT30+0PGmpFlDgaF0TnaIGCs="; })
(fetchNuGet { pname = "runtime.any.System.Threading.Tasks"; version = "4.3.0"; hash = "sha256-agdOM0NXupfHbKAQzQT8XgbI9B8hVEh+a/2vqeHctg4="; })
(fetchNuGet { pname = "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-LXUPLX3DJxsU1Pd3UwjO1PO9NM2elNEDXeu2Mu/vNps="; })
(fetchNuGet { pname = "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-qeSqaUI80+lqw5MK4vMpmO0CZaqrmYktwp6L+vQAb0I="; })
(fetchNuGet { pname = "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-SrHqT9wrCBsxILWtaJgGKd6Odmxm8/Mh7Kh0CUkZVzA="; })
(fetchNuGet { pname = "runtime.native.System"; version = "4.3.0"; hash = "sha256-ZBZaodnjvLXATWpXXakFgcy6P+gjhshFXmglrL5xD5Y="; })
(fetchNuGet { pname = "runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-Jy01KhtcCl2wjMpZWH+X3fhHcVn+SyllWFY8zWlz/6I="; })
(fetchNuGet { pname = "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-wyv00gdlqf8ckxEdV7E+Ql9hJIoPcmYEuyeWb5Oz3mM="; })
(fetchNuGet { pname = "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-zi+b4sCFrA9QBiSGDD7xPV27r3iHGlV99gpyVUjRmc4="; })
(fetchNuGet { pname = "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-gybQU6mPgaWV3rBG2dbH6tT3tBq8mgze3PROdsuWnX0="; })
(fetchNuGet { pname = "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-VsP72GVveWnGUvS/vjOQLv1U80H2K8nZ4fDAmI61Hm4="; })
(fetchNuGet { pname = "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-4yKGa/IrNCKuQ3zaDzILdNPD32bNdy6xr5gdJigyF5g="; })
(fetchNuGet { pname = "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-HmdJhhRsiVoOOCcUvAwdjpMRiyuSwdcgEv2j9hxi+Zc="; })
(fetchNuGet { pname = "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; hash = "sha256-pVFUKuPPIx0edQKjzRon3zKq8zhzHEzko/lc01V/jdw="; })
(fetchNuGet { pname = "runtime.unix.System.Diagnostics.Debug"; version = "4.3.0"; hash = "sha256-ReoazscfbGH+R6s6jkg5sIEHWNEvjEoHtIsMbpc7+tI="; })
(fetchNuGet { pname = "runtime.unix.System.Private.Uri"; version = "4.3.0"; hash = "sha256-c5tXWhE/fYbJVl9rXs0uHh3pTsg44YD1dJvyOA0WoMs="; })
(fetchNuGet { pname = "runtime.unix.System.Runtime.Extensions"; version = "4.3.0"; hash = "sha256-l8S9gt6dk3qYG6HYonHtdlYtBKyPb29uQ6NDjmrt3V4="; })
(fetchNuGet { pname = "Ryujinx.AtkSharp"; version = "3.24.24.59-ryujinx"; hash = "sha256-mK1zXkd6bdo7YqOm/rcI8MTniugvs5Kjw+esGmHYJxE="; })
(fetchNuGet { pname = "Ryujinx.Audio.OpenAL.Dependencies"; version = "1.21.0.1"; hash = "sha256-NHGzMcYduuYJjduIlf8M8zSQQuJcXAEaMNmKIqAgs3w="; })
(fetchNuGet { pname = "Ryujinx.CairoSharp"; version = "3.24.24.59-ryujinx"; hash = "sha256-+gV4Vlkd0jMZ6yGCz1/KoiE32/O26gHOZiHXmZ292rE="; })
(fetchNuGet { pname = "Ryujinx.GdkSharp"; version = "3.24.24.59-ryujinx"; hash = "sha256-r1UK7YzhMOJ3Z8eWuUyTf0nGQ0Bdlic8Qri170ilEbs="; })
(fetchNuGet { pname = "Ryujinx.GioSharp"; version = "3.24.24.59-ryujinx"; hash = "sha256-sbfdn16UoQtcU9gAgvP2kjBUbYi9nKy09bmhvn9IGtU="; })
(fetchNuGet { pname = "Ryujinx.GLibSharp"; version = "3.24.24.59-ryujinx"; hash = "sha256-zZv0B4BvKuRdse8oqSbb4P6FFq79w4M+MCk8EqqLVWk="; })
(fetchNuGet { pname = "Ryujinx.Graphics.Nvdec.Dependencies"; version = "5.0.3-build14"; hash = "sha256-+ff+tlWggY+qZTBJroOOphRpOjBDg5cQgwGtVOTiqRQ="; })
(fetchNuGet { pname = "Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK"; version = "1.2.0"; hash = "sha256-vdDw6YGoyQzv6ustyXP6v7YWUIKEXaZOyUKAaVbRauI="; })
(fetchNuGet { pname = "Ryujinx.GtkSharp"; version = "3.24.24.59-ryujinx"; hash = "sha256-2duc6+KLuctobfwqeuewxRLZnXn83QomF4rN0hEoMTc="; })
(fetchNuGet { pname = "Ryujinx.PangoSharp"; version = "3.24.24.59-ryujinx"; hash = "sha256-gGAK/aEfTUAxEihjlBOtHlhPZZFAwCasgUB/Umapva0="; })
(fetchNuGet { pname = "Ryujinx.SDL2-CS"; version = "2.30.0-build32"; hash = "sha256-KrrlDq0pXcunnOhJL12dt1CAdNbaupbDlnza5gXuVKE="; })
(fetchNuGet { pname = "securifybv.PropertyStore"; version = "0.1.0"; hash = "sha256-jTPT9E2LyElgJq4HMavkdwT8tA9uklnJv00mlIx66+g="; })
(fetchNuGet { pname = "securifybv.ShellLink"; version = "0.1.0"; hash = "sha256-Am+ZednCVJUDgB7TePyY3CTxKDQ6Lr8M8KiCVAJoouw="; })
(fetchNuGet { pname = "shaderc.net"; version = "0.1.0"; hash = "sha256-+K7ObC9ucilwWY+Tlf9KcrAVoTFS65V6Di7JDWDSZTg="; })
(fetchNuGet { pname = "SharpZipLib"; version = "1.4.2"; hash = "sha256-/giVqikworG2XKqfN9uLyjUSXr35zBuZ2FX2r8X/WUY="; })
(fetchNuGet { pname = "ShimSkiaSharp"; version = "1.0.0.18"; hash = "sha256-72NV+OuW8bCfI/EOXwgS6dleLZnomLJTYeQPPmfhuu8="; })
(fetchNuGet { pname = "Silk.NET.Core"; version = "2.21.0"; hash = "sha256-D4ZUCm1Gf/EiRWrSwSLrdXT6U54icY2E/vrvCD/bRHw="; })
(fetchNuGet { pname = "Silk.NET.Vulkan"; version = "2.21.0"; hash = "sha256-Xnxl13+ziJ1+jNxMFSrEuh6NvL1FBrYmJ/d3HQXpgzY="; })
(fetchNuGet { pname = "Silk.NET.Vulkan.Extensions.EXT"; version = "2.21.0"; hash = "sha256-udELG0ppCOP9eT2yl/sI9MgKOVOuK0py9znmoaBGDpk="; })
(fetchNuGet { pname = "Silk.NET.Vulkan.Extensions.KHR"; version = "2.21.0"; hash = "sha256-KyiGHW6CNkXE3EdHk3ufwTVG7oLvSyHwx+MmIJhsiBk="; })
(fetchNuGet { pname = "SkiaSharp"; version = "2.88.3"; hash = "sha256-WyMAjnQt8ZsuWpGLI89l/f4bHvv+cg7FdTAL7CtJBvs="; })
(fetchNuGet { pname = "SkiaSharp"; version = "2.88.6"; hash = "sha256-y0wzgwdQXtgl5boCz/EgLWbK3SwC0cFVRUbBxOUPQXc="; })
(fetchNuGet { pname = "SkiaSharp"; version = "2.88.7"; hash = "sha256-Ip3afwTr4QOqtwOUKqK6g/9Ug4dMSebTci5K29Jc3Dg="; })
(fetchNuGet { pname = "SkiaSharp.HarfBuzz"; version = "2.88.6"; hash = "sha256-gpHiTuHfiXgbkBkzipXb8EXIatefsod75nyrFdPcwcA="; })
(fetchNuGet { pname = "SkiaSharp.NativeAssets.Linux"; version = "2.88.3"; hash = "sha256-eExWAAURgnwwm2fRwsK/rf+TeOAPs2n02XZzC0zeUjU="; })
(fetchNuGet { pname = "SkiaSharp.NativeAssets.Linux"; version = "2.88.7"; hash = "sha256-QdQRN1IBjqohmI8U+6WJRPgOsh8a9soN2UvVObs1H1w="; })
(fetchNuGet { pname = "SkiaSharp.NativeAssets.macOS"; version = "2.88.3"; hash = "sha256-8G4swiLMr6XS3kjfO/YC1PyoVdfSq7nxZthZZ+KTKqQ="; })
(fetchNuGet { pname = "SkiaSharp.NativeAssets.macOS"; version = "2.88.6"; hash = "sha256-7hOMjlYTOiNPLNwfLFUjTcdgiGEtmYUI1EubiRiC6bo="; })
(fetchNuGet { pname = "SkiaSharp.NativeAssets.macOS"; version = "2.88.7"; hash = "sha256-WgPldXSqPMm0TrdUWAyjge5rcRhd9G3/Ix/v/2NQvBc="; })
(fetchNuGet { pname = "SkiaSharp.NativeAssets.WebAssembly"; version = "2.88.3"; hash = "sha256-/SkV2pIZnt0ziSKB7gt7U2Rltk2Id+zOzbmqgfWUtvA="; })
(fetchNuGet { pname = "SkiaSharp.NativeAssets.WebAssembly"; version = "2.88.7"; hash = "sha256-oIjFF+Rv+g8AKyNaaVAgnHX3eeP/l8K2sgHs9bRyUMw="; })
(fetchNuGet { pname = "SkiaSharp.NativeAssets.Win32"; version = "2.88.3"; hash = "sha256-2PhMTwRHitT13KCKiZltKIFieAvNY4jBmVZ2ndVynA8="; })
(fetchNuGet { pname = "SkiaSharp.NativeAssets.Win32"; version = "2.88.6"; hash = "sha256-ljD4QmAO2/vwA6I8GIUNkONpOzmGsOVJJy9vPDnjVfA="; })
(fetchNuGet { pname = "SkiaSharp.NativeAssets.Win32"; version = "2.88.7"; hash = "sha256-+7RxCAr+ne9MZWdXKKpV4ZbHW0k6hLD20ZFWWOCiNYU="; })
(fetchNuGet { pname = "SPB"; version = "0.0.4-build32"; hash = "sha256-GUzbV5rLWtXTpiddYrKnWWLujG38vBDCO4xRStwAaDo="; })
(fetchNuGet { pname = "Svg.Custom"; version = "1.0.0.18"; hash = "sha256-RguRPwBM/KCogaiOgjELlvuqN1Tr+b3HA4Odz1rXBgU="; })
(fetchNuGet { pname = "Svg.Model"; version = "1.0.0.18"; hash = "sha256-CXZC45txfcd8MuRmDENw2ujlGk74YaUPNs7dXq+Zcg8="; })
(fetchNuGet { pname = "Svg.Skia"; version = "1.0.0.18"; hash = "sha256-o5VnCaAGX4LuwNyl7QM0KOg2gNfkD5uNMNthxB7w0m4="; })
(fetchNuGet { pname = "System.Buffers"; version = "4.5.1"; hash = "sha256-wws90sfi9M7kuCPWkv1CEYMJtCqx9QB/kj0ymlsNaxI="; })
(fetchNuGet { pname = "System.CodeDom"; version = "4.4.0"; hash = "sha256-L1xyspJ8pDJNVPYKl+FMGf4Zwm0tlqtAyQCNW6pT6/0="; })
(fetchNuGet { pname = "System.CodeDom"; version = "8.0.0"; hash = "sha256-uwVhi3xcvX7eiOGQi7dRETk3Qx1EfHsUfchZsEto338="; })
(fetchNuGet { pname = "System.Collections"; version = "4.3.0"; hash = "sha256-afY7VUtD6w/5mYqrce8kQrvDIfS2GXDINDh73IjxJKc="; })
(fetchNuGet { pname = "System.Collections.Immutable"; version = "5.0.0"; hash = "sha256-GdwSIjLMM0uVfE56VUSLVNgpW0B//oCeSFj8/hSlbM8="; })
(fetchNuGet { pname = "System.Collections.Immutable"; version = "8.0.0"; hash = "sha256-F7OVjKNwpqbUh8lTidbqJWYi476nsq9n+6k0+QVRo3w="; })
(fetchNuGet { pname = "System.ComponentModel.Annotations"; version = "4.5.0"; hash = "sha256-15yE2NoT9vmL9oGCaxHClQR1jLW1j1ef5hHMg55xRso="; })
(fetchNuGet { pname = "System.Diagnostics.Debug"; version = "4.3.0"; hash = "sha256-fkA79SjPbSeiEcrbbUsb70u9B7wqbsdM9s1LnoKj0gM="; })
(fetchNuGet { pname = "System.Dynamic.Runtime"; version = "4.3.0"; hash = "sha256-k75gjOYimIQtLBD5NDzwwi3ZMUBPRW3jmc3evDMMJbU="; })
(fetchNuGet { pname = "System.Globalization"; version = "4.3.0"; hash = "sha256-caL0pRmFSEsaoeZeWN5BTQtGrAtaQPwFi8YOZPZG5rI="; })
(fetchNuGet { pname = "System.IO"; version = "4.3.0"; hash = "sha256-ruynQHekFP5wPrDiVyhNiRIXeZ/I9NpjK5pU+HPDiRY="; })
(fetchNuGet { pname = "System.IO.Hashing"; version = "8.0.0"; hash = "sha256-szOGt0TNBo6dEdC3gf6H+e9YW3Nw0woa6UnCGGGK5cE="; })
(fetchNuGet { pname = "System.IO.Pipelines"; version = "6.0.0"; hash = "sha256-xfjF4UqTMJpf8KsBWUyJlJkzPTOO/H5MW023yTYNQSA="; })
(fetchNuGet { pname = "System.Linq"; version = "4.3.0"; hash = "sha256-R5uiSL3l6a3XrXSSL6jz+q/PcyVQzEAByiuXZNSqD/A="; })
(fetchNuGet { pname = "System.Linq.Expressions"; version = "4.3.0"; hash = "sha256-+3pvhZY7rip8HCbfdULzjlC9FPZFpYoQxhkcuFm2wk8="; })
(fetchNuGet { pname = "System.Management"; version = "8.0.0"; hash = "sha256-HwpfDb++q7/vxR6q57mGFgl5U0vxy+oRJ6orFKORfP0="; })
(fetchNuGet { pname = "System.Memory"; version = "4.5.4"; hash = "sha256-3sCEfzO4gj5CYGctl9ZXQRRhwAraMQfse7yzKoRe65E="; })
(fetchNuGet { pname = "System.Memory"; version = "4.5.5"; hash = "sha256-EPQ9o1Kin7KzGI5O3U3PUQAZTItSbk9h/i4rViN3WiI="; })
(fetchNuGet { pname = "System.Numerics.Vectors"; version = "4.3.0"; hash = "sha256-eog2Sp8CAntRlyp2Aar1tpAwDrojGFZ5LIdqsmuIchY="; })
(fetchNuGet { pname = "System.Numerics.Vectors"; version = "4.4.0"; hash = "sha256-auXQK2flL/JpnB/rEcAcUm4vYMCYMEMiWOCAlIaqu2U="; })
(fetchNuGet { pname = "System.Numerics.Vectors"; version = "4.5.0"; hash = "sha256-qdSTIFgf2htPS+YhLGjAGiLN8igCYJnCCo6r78+Q+c8="; })
(fetchNuGet { pname = "System.ObjectModel"; version = "4.3.0"; hash = "sha256-gtmRkWP2Kwr3nHtDh0yYtce38z1wrGzb6fjm4v8wN6Q="; })
(fetchNuGet { pname = "System.Private.Uri"; version = "4.3.0"; hash = "sha256-fVfgcoP4AVN1E5wHZbKBIOPYZ/xBeSIdsNF+bdukIRM="; })
(fetchNuGet { pname = "System.Reactive"; version = "6.0.1"; hash = "sha256-Lo5UMqp8DsbVSUxa2UpClR1GoYzqQQcSxkfyFqB/d4Q="; })
(fetchNuGet { pname = "System.Reflection"; version = "4.3.0"; hash = "sha256-NQSZRpZLvtPWDlvmMIdGxcVuyUnw92ZURo0hXsEshXY="; })
(fetchNuGet { pname = "System.Reflection.Emit"; version = "4.3.0"; hash = "sha256-5LhkDmhy2FkSxulXR+bsTtMzdU3VyyuZzsxp7/DwyIU="; })
(fetchNuGet { pname = "System.Reflection.Emit.ILGeneration"; version = "4.3.0"; hash = "sha256-mKRknEHNls4gkRwrEgi39B+vSaAz/Gt3IALtS98xNnA="; })
(fetchNuGet { pname = "System.Reflection.Emit.Lightweight"; version = "4.3.0"; hash = "sha256-rKx4a9yZKcajloSZHr4CKTVJ6Vjh95ni+zszPxWjh2I="; })
(fetchNuGet { pname = "System.Reflection.Extensions"; version = "4.3.0"; hash = "sha256-mMOCYzUenjd4rWIfq7zIX9PFYk/daUyF0A8l1hbydAk="; })
(fetchNuGet { pname = "System.Reflection.Metadata"; version = "1.6.0"; hash = "sha256-JJfgaPav7UfEh4yRAQdGhLZF1brr0tUWPl6qmfNWq/E="; })
(fetchNuGet { pname = "System.Reflection.Metadata"; version = "5.0.0"; hash = "sha256-Wo+MiqhcP9dQ6NuFGrQTw6hpbJORFwp+TBNTq2yhGp8="; })
(fetchNuGet { pname = "System.Reflection.Metadata"; version = "8.0.0"; hash = "sha256-dQGC30JauIDWNWXMrSNOJncVa1umR1sijazYwUDdSIE="; })
(fetchNuGet { pname = "System.Reflection.Primitives"; version = "4.3.0"; hash = "sha256-5ogwWB4vlQTl3jjk1xjniG2ozbFIjZTL9ug0usZQuBM="; })
(fetchNuGet { pname = "System.Reflection.TypeExtensions"; version = "4.3.0"; hash = "sha256-4U4/XNQAnddgQIHIJq3P2T80hN0oPdU2uCeghsDTWng="; })
(fetchNuGet { pname = "System.Resources.ResourceManager"; version = "4.3.0"; hash = "sha256-idiOD93xbbrbwwSnD4mORA9RYi/D/U48eRUsn/WnWGo="; })
(fetchNuGet { pname = "System.Runtime"; version = "4.3.0"; hash = "sha256-51813WXpBIsuA6fUtE5XaRQjcWdQ2/lmEokJt97u0Rg="; })
(fetchNuGet { pname = "System.Runtime.CompilerServices.Unsafe"; version = "4.7.1"; hash = "sha256-UvyoDV8O0oY3HPG1GbA56YVdvwTGEfjYR5gW1O7IK4U="; })
(fetchNuGet { pname = "System.Runtime.CompilerServices.Unsafe"; version = "5.0.0"; hash = "sha256-neARSpLPUzPxEKhJRwoBzhPxK+cKIitLx7WBYncsYgo="; })
(fetchNuGet { pname = "System.Runtime.CompilerServices.Unsafe"; version = "6.0.0"; hash = "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I="; })
(fetchNuGet { pname = "System.Runtime.Extensions"; version = "4.3.0"; hash = "sha256-wLDHmozr84v1W2zYCWYxxj0FR0JDYHSVRaRuDm0bd/o="; })
(fetchNuGet { pname = "System.Runtime.Handles"; version = "4.3.0"; hash = "sha256-KJ5aXoGpB56Y6+iepBkdpx/AfaJDAitx4vrkLqR7gms="; })
(fetchNuGet { pname = "System.Runtime.InteropServices"; version = "4.3.0"; hash = "sha256-8sDH+WUJfCR+7e4nfpftj/+lstEiZixWUBueR2zmHgI="; })
(fetchNuGet { pname = "System.Security.AccessControl"; version = "4.5.0"; hash = "sha256-AFsKPb/nTk2/mqH/PYpaoI8PLsiKKimaXf+7Mb5VfPM="; })
(fetchNuGet { pname = "System.Security.Principal.Windows"; version = "4.5.0"; hash = "sha256-BkUYNguz0e4NJp1kkW7aJBn3dyH9STwB5N8XqnlCsmY="; })
(fetchNuGet { pname = "System.Text.Encoding"; version = "4.3.0"; hash = "sha256-GctHVGLZAa/rqkBNhsBGnsiWdKyv6VDubYpGkuOkBLg="; })
(fetchNuGet { pname = "System.Text.Encoding.CodePages"; version = "4.5.1"; hash = "sha256-PIhkv59IXjyiuefdhKxS9hQfEwO9YWRuNudpo53HQfw="; })
(fetchNuGet { pname = "System.Text.Encoding.CodePages"; version = "8.0.0"; hash = "sha256-fjCLQc1PRW0Ix5IZldg0XKv+J1DqPSfu9pjMyNBp7dE="; })
(fetchNuGet { pname = "System.Text.Encodings.Web"; version = "8.0.0"; hash = "sha256-IUQkQkV9po1LC0QsqrilqwNzPvnc+4eVvq+hCvq8fvE="; })
(fetchNuGet { pname = "System.Text.Json"; version = "8.0.0"; hash = "sha256-XFcCHMW1u2/WujlWNHaIWkbW1wn8W4kI0QdrwPtWmow="; })
(fetchNuGet { pname = "System.Threading"; version = "4.3.0"; hash = "sha256-ZDQ3dR4pzVwmaqBg4hacZaVenQ/3yAF/uV7BXZXjiWc="; })
(fetchNuGet { pname = "System.Threading.Tasks"; version = "4.3.0"; hash = "sha256-Z5rXfJ1EXp3G32IKZGiZ6koMjRu0n8C1NGrwpdIen4w="; })
(fetchNuGet { pname = "System.Threading.Tasks.Extensions"; version = "4.5.4"; hash = "sha256-owSpY8wHlsUXn5xrfYAiu847L6fAKethlvYx97Ri1ng="; })
(fetchNuGet { pname = "Tmds.DBus.Protocol"; version = "0.15.0"; hash = "sha256-4gk2vXDjKFaBh82gTkwg3c/5GRjiH+bvM5elfDSbKTU="; })
(fetchNuGet { pname = "UnicornEngine.Unicorn"; version = "2.0.2-rc1-fb78016"; hash = "sha256-NrJ4/o4FmCt2zoB1fWAzqdonvpYhTFsWwh3h0lxZg+Q="; })
]

61
flake.lock Normal file
View file

@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1727634051,
"narHash": "sha256-S5kVU7U82LfpEukbn/ihcyNt2+EvG7Z5unsKW9H/yFA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "06cf0e1da4208d3766d898b7fdab6513366d45b9",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

38
flake.nix Normal file
View file

@ -0,0 +1,38 @@
{
description = "Experimental Nintendo Switch Emulator written in C#";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{ self
, nixpkgs
, flake-utils
}: (flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system: {
packages.default =
let
pkgs = import nixpkgs {
inherit system;
overlays = [
self.overlays.default
];
};
in
pkgs.ryujinx;
})) // {
nixosModules.default = { config, lib, pkgs, ... }: {
nixpkgs.overlays = [
self.overlays.default
];
};
overlays.default = final: prev: {
ryujinx = final.callPackage ./package.nix {
src = self;
};
};
};
}

111
package.nix Normal file
View file

@ -0,0 +1,111 @@
{ lib
, buildDotnetModule
, dotnetCorePackages
, libX11
, libgdiplus
, ffmpeg
, openal
, libsoundio
, sndio
, pulseaudio
, vulkan-loader
, glew
, libGL
, udev
, SDL2
, SDL2_mixer
, src
}:
buildDotnetModule {
pname = "ryujinx";
version = "1.1.1401"; # Based off of the official github actions builds: https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml
inherit src;
enableParallelBuilding = false;
dotnet-sdk = dotnetCorePackages.sdk_8_0;
dotnet-runtime = dotnetCorePackages.runtime_8_0;
nugetDeps = ./deps.nix;
runtimeDeps = [
libX11
libgdiplus
SDL2_mixer
openal
libsoundio
sndio
pulseaudio
vulkan-loader
ffmpeg
udev
# Avalonia UI
glew
# Headless executable
libGL
SDL2
];
projectFile = "Ryujinx.sln";
testProjectFile = "src/Ryujinx.Tests/Ryujinx.Tests.csproj";
doCheck = true;
dotnetFlags = [
"/p:ExtraDefineConstants=DISABLE_UPDATER%2CFORCE_EXTERNAL_BASE_DIR"
];
executables = [
"Ryujinx.Headless.SDL2"
"Ryujinx"
];
makeWrapperArgs = [
# Without this Ryujinx fails to start on wayland. See https://github.com/Ryujinx/Ryujinx/issues/2714
"--set SDL_VIDEODRIVER x11"
];
preInstall = ''
# workaround for https://github.com/Ryujinx/Ryujinx/issues/2349
mkdir -p $out/lib/sndio-6
ln -s ${sndio}/lib/libsndio.so $out/lib/sndio-6/libsndio.so.6
'';
preFixup = ''
mkdir -p $out/share/{applications,icons/hicolor/scalable/apps,mime/packages}
pushd ${src}/distribution/linux
install -D ./Ryujinx.desktop $out/share/applications/Ryujinx.desktop
install -D ./Ryujinx.sh $out/bin/Ryujinx.sh
install -D ./mime/Ryujinx.xml $out/share/mime/packages/Ryujinx.xml
install -D ../misc/Logo.svg $out/share/icons/hicolor/scalable/apps/Ryujinx.svg
substituteInPlace $out/share/applications/Ryujinx.desktop \
--replace "Ryujinx.sh %f" "$out/bin/Ryujinx.sh %f"
ln -s $out/bin/Ryujinx $out/bin/ryujinx
popd
'';
passthru.updateScript = ./updater.sh;
meta = with lib; {
homepage = "https://ryujinx.org/";
changelog = "https://github.com/Ryujinx/Ryujinx/wiki/Changelog";
description = "Experimental Nintendo Switch Emulator written in C#";
longDescription = ''
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan,
written in C#. This emulator aims at providing excellent accuracy and
performance, a user-friendly interface and consistent builds. It was
written from scratch and development on the project began in September
2017.
'';
license = licenses.mit;
maintainers = with maintainers; [ jk artemist ];
platforms = [ "x86_64-linux" "aarch64-linux" ];
mainProgram = "Ryujinx";
};
}

View file

@ -1,9 +1,11 @@
using Ryujinx.Audio.Renderer.Dsp.Effect; using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Dsp.State; using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Parameter.Effect; using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Server.Effect; using Ryujinx.Audio.Renderer.Server.Effect;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Dsp.Command namespace Ryujinx.Audio.Renderer.Dsp.Command
{ {
@ -21,18 +23,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CompressorParameter Parameter => _parameter; public CompressorParameter Parameter => _parameter;
public Memory<CompressorState> State { get; } public Memory<CompressorState> State { get; }
public Memory<EffectResultState> ResultState { get; }
public ushort[] OutputBufferIndices { get; } public ushort[] OutputBufferIndices { get; }
public ushort[] InputBufferIndices { get; } public ushort[] InputBufferIndices { get; }
public bool IsEffectEnabled { get; } public bool IsEffectEnabled { get; }
private CompressorParameter _parameter; private CompressorParameter _parameter;
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId) public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, Memory<EffectResultState> resultState, bool isEnabled, int nodeId)
{ {
Enabled = true; Enabled = true;
NodeId = nodeId; NodeId = nodeId;
_parameter = parameter; _parameter = parameter;
State = state; State = state;
ResultState = resultState;
IsEffectEnabled = isEnabled; IsEffectEnabled = isEnabled;
@ -71,9 +75,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (IsEffectEnabled && _parameter.IsChannelCountValid()) if (IsEffectEnabled && _parameter.IsChannelCountValid())
{ {
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; if (!ResultState.IsEmpty && _parameter.StatisticsReset)
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; {
Span<float> channelInput = stackalloc float[Parameter.ChannelCount]; ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(ResultState.Span[0].SpecificData)[0];
statistics.Reset(_parameter.ChannelCount);
}
Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
Span<float> channelInput = stackalloc float[_parameter.ChannelCount];
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage; ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
float unknown4 = state.Unknown4; float unknown4 = state.Unknown4;
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage; ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
@ -92,7 +103,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex); channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
} }
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain); float mean = FloatingPointHelper.MeanSquare(channelInput);
float newMean = inputMovingAverage.Update(mean, _parameter.InputGain);
float y = FloatingPointHelper.Log10(newMean) * 10.0f; float y = FloatingPointHelper.Log10(newMean) * 10.0f;
float z = 1.0f; float z = 1.0f;
@ -111,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (y >= state.Unknown14) if (y >= state.Unknown14)
{ {
tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold); tmpGain = ((1.0f / _parameter.Ratio) - 1.0f) * (y - _parameter.Threshold);
} }
else else
{ {
@ -126,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if ((unknown4 - z) <= 0.08f) if ((unknown4 - z) <= 0.08f)
{ {
compressionEmaAlpha = Parameter.ReleaseCoefficient; compressionEmaAlpha = _parameter.ReleaseCoefficient;
if ((unknown4 - z) >= -0.08f) if ((unknown4 - z) >= -0.08f)
{ {
@ -140,18 +152,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
} }
else else
{ {
compressionEmaAlpha = Parameter.AttackCoefficient; compressionEmaAlpha = _parameter.AttackCoefficient;
} }
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha); float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{ {
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain; *((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
} }
unknown4 = unknown4New; unknown4 = unknown4New;
previousCompressionEmaAlpha = compressionEmaAlpha; previousCompressionEmaAlpha = compressionEmaAlpha;
if (!ResultState.IsEmpty)
{
ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(ResultState.Span[0].SpecificData)[0];
statistics.MinimumGain = MathF.Min(statistics.MinimumGain, compressionGain * state.OutputGain);
statistics.MaximumMean = MathF.Max(statistics.MaximumMean, mean);
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
statistics.LastSamples[channelIndex] = MathF.Abs(channelInput[channelIndex] * (1f / 32768f));
}
}
} }
state.InputMovingAverage = inputMovingAverage; state.InputMovingAverage = inputMovingAverage;
@ -161,7 +186,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
} }
else else
{ {
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
if (InputBufferIndices[i] != OutputBufferIndices[i]) if (InputBufferIndices[i] != OutputBufferIndices[i])
{ {

View file

@ -38,10 +38,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
} }
} }
@ -51,11 +51,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (IsEffectEnabled) if (IsEffectEnabled)
{ {
if (Parameter.Status == UsageState.Invalid) if (_parameter.Status == UsageState.Invalid)
{ {
state = new LimiterState(ref _parameter, WorkBuffer); state = new LimiterState(ref _parameter, WorkBuffer);
} }
else if (Parameter.Status == UsageState.New) else if (_parameter.Status == UsageState.New)
{ {
LimiterState.UpdateParameter(ref _parameter); LimiterState.UpdateParameter(ref _parameter);
} }
@ -66,56 +66,56 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state) private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
{ {
Debug.Assert(Parameter.IsChannelCountValid()); Debug.Assert(_parameter.IsChannelCountValid());
if (IsEffectEnabled && Parameter.IsChannelCountValid()) if (IsEffectEnabled && _parameter.IsChannelCountValid())
{ {
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
} }
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{ {
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{ {
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex); float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain; float inputSample = (rawInputSample / short.MaxValue) * _parameter.InputGain;
float sampleInputMax = Math.Abs(inputSample); float sampleInputMax = Math.Abs(inputSample);
float inputCoefficient = Parameter.ReleaseCoefficient; float inputCoefficient = _parameter.ReleaseCoefficient;
if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{ {
inputCoefficient = Parameter.AttackCoefficient; inputCoefficient = _parameter.AttackCoefficient;
} }
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f; float attenuation = 1.0f;
if (detectorValue > Parameter.Threshold) if (detectorValue > _parameter.Threshold)
{ {
attenuation = Parameter.Threshold / detectorValue; attenuation = _parameter.Threshold / detectorValue;
} }
float outputCoefficient = Parameter.ReleaseCoefficient; float outputCoefficient = _parameter.ReleaseCoefficient;
if (state.CompressionGainAverage[channelIndex].Read() > attenuation) if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{ {
outputCoefficient = Parameter.AttackCoefficient; outputCoefficient = _parameter.AttackCoefficient;
} }
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient); float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]]; ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * _parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
float outputSample = delayedSample * compressionGain * Parameter.OutputGain; float outputSample = delayedSample * compressionGain * _parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue; *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
@ -123,16 +123,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
state.DelayedSampleBufferPosition[channelIndex]++; state.DelayedSampleBufferPosition[channelIndex]++;
while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin) while (state.DelayedSampleBufferPosition[channelIndex] >= _parameter.DelayBufferSampleCountMin)
{ {
state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin; state.DelayedSampleBufferPosition[channelIndex] -= _parameter.DelayBufferSampleCountMin;
} }
} }
} }
} }
else else
{ {
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
if (InputBufferIndices[i] != OutputBufferIndices[i]) if (InputBufferIndices[i] != OutputBufferIndices[i])
{ {

View file

@ -49,10 +49,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
} }
} }
@ -62,11 +62,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (IsEffectEnabled) if (IsEffectEnabled)
{ {
if (Parameter.Status == UsageState.Invalid) if (_parameter.Status == UsageState.Invalid)
{ {
state = new LimiterState(ref _parameter, WorkBuffer); state = new LimiterState(ref _parameter, WorkBuffer);
} }
else if (Parameter.Status == UsageState.New) else if (_parameter.Status == UsageState.New)
{ {
LimiterState.UpdateParameter(ref _parameter); LimiterState.UpdateParameter(ref _parameter);
} }
@ -77,63 +77,63 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state) private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
{ {
Debug.Assert(Parameter.IsChannelCountValid()); Debug.Assert(_parameter.IsChannelCountValid());
if (IsEffectEnabled && Parameter.IsChannelCountValid()) if (IsEffectEnabled && _parameter.IsChannelCountValid())
{ {
if (!ResultState.IsEmpty && Parameter.StatisticsReset) if (!ResultState.IsEmpty && _parameter.StatisticsReset)
{ {
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0]; ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
statistics.Reset(); statistics.Reset();
} }
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
} }
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{ {
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{ {
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex); float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain; float inputSample = (rawInputSample / short.MaxValue) * _parameter.InputGain;
float sampleInputMax = Math.Abs(inputSample); float sampleInputMax = Math.Abs(inputSample);
float inputCoefficient = Parameter.ReleaseCoefficient; float inputCoefficient = _parameter.ReleaseCoefficient;
if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{ {
inputCoefficient = Parameter.AttackCoefficient; inputCoefficient = _parameter.AttackCoefficient;
} }
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f; float attenuation = 1.0f;
if (detectorValue > Parameter.Threshold) if (detectorValue > _parameter.Threshold)
{ {
attenuation = Parameter.Threshold / detectorValue; attenuation = _parameter.Threshold / detectorValue;
} }
float outputCoefficient = Parameter.ReleaseCoefficient; float outputCoefficient = _parameter.ReleaseCoefficient;
if (state.CompressionGainAverage[channelIndex].Read() > attenuation) if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{ {
outputCoefficient = Parameter.AttackCoefficient; outputCoefficient = _parameter.AttackCoefficient;
} }
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient); float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]]; ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * _parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
float outputSample = delayedSample * compressionGain * Parameter.OutputGain; float outputSample = delayedSample * compressionGain * _parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue; *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
@ -141,9 +141,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
state.DelayedSampleBufferPosition[channelIndex]++; state.DelayedSampleBufferPosition[channelIndex]++;
while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin) while (state.DelayedSampleBufferPosition[channelIndex] >= _parameter.DelayBufferSampleCountMin)
{ {
state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin; state.DelayedSampleBufferPosition[channelIndex] -= _parameter.DelayBufferSampleCountMin;
} }
if (!ResultState.IsEmpty) if (!ResultState.IsEmpty)
@ -158,7 +158,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
} }
else else
{ {
for (int i = 0; i < Parameter.ChannelCount; i++) for (int i = 0; i < _parameter.ChannelCount; i++)
{ {
if (InputBufferIndices[i] != OutputBufferIndices[i]) if (InputBufferIndices[i] != OutputBufferIndices[i])
{ {

View file

@ -90,9 +90,16 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
public bool MakeupGainEnabled; public bool MakeupGainEnabled;
/// <summary> /// <summary>
/// Reserved/padding. /// Indicate if the compressor effect should output statistics.
/// </summary> /// </summary>
private Array2<byte> _reserved; [MarshalAs(UnmanagedType.I1)]
public bool StatisticsEnabled;
/// <summary>
/// Indicate to the DSP that the user did a statistics reset.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool StatisticsReset;
/// <summary> /// <summary>
/// Check if the <see cref="ChannelCount"/> is valid. /// Check if the <see cref="ChannelCount"/> is valid.

View file

@ -0,0 +1,38 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// Effect result state for <seealso cref="Common.EffectType.Compressor"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CompressorStatistics
{
/// <summary>
/// Maximum input mean value since last reset.
/// </summary>
public float MaximumMean;
/// <summary>
/// Minimum output gain since last reset.
/// </summary>
public float MinimumGain;
/// <summary>
/// Last processed input sample, per channel.
/// </summary>
public Array6<float> LastSamples;
/// <summary>
/// Reset the statistics.
/// </summary>
/// <param name="channelCount">Number of channels to reset.</param>
public void Reset(ushort channelCount)
{
MaximumMean = 0.0f;
MinimumGain = 1.0f;
LastSamples.AsSpan()[..channelCount].Clear();
}
}
}

View file

@ -28,6 +28,11 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// </summary> /// </summary>
bool IsUsed { get; } bool IsUsed { get; }
/// <summary>
/// Set to true to force resetting the previous mix volumes.
/// </summary>
bool ResetPrevVolume { get; }
/// <summary> /// <summary>
/// Mix buffer volumes. /// Mix buffer volumes.
/// </summary> /// </summary>

View file

@ -37,10 +37,16 @@ namespace Ryujinx.Audio.Renderer.Parameter
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool IsUsed; public bool IsUsed;
/// <summary>
/// Set to true to force resetting the previous mix volumes.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool ResetPrevVolume;
/// <summary> /// <summary>
/// Reserved/padding. /// Reserved/padding.
/// </summary> /// </summary>
private unsafe fixed byte _reserved[3]; private unsafe fixed byte _reserved[2];
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { } private struct MixArray { }
@ -58,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default; readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default;
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed; readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
/// <summary> /// <summary>
/// The expected constant of any input header. /// The expected constant of any input header.

View file

@ -42,10 +42,16 @@ namespace Ryujinx.Audio.Renderer.Parameter
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool IsUsed; public bool IsUsed;
/// <summary>
/// Set to true to force resetting the previous mix volumes.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool ResetPrevVolume;
/// <summary> /// <summary>
/// Reserved/padding. /// Reserved/padding.
/// </summary> /// </summary>
private unsafe fixed byte _reserved[11]; private unsafe fixed byte _reserved[10];
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { } private struct MixArray { }
@ -63,6 +69,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters; readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed; readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
/// <summary> /// <summary>
/// The expected constant of any input header. /// The expected constant of any input header.

View file

@ -108,10 +108,18 @@ namespace Ryujinx.Audio.Renderer.Server
/// <remarks>This was added in system update 17.0.0</remarks> /// <remarks>This was added in system update 17.0.0</remarks>
public const int Revision12 = 12 << 24; public const int Revision12 = 12 << 24;
/// <summary>
/// REV13:
/// The compressor effect can now output statistics.
/// Splitter destinations now explicitly reset the previous mix volume, instead of doing so on first use.
/// </summary>
/// <remarks>This was added in system update 18.0.0</remarks>
public const int Revision13 = 13 << 24;
/// <summary> /// <summary>
/// Last revision supported by the implementation. /// Last revision supported by the implementation.
/// </summary> /// </summary>
public const int LastRevision = Revision12; public const int LastRevision = Revision13;
/// <summary> /// <summary>
/// Target revision magic supported by the implementation. /// Target revision magic supported by the implementation.
@ -384,6 +392,15 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12); return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
} }
/// <summary>
/// Check if the audio renderer should support explicit previous mix volume reset on splitter.
/// </summary>
/// <returns>True if the audio renderer support explicit previous mix volume reset on splitter</returns>
public bool IsSplitterPrevVolumeResetSupported()
{
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision13);
}
/// <summary> /// <summary>
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>. /// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
/// </summary> /// </summary>

View file

@ -583,11 +583,20 @@ namespace Ryujinx.Audio.Renderer.Server
} }
} }
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId) /// <summary>
/// Generate a new <see cref="CompressorCommand"/>.
/// </summary>
/// <param name="bufferOffset">The target buffer offset.</param>
/// <param name="parameter">The compressor parameter.</param>
/// <param name="state">The compressor state.</param>
/// <param name="effectResultState">The DSP effect result state.</param>
/// <param name="isEnabled">Set to true if the effect should be active.</param>
/// <param name="nodeId">The node id associated to this command.</param>
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, Memory<EffectResultState> effectResultState, bool isEnabled, int nodeId)
{ {
if (parameter.IsChannelCountValid()) if (parameter.IsChannelCountValid())
{ {
CompressorCommand command = new(bufferOffset, parameter, state, isEnabled, nodeId); CompressorCommand command = new(bufferOffset, parameter, state, effectResultState, isEnabled, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);

View file

@ -735,14 +735,26 @@ namespace Ryujinx.Audio.Renderer.Server
} }
} }
private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId) private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId, int effectId)
{ {
Debug.Assert(effect.Type == EffectType.Compressor); Debug.Assert(effect.Type == EffectType.Compressor);
Memory<EffectResultState> dspResultState;
if (effect.Parameter.StatisticsEnabled)
{
dspResultState = _effectContext.GetDspStateMemory(effectId);
}
else
{
dspResultState = Memory<EffectResultState>.Empty;
}
_commandBuffer.GenerateCompressorEffect( _commandBuffer.GenerateCompressorEffect(
bufferOffset, bufferOffset,
effect.Parameter, effect.Parameter,
effect.State, effect.State,
dspResultState,
effect.IsEnabled, effect.IsEnabled,
nodeId); nodeId);
} }
@ -795,7 +807,7 @@ namespace Ryujinx.Audio.Renderer.Server
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId); GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
break; break;
case EffectType.Compressor: case EffectType.Compressor:
GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId); GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId, effectId);
break; break;
default: default:
throw new NotImplementedException($"Unsupported effect type {effect.Type}"); throw new NotImplementedException($"Unsupported effect type {effect.Type}");

View file

@ -169,14 +169,28 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
if (command.Enabled) if (command.Enabled)
{ {
return command.Parameter.ChannelCount switch if (command.Parameter.StatisticsEnabled)
{ {
1 => 34431, return command.Parameter.ChannelCount switch
2 => 44253, {
4 => 63827, 1 => 22100,
6 => 83361, 2 => 33211,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), 4 => 41587,
}; 6 => 58819,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
};
}
else
{
return command.Parameter.ChannelCount switch
{
1 => 19052,
2 => 29852,
4 => 37904,
6 => 55020,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
};
}
} }
return command.Parameter.ChannelCount switch return command.Parameter.ChannelCount switch
@ -191,14 +205,28 @@ namespace Ryujinx.Audio.Renderer.Server
if (command.Enabled) if (command.Enabled)
{ {
return command.Parameter.ChannelCount switch if (command.Parameter.StatisticsEnabled)
{ {
1 => 51095, return command.Parameter.ChannelCount switch
2 => 65693, {
4 => 95383, 1 => 32518,
6 => 124510, 2 => 49102,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), 4 => 61685,
}; 6 => 87250,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
};
}
else
{
return command.Parameter.ChannelCount switch
{
1 => 27963,
2 => 44016,
4 => 56183,
6 => 81862,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
};
}
} }
return command.Parameter.ChannelCount switch return command.Parameter.ChannelCount switch

View file

@ -62,6 +62,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
UpdateUsageStateForCommandGeneration(); UpdateUsageStateForCommandGeneration();
Parameter.Status = UsageState.Enabled; Parameter.Status = UsageState.Enabled;
Parameter.StatisticsReset = false;
}
public override void InitializeResultState(ref EffectResultState state)
{
ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(state.SpecificData)[0];
statistics.Reset(Parameter.ChannelCount);
}
public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState)
{
destState = srcState;
} }
} }
} }

View file

@ -51,6 +51,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// </summary> /// </summary>
public bool IsBugFixed { get; private set; } public bool IsBugFixed { get; private set; }
/// <summary>
/// If set to true, the previous mix volume is explicitly resetted using the input parameter, instead of implicitly on first use.
/// </summary>
public bool IsSplitterPrevVolumeResetSupported { get; private set; }
/// <summary> /// <summary>
/// Initialize <see cref="SplitterContext"/>. /// Initialize <see cref="SplitterContext"/>.
/// </summary> /// </summary>
@ -139,6 +144,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
} }
} }
IsSplitterPrevVolumeResetSupported = behaviourContext.IsSplitterPrevVolumeResetSupported();
SplitterState.InitializeSplitters(splitters.Span); SplitterState.InitializeSplitters(splitters.Span);
Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed()); Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
@ -277,7 +284,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{ {
SplitterDestination destination = GetDestination(parameter.Id); SplitterDestination destination = GetDestination(parameter.Id);
destination.Update(parameter); destination.Update(parameter, IsSplitterPrevVolumeResetSupported);
} }
return true; return true;

View file

@ -184,15 +184,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Update the splitter destination data from user parameter. /// Update the splitter destination data from user parameter.
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter /// <param name="isPrevVolumeResetSupported">Indicates that the audio renderer revision in use supports explicitly resetting the volume.</param>
public void Update<T>(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
{ {
if (Unsafe.IsNullRef(ref _v2)) if (Unsafe.IsNullRef(ref _v2))
{ {
_v1.Update(parameter); _v1.Update(parameter, isPrevVolumeResetSupported);
} }
else else
{ {
_v2.Update(parameter); _v2.Update(parameter, isPrevVolumeResetSupported);
} }
} }

View file

@ -93,7 +93,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Update the <see cref="SplitterDestinationVersion1"/> from user parameter. /// Update the <see cref="SplitterDestinationVersion1"/> from user parameter.
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter /// <param name="isPrevVolumeResetSupported">Indicates that the audio renderer revision in use supports explicitly resetting the volume.</param>
public void Update<T>(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
{ {
Debug.Assert(Id == parameter.Id); Debug.Assert(Id == parameter.Id);
@ -103,7 +104,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
parameter.MixBufferVolume.CopyTo(MixBufferVolume); parameter.MixBufferVolume.CopyTo(MixBufferVolume);
if (!IsUsed && parameter.IsUsed) bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed;
if (resetPrevVolume)
{ {
MixBufferVolume.CopyTo(PreviousMixBufferVolume); MixBufferVolume.CopyTo(PreviousMixBufferVolume);

View file

@ -98,7 +98,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Update the <see cref="SplitterDestinationVersion2"/> from user parameter. /// Update the <see cref="SplitterDestinationVersion2"/> from user parameter.
/// </summary> /// </summary>
/// <param name="parameter">The user parameter.</param> /// <param name="parameter">The user parameter.</param>
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter /// <param name="isPrevVolumeResetSupported">Indicates that the audio renderer revision in use supports explicitly resetting the volume.</param>
public void Update<T>(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
{ {
Debug.Assert(Id == parameter.Id); Debug.Assert(Id == parameter.Id);
@ -110,7 +111,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
_biquadFilters = parameter.BiquadFilters; _biquadFilters = parameter.BiquadFilters;
if (!IsUsed && parameter.IsUsed) bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed;
if (resetPrevVolume)
{ {
MixBufferVolume.CopyTo(PreviousMixBufferVolume); MixBufferVolume.CopyTo(PreviousMixBufferVolume);

View file

@ -8,5 +8,6 @@ namespace Ryujinx.Horizon.Sdk.Audio
public static Result DeviceNotFound => new(ModuleId, 1); public static Result DeviceNotFound => new(ModuleId, 1);
public static Result UnsupportedRevision => new(ModuleId, 2); public static Result UnsupportedRevision => new(ModuleId, 2);
public static Result NotImplemented => new(ModuleId, 513);
} }
} }

View file

@ -233,6 +233,48 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
return Result.Success; return Result.Success;
} }
[CmifCommand(15)] // 17.0.0+
public Result AcquireAudioOutputDeviceNotification([CopyHandle] out int eventHandle, ulong deviceId)
{
eventHandle = 0;
return AudioResult.NotImplemented;
}
[CmifCommand(16)] // 17.0.0+
public Result ReleaseAudioOutputDeviceNotification(ulong deviceId)
{
return AudioResult.NotImplemented;
}
[CmifCommand(17)] // 17.0.0+
public Result AcquireAudioInputDeviceNotification([CopyHandle] out int eventHandle, ulong deviceId)
{
eventHandle = 0;
return AudioResult.NotImplemented;
}
[CmifCommand(18)] // 17.0.0+
public Result ReleaseAudioInputDeviceNotification(ulong deviceId)
{
return AudioResult.NotImplemented;
}
[CmifCommand(19)] // 18.0.0+
public Result SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled)
{
return AudioResult.NotImplemented;
}
[CmifCommand(20)] // 18.0.0+
public Result IsAudioDeviceOutputVolumeAutoTuneEnabled(out bool enabled)
{
enabled = false;
return AudioResult.NotImplemented;
}
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)

View file

@ -55,6 +55,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -83,6 +84,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -111,6 +113,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -139,6 +142,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -167,6 +171,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -195,6 +200,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -223,6 +229,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -251,6 +258,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -279,6 +287,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -307,6 +316,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -335,6 +345,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@ -363,6 +374,36 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
}
[Test]
public void TestRevision13()
{
BehaviourContext behaviourContext = new();
behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision13);
Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
Assert.IsTrue(behaviourContext.IsSplitterSupported());
Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
Assert.IsTrue(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());