Compare commits

..

71 Commits

Author SHA1 Message Date
Emmanuel Hansen
02714a1291 Avalonia - Add source generator for locale items (#3999)
* Add source generator for locale keys

* use locale keys in Ui subdir
2023-01-03 19:45:08 +01:00
Mary-nyan
09c9686498 misc: Use official names for NVDEC registers (#4192)
* misc: Uses official names for NVDEC registers

* Address gdkchan's comment

* Address comments
2023-01-02 14:48:46 +00:00
Mary-nyan
b6614c6ad5 chore: Update tests dependencies (#3978)
* chore: Update tests dependencies

* Apply TSR Berry suggestion to add a GC.SuppressFinalize in MemoryBlock.cs

* Ensure we wait for the test thread to be dead on PartialUnmap

* Use platform attribute for os specific tests

* Make P/Invoke methods private

* Downgrade NUnit3TestAdapter to 4.1.0

* test: Disable warning about platform compat for ThreadLocalMap()

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2023-01-01 17:35:29 +01:00
Emmanuel Hansen
b1d4b174a6 fix typo in left joycon sl binding (#4195) 2023-01-01 15:46:02 +01:00
Andrew Glaze
2b23463daa Filter hidden game files from the Game List (#4051)
* Filter “._” files from the game list

* Filter all hidden files from the game list

* Fix style

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* merge OR expression into a pattern

* migrate from GetFiles/Directories to Enumerate

* Remove GetFilesInDirectory()

* Update Ryujinx.Ui.Common/App/ApplicationLibrary.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* add error handeling

* code cleanup

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-12-29 16:52:30 +01:00
gdkchan
9dfe81770a Use vector outputs for texture operations (#3939)
* Change AggregateType to include vector type counts

* Replace VariableType uses with AggregateType and delete VariableType

* Support new local vector types on SPIR-V and GLSL

* Start using vector outputs for texture operations

* Use vectors on more texture operations

* Use vector output for ImageLoad operations

* Replace all uses of single destination texture constructors with multi destination ones

* Update textureGatherOffsets replacement to split vector operations

* Shader cache version bump

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-12-29 16:09:34 +01:00
Luminoso-256
52c115a1f8 HLE: Add basic stubs to get Labo VR booting to title screen. (#4007)
* HLE: Add basic stubs to get Labo VR booting to title screen.

* Address code review

* Apply suggestions from code review (pt. 2)

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Apply suggestions from code review (pt. 3)

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Formatting: final batch?

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Address last? bit of formatting

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-12-29 15:57:35 +01:00
riperiperi
e20abbf9cc Vulkan: Don't flush commands when creating most sync (#4087)
* Vulkan: Don't flush commands when creating most sync

When the WaitForIdle method is called, we create sync as some internal GPU method may read back written buffer data. Some games randomly intersperse compute dispatch into their render passes, which result in this happening an unbounded number of times depending on how many times they run compute.

Creating sync in Vulkan is expensive, as we need to flush the current command buffer so that it can be waited on. We have a limited number of active command buffers due to how we track resource usage, so submitting too many command buffers will force us to wait for them to return to the pool.

This PR allows less "important" sync (things which are less likely to be waited on) to wait on a command buffer's result without submitting it, instead relying on AutoFlush or another, more important sync to flush it later on.

Because of the possibility of us waiting for a command buffer that hasn't submitted yet, any thread needs to be able to force the active command buffer to submit. The ability to do this has been added to the backend multithreading via an "Interrupt", though it is not supported without multithreading.

OpenGL drivers should already be doing something similar so they don't blow up when creating lots of sync, which is why this hasn't been a problem for these games over there.

Improves Vulkan performance on Xenoblade DE, Pokemon Scarlet/Violet, and Zelda BOTW (still another large issue here)

* Add strict argument

This is technically a separate concern from whether the sync is a host syncpoint.

* Remove _interrupted variable

* Actually wait for the invoke

This is required by AMD GPUs, and also may have caused some issues on other GPUs.

* Remove unused using.

* I don't know why it added these ones.

* Address Feedback

* Fix typo
2022-12-29 15:39:04 +01:00
Isaac Marovitz
76671d63d4 Ava GUI: Restructure Ryujinx.Ava (#4165)
* Restructure `Ryujinx.Ava`

* Stylistic consistency

* Update Ryujinx.Ava/UI/Controls/UserEditor.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Controls/UserEditor.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Controls/UserSelector.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Fix redundancies

* Remove redunancies

* Add back elses

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2022-12-29 15:24:05 +01:00
TSRBerry
3d1a0bf374 [Headless] Fix for not receiving any SDL events on Linux (#4182)
* headless: Fix SDL events not working

* Add nuget.config
2022-12-29 15:09:18 +01:00
Mary-nyan
c20f3fbebd haydn: Add support for PCMFloat, PCM32 and PCM8 conversions (#4100)
* haydn: Add support for PCMFloat, PCM32 and PCM8 conversions

This adds support in the compatibility layer for other sample format
than PCM16.

This should help extends compatibility with soundio on devices that
doesn't expose PCM16.

I ommited PCM24 conversion for now as it's not simplest of all.

* Address TSRBerry's comment

* Address comments

* Fix conversion issue and clean up saturation usage

* Revert saturation changes

* Address gdkchan's comment
2022-12-28 19:34:30 -03:00
Berkan Diler
0d3b82477e Use new ArgumentNullException and ObjectDisposedException throw-helper API (#4163) 2022-12-27 20:27:11 +01:00
riperiperi
470be03c2f GPU: Add fallback when 16-bit formats are not supported (#4108)
* Add conversion for 16 bit RGBA formats (not supported in Rosetta)

* Rebase fix

Rebase fix

* Forgot to remove this

* Fix RGBA16 format conversion

* Add RGBA4 -> RGBA8 conversion

* Handle host stride alignment

* Address Feedback Part 1

* Can't count

* Don't zero out rgb when alpha is 0

* Separate RGBA4 and 5-bit component formats

Not sure of a better way to name them...

* Add A1B5G5R5 conversion

* Put this in the right place.

* Make format naming consistent for capabilities

* Change method names
2022-12-26 15:50:27 -03:00
Hunter
c963b3c804 Added Generic Math to BitUtils (#3929)
* Generic Math Update

Updated Several functions in Ryujinx.Common/Utilities/BitUtils to use generic math

* Updated BitUtil calls

* Removed Whitespace

* Switched decrement

* Fixed changed method calls.

The method calls were originally changed on accident due to me relying too much on intellisense doing stuff for me

* Update Ryujinx.Common/Utilities/BitUtils.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2022-12-26 14:11:05 +00:00
gnisman
a4fdfb5f94 bsd::RecvFrom: verify output buffer size before writing socket address (#4135)
* bsd::RecvFrom: Ryujinx does not verify output buffer size before writing socket address

* Calculate the size of BsdSockAddr

* use bsdSockAddr variable

* Update Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs

Co-authored-by: Mary-nyan <thog@protonmail.com>

* set errno to ENOMEM in case we can't write the address to memory

* Update Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs

Co-authored-by: Mary-nyan <thog@protonmail.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-12-26 13:49:26 +00:00
Berkan Diler
37d27c4c99 Some minor cleanups and optimizations (#4174)
* Replace Array.Clear(x, 0, x.Length) with Array.Clear(x)

* Use DateTime.UnixEpoch field

* Replace SHA256.ComputeHash calls with static SHA256.HashData call

More performant and avoids the need to initialize a SHA256 instance.
2022-12-24 14:30:39 -03:00
gdkchan
f906eb06c2 Implement a software ETC2 texture decoder (#4121)
* Implement a software ETC2 texture decoder

* Fix output size calculation for non-2D textures

* Address PR feedback
2022-12-21 20:39:58 -03:00
gdkchan
219f63ff4e Fix CPU FCVTN instruction implementation (slow path) (#4159)
* Fix CPU FCVTN instruction implementation (slow path)

* PPTC version bump
2022-12-21 23:05:58 +00:00
gdkchan
1cca3e99ab GPU: Force rebind when pool changes (#4129) 2022-12-21 17:35:28 -03:00
Ccl
55a23e5ec8 make UI display correct content in Chinese (#4155) 2022-12-21 17:20:37 -03:00
Mary-nyan
479d1fd8b0 hle: Handle GPU profiler and debugger device path correctly (#4138)
This fix a warning on "慟哭そして…" by handling correctly the debug mode
flag.

When debug mode isn't enabled, opening /dev/nvhost-dbg-gpu or /dev/nvhost-prof-gpu should fail with a not implemented error code.

This implement this behaviour and also define stubbed interfaces for
completness.
2022-12-21 18:23:11 +00:00
gdkchan
cb70e7bb30 Fix DrawArrays vertex buffer size (#4141) 2022-12-21 19:08:12 +01:00
riperiperi
c200a7b7c6 ARMeilleure: Hash _data pointer instead of value for Operand (#4156)
I noticed a weirdly high cost for dictionary accesses from MarkLabel etc. Turns out that the hash code was always the same for labels, so the whole point of having a dictionary was missed and it was putting everything in the same bucket. I made it always hash the _data pointer as that's a good source of identifiable and "random" data.
2022-12-21 02:28:18 +01:00
Emmanuel Hansen
6268170a10 fix sw kbd row collision (#4144) 2022-12-19 15:09:36 -03:00
gdkchan
ee0f9b03a4 Eliminate zero-extension moves in more cases on 32-bit games (#4140)
* Eliminate zero-extension moves in more cases on 32-bit games

* PPTC version bump

* Revert X86Optimizer changes
2022-12-19 14:45:58 -03:00
gdkchan
f93c5f006a Revert "ARMeilleure: Add initial support for AVX512(EVEX encoding) (#3663)" (#4145)
This reverts commit 295fbd0542.
2022-12-18 20:21:10 -03:00
Wunk
295fbd0542 ARMeilleure: Add initial support for AVX512(EVEX encoding) (#3663)
* ARMeilleure: Add AVX512{F,VL,DQ,BW} detection

Add `UseAvx512Ortho` and `UseAvx512OrthoFloat` optimization flags as
short-hands for `F+VL` and `F+VL+DQ`.

* ARMeilleure: Add initial support for EVEX instruction encoding

Does not implement rounding, or exception controls.

* ARMeilleure: Add `X86Vpternlogd`

Accelerates the vector-`Not` instruction.

* ARMeilleure: Add check for `OSXSAVE` for AVX{2,512}

* ARMeilleure: Add check for `XCR0` flags

Add XCR0 register checks for AVX and AVX512F, following the guidelines
from section 14.3 and 15.2 from the Intel Architecture Software
Developer's Manual.

* ARMeilleure: Increment InternalVersion

* ARMeilleure: Remove redundant `ReProtect` and `Dispose`, formatting

* ARMeilleure: Move XCR0 procedure to GetXcr0Eax

* ARMeilleure: Add `XCR0` to `FeatureInfo` structure

* ARMeilleure: Utilize `ReadOnlySpan` for Xcr0 assembly

Avoids an additional allocation

* ARMeilleure: Formatting fixes
2022-12-18 16:46:13 -03:00
Mary-nyan
d7310d7a1c hle: Fix wrong conversion in UserPresence.ToString (#4142)
This fixes an error from #3805 that caused a wrong conversion of ``AppKeyValueStorage`` to string.
As that information isn't really relevant without appropriate parsing, it was removed from ``ToString``.

This should get ride of "bell warning" in Mario Kart 8 when entering Time Trials.
2022-12-18 14:23:19 +00:00
dependabot[bot]
8c50943a2e nuget: bump Microsoft.NET.Test.Sdk from 17.4.0 to 17.4.1 (#4137)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.4.0 to 17.4.1.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.4.0...v17.4.1)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-17 01:19:04 +01:00
gdkchan
ec4cd57ccf Implement another non-indexed draw method on GPU (#4123) 2022-12-16 12:06:38 -03:00
riperiperi
5a085cba0f GPU: Fix layered attachment write (#4131)
Fixes a regression caused by #4003 where the code that writes `_vtgWritesRtLayer` was removed, breaking the crowd in mario strikers.
2022-12-16 09:40:01 -03:00
TSRBerry
1a1d33a018 ava: Fix invisible swkbd applet on Linux (#4130) 2022-12-16 02:40:55 +01:00
Isaac Marovitz
0fbcd630bc Replace DllImport usage with LibraryImport (#4084)
* Replace usage of `DllImport` with `LibraryImport`

* Mark methods as `partial`

* Marshalling

* More `partial` & marshalling

* More `partial` and marshalling

* More partial and marshalling

* Update GdiPlusHelper to LibraryImport

* Unicorn

* More Partial

* Marshal

* Specify EntryPoint

* Specify EntryPoint

* Change GlobalMemoryStatusEx to LibraryImport

* Change RegisterClassEx to LibraryImport

* Define EntryPoints

* Update Ryujinx.Ava/Ui/Controls/Win32NativeInterop.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Move return mashal

* Remove calling convention specification

* Remove calling conventions

* Update Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx/Modules/Updater/Updater.cs

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update Ryujinx.Ava/Modules/Updater/Updater.cs

Co-authored-by: Mary-nyan <thog@protonmail.com>

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Mary-nyan <thog@protonmail.com>
2022-12-15 18:07:31 +01:00
gdkchan
f4d731ae20 Fix NRE when loading Vulkan shader cache with Vertex A shaders (#4124) 2022-12-15 17:52:12 +01:00
Isaac Marovitz
8ac53c66b4 Remove Half Conversion (#4106)
* Remove HalfConversion

* Update `CodeGenVersion`
2022-12-14 21:13:23 -03:00
Georg Lehmann
0f50de72be Vulkan: enable VK_EXT_custom_border_color features (#4116)
* Vulkan: enable VK_EXT_custom_border_color features

radv only create the border color bo if this feature is enabled, so it crashed when creating samplers with custom border colors
Fixes #4072
Fixes #3993

* Address gdkchan's comment

Co-authored-by: Mary <mary@mary.zone>
2022-12-14 20:53:33 -03:00
TSRBerry
df758eddd1 Bsd: Add support for dns_mitm (#4102)
* bsd: Add dns_mitm from Atmosphère

related AMS files:
- https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.cpp
- https://github.com/Atmosphere-NX/Atmosphere/blob/master/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp

* Remove debug logging and adjust redirect message

* Improve formatting

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Replace Initialize with instance property

* bsd: dns_mitm - Ignore empty lines

* bsd: Mark _mitmHostEntries as readonly

* bsd: Initialize Aliases when returning IpHostEntry

Fixes NullReferenceException

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-12-12 18:04:08 +01:00
Mary-nyan
5f32a8ed94 misc: Update to Ryujinx.Graphics.Nvdec.Dependencies 5.0.1-build13 (#4097)
Fix packaging issues on macOS related to an unsatisfied dependency on libX11
2022-12-12 16:19:46 +01:00
Andrey Sukharev
535fbec675 Use NuGet Central Package Management to manage package versions solution-wise (#4095) 2022-12-12 16:03:10 +01:00
Mary-nyan
6fe88115a3 misc: Some fixes to the updaters (#4092)
This was meant to be only an upgrade of how we set unix permission in
the updater to use .NET 7 new APIs, but I end up finding bugs along the
way.

Changelog:
- Remove direct usage of chmod to use File.SetUnixFileMode.
- Fix command line being broken when updating (#3744) but on
  Ryujinx.Ava.
- Makes Ryujinx.Ava updater fallback to Ryujinx executable if current
  name isn't found.
- Make permission setter function more generic.
2022-12-12 15:17:22 +01:00
&Olga
475fa4d390 Fix "UI" abbreviation being miscapitalized (#4093) 2022-12-12 15:11:55 +01:00
Andrey Sukharev
edf7e628ca Use method overloads that support trimming. Mark some types to be trimming friendly (#4083)
* Use method overloads that support trimming. Mark some types to be trimming friendly

* Use generic version of marshalling method
2022-12-12 15:10:05 +01:00
TSRBerry
ba5c0cf5d8 Bsd: Implement Select (#4017)
* bsd: Add gdkchan's Select implementation

Co-authored-by: TSRBerry <20988865+tsrberry@users.noreply.github.com>

* bsd: Fix Select() causing a crash with an ArgumentException

.NET Sockets have to be used for the Select() call

* bsd: Make Select more generic

* bsd: Adjust namespaces and remove unused imports

* bsd: Fix NullReferenceException in Select

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2022-12-12 14:59:31 +01:00
Mary-nyan
403e67d983 audio: Rewrite SoundIo bindings (#4088)
* audio: Rewrite SoundIo bindings

This rewrite SoundIo bindings to be safer and not a pedantic autogenerated mess.

* Address comments

* Switch DllImport to LibraryImport

* Address gdkchan's comment
2022-12-11 00:57:01 +01:00
Isaac Marovitz
c6f1908e0f Fix Lambda Explicit Type Specification Warning (#4090) 2022-12-10 22:12:51 +01:00
Isaac Marovitz
851d81d24a Fix Redundant Qualifer Warnings (#4091)
* Fix Redundant Qualifer Warnings

* Remove unnecessary using
2022-12-10 21:21:13 +01:00
gdkchan
459c4caeba Fix HasUnalignedStorageBuffers value when buffers are always unaligned (#4078) 2022-12-09 17:41:40 -03:00
Mary
539b22ef7b Add explicit dependency on System.Drawing.Common on Ryujinx.Ava to workaround triming bugs 2022-12-09 20:12:09 +01:00
Mary-nyan
872f036d64 misc: Remove dependency on System.Drawing.Common (#4082)
We only used it in one spot for DPI scaling factor.

This implements the same behaviour using gdiplus.

This remove 700KB of dependency to download and around 170KB unpacked.
2022-12-09 18:00:53 +01:00
TSRBerry
dca96122bf gha: Add concurrency restriction on release workflow (#4081) 2022-12-09 16:15:28 +00:00
Mary-nyan
e752959109 misc: Update Ryujinx.Graphics.Nvdec.Dependencies to 5.0.1-build12 (#4080)
This adds support for Linux x64, macOS x64 and macOS arm64.
2022-12-09 15:46:07 +01:00
Ac_K
cf01664698 ava: Restyle the Status Bar (#4048) 2022-12-09 15:21:54 +01:00
dependabot[bot]
b283a4adcd nuget: bump CommandLineParser from 2.8.0 to 2.9.1 (#4058)
Bumps [CommandLineParser](https://github.com/commandlineparser/commandline) from 2.8.0 to 2.9.1.
- [Release notes](https://github.com/commandlineparser/commandline/releases)
- [Changelog](https://github.com/commandlineparser/commandline/blob/master/CHANGELOG.md)
- [Commits](https://github.com/commandlineparser/commandline/compare/2.8.0...v2.9.1)

---
updated-dependencies:
- dependency-name: CommandLineParser
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-09 14:16:22 +01:00
gdkchan
8428bb6541 Fix shader FSWZADD instruction (#4069)
* Fix shader FSWZADD instruction

* Shader cache version bump
2022-12-08 14:08:07 -03:00
gdkchan
9a0330f7f8 Shader: Implement PrimitiveID (#4067)
* Shader: Implement PrimitiveID

* Shader cache version bump
2022-12-08 10:55:03 +01:00
IverCoder
57fc996337 Fix inconsistent capitalization (#4070) 2022-12-08 09:32:24 +00:00
Ac_K
1f3b860f06 acc: Stub CheckNetworkServiceAvailabilityAsync (#4052) 2022-12-07 23:19:22 +01:00
dependabot[bot]
abe3c02ab4 nuget: bump DynamicData from 7.12.8 to 7.12.11 (#4059)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 7.12.8 to 7.12.11.
- [Release notes](https://github.com/reactiveui/DynamicData/releases)
- [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md)
- [Commits](https://github.com/reactiveui/DynamicData/compare/7.12.8...7.12.11)

---
updated-dependencies:
- dependency-name: DynamicData
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-07 23:07:23 +01:00
dependabot[bot]
45b417b2b4 nuget: bump NUnit from 3.12.0 to 3.13.3 (#4060)
Bumps [NUnit](https://github.com/nunit/nunit) from 3.12.0 to 3.13.3.
- [Release notes](https://github.com/nunit/nunit/releases)
- [Changelog](https://github.com/nunit/nunit/blob/v3.13.3/CHANGES.md)
- [Commits](https://github.com/nunit/nunit/compare/v3.12...v3.13.3)

---
updated-dependencies:
- dependency-name: NUnit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-07 22:02:31 +01:00
TSRBerry
d076339e3e Add Ryujinx license file to builds (#4057) 2022-12-07 18:20:18 +01:00
dependabot[bot]
837836431d nuget: bump System.Drawing.Common from 6.0.0 to 7.0.0 (#4024)
Bumps [System.Drawing.Common](https://github.com/dotnet/runtime) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v6.0.0...v7.0.0)

---
updated-dependencies:
- dependency-name: System.Drawing.Common
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-07 16:24:34 +00:00
Ac_K
9f555db5cd hle: Do not add disabled AoC item to the list (#4044)
* hle: Do not add disabled AoC item to the list

We currently add all AoC items to a list in `ContentManager` and the enable check is only done when FS service ask for the data. Which is wrong. It causes an issue in MK8D which doesn't boot even if you have disabled a not updated DLC.

I've fixed it by not adding the disabled AoC item to the list, I've removed some duplicate code too.
There is still an edge case because we currently don't check the AoC Item version, but that should be fixed later since now MK8D throw an error if the DLC isn't updated.

* remove useless "enabled"
2022-12-07 15:00:28 +01:00
Isaac Marovitz
bf7fa60dfc Fix struct layout packing (#4039) 2022-12-07 02:04:01 +00:00
Ac_K
752b93d3b7 gtk: Fixes warnings about obsolete components (#4049)
* gtk: Fixes warnings about obsolete components

* remove wrong using
2022-12-07 01:49:37 +01:00
riperiperi
f23b2878cc Shader: Add fallback for LDG from "ube" buffer ranges. (#4027)
We have a conversion from LDG on the compute shader to a special constant buffer binding that's used to exceed hardware limits on compute, but it was only running if the byte offset could be identified. The fallback that checks all of the bindings at runtime only checks the storage buffers.

This PR adds checking ube ranges to the LoadGlobal fallback. This extends the changes in #4011 to only check ube entries which are accessed by the shader.

Fixes particles affected by the wind in The Legend of Zelda: Breath of the Wild. May fix other weird issues with compute shaders in some games.

Try a bunch of games and drivers to make sure they don't blow up loading constants willynilly from searchable buffers.
2022-12-06 23:15:44 +00:00
riperiperi
e211c3f00a UI: Add Metal surface creation for MoltenVK (#3980)
* Initial implementation of metal surface across UIs

* Fix SDL2 on windows

* Update Ryujinx/Ryujinx.csproj

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Address Feedback

Co-authored-by: Mary-nyan <thog@protonmail.com>
2022-12-06 19:00:25 -03:00
dependabot[bot]
d3709a753f nuget: bump XamlNameReferenceGenerator from 1.4.2 to 1.5.1 (#4026)
Bumps [XamlNameReferenceGenerator](https://github.com/avaloniaui/Avalonia.NameGenerator) from 1.4.2 to 1.5.1.
- [Release notes](https://github.com/avaloniaui/Avalonia.NameGenerator/releases)
- [Commits](https://github.com/avaloniaui/Avalonia.NameGenerator/compare/1.4.2...1.5.1)

---
updated-dependencies:
- dependency-name: XamlNameReferenceGenerator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 19:00:08 +01:00
dependabot[bot]
ab676d58ea nuget: bump System.IdentityModel.Tokens.Jwt from 6.25.0 to 6.25.1 (#4043)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.25.0 to 6.25.1.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.25.0...6.25.1)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 18:01:21 +01:00
Ac_K
2372c194f1 ava: Cleanup Input classes (#4042)
* ava: Cleanup Input classes

This PR just cleanup all Input classes for consistencies.

* Addresses TSRBerry's feedback
2022-12-06 15:32:14 +00:00
Mary-nyan
40311310d1 amadeus: Add missing compressor effect from REV11 (#4010)
* amadeus: Add missing compressor effect from REV11

This was in my reversing notes but seems I completely forgot to
implement it

Also took the opportunity to simplify the Limiter effect a bit.

* Remove some outdated comment

* Address gdkchan's comments
2022-12-06 15:04:25 +01:00
gdkchan
dde9bb5c69 Fix storage buffer access when match fails (#4037)
* Fix storage buffer access when match fails

* Shader cache version bump
2022-12-06 03:36:54 +00:00
425 changed files with 7302 additions and 5587 deletions

View File

@@ -11,6 +11,7 @@ on:
- '*.yml'
- 'README.md'
concurrency: release
jobs:
release:

3
.gitignore vendored
View File

@@ -125,6 +125,9 @@ ClientBin/
packages/*
*.config
# Include nuget.config
!nuget.config
# RIA/Silverlight projects
Generated_Code/

View File

@@ -1587,6 +1587,12 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger());
// We can eliminate the move if source is already 32-bit and the registers are the same.
if (dest.Value == source.Value && source.Type == OperandType.I32)
{
return;
}
context.Assembler.Mov(dest, source, OperandType.I32);
}

View File

@@ -80,10 +80,7 @@ namespace ARMeilleure.Common
{
get
{
if (_disposed)
{
throw new ObjectDisposedException(null);
}
ObjectDisposedException.ThrowIf(_disposed, this);
lock (_pages)
{
@@ -100,10 +97,7 @@ namespace ARMeilleure.Common
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
public AddressTable(Level[] levels)
{
if (levels == null)
{
throw new ArgumentNullException(nameof(levels));
}
ArgumentNullException.ThrowIfNull(levels);
if (levels.Length < 2)
{
@@ -141,10 +135,7 @@ namespace ARMeilleure.Common
/// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
public ref TEntry GetValue(ulong address)
{
if (_disposed)
{
throw new ObjectDisposedException(null);
}
ObjectDisposedException.ThrowIf(_disposed, this);
if (!IsValid(address))
{

View File

@@ -49,10 +49,7 @@ namespace ARMeilleure.Common
{
get
{
if (_disposed)
{
throw new ObjectDisposedException(null);
}
ObjectDisposedException.ThrowIf(_disposed, this);
return ref _countTable.GetValue(_index);
}

View File

@@ -53,10 +53,7 @@ namespace ARMeilleure.Common
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
public int Allocate()
{
if (_disposed)
{
throw new ObjectDisposedException(null);
}
ObjectDisposedException.ThrowIf(_disposed, this);
lock (_allocated)
{
@@ -83,10 +80,7 @@ namespace ARMeilleure.Common
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
public void Free(int index)
{
if (_disposed)
{
throw new ObjectDisposedException(null);
}
ObjectDisposedException.ThrowIf(_disposed, this);
lock (_allocated)
{
@@ -108,10 +102,7 @@ namespace ARMeilleure.Common
/// <exception cref="ArgumentException">Entry at <paramref name="index"/> is not allocated</exception>
public ref TEntry GetValue(int index)
{
if (_disposed)
{
throw new ObjectDisposedException(null);
}
ObjectDisposedException.ThrowIf(_disposed, this);
lock (_allocated)
{

View File

@@ -1341,7 +1341,7 @@ namespace ARMeilleure.Decoders
{
string reversedEncoding = encoding.Substring(16) + encoding.Substring(0, 16);
MakeOp reversedMakeOp =
(InstDescriptor inst, ulong address, int opCode)
(inst, address, opCode)
=> makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16));
Set(reversedEncoding, AllInstT32, new InstDescriptor(name, emitter), reversedMakeOp);
}

View File

@@ -381,7 +381,7 @@ namespace ARMeilleure.Instructions
for (int index = 0; index < elems; index++)
{
Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0);
Operand ne = context.VectorExtract(type, GetVec(op.Rn), index);
if (sizeF == 0)
{
@@ -389,8 +389,6 @@ namespace ARMeilleure.Instructions
Operand e = context.Call(typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)), ne);
context.LoadFromContext();
e = context.ZeroExtend16(OperandType.I64, e);
res = EmitVectorInsert(context, res, e, part + index, 1);
}
else /* if (sizeF == 1) */

View File

@@ -48,10 +48,7 @@ namespace ARMeilleure.IntermediateRepresentation
public void AddSuccessor(BasicBlock block)
{
if (block == null)
{
ThrowNull(nameof(block));
}
ArgumentNullException.ThrowIfNull(block);
if ((uint)_succCount + 1 > MaxSuccessors)
{
@@ -100,10 +97,7 @@ namespace ARMeilleure.IntermediateRepresentation
public void SetSuccessor(int index, BasicBlock block)
{
if (block == null)
{
ThrowNull(nameof(block));
}
ArgumentNullException.ThrowIfNull(block);
if ((uint)index >= (uint)_succCount)
{
@@ -144,7 +138,6 @@ namespace ARMeilleure.IntermediateRepresentation
}
}
private static void ThrowNull(string name) => throw new ArgumentNullException(name);
private static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name);
private static void ThrowSuccessorOverflow() => throw new OverflowException($"BasicBlock can only have {MaxSuccessors} successors.");

View File

@@ -378,14 +378,7 @@ namespace ARMeilleure.IntermediateRepresentation
public override int GetHashCode()
{
if (Kind == OperandKind.LocalVariable)
{
return base.GetHashCode();
}
else
{
return (int)Value ^ ((int)Kind << 16) ^ ((int)Type << 20);
}
return ((ulong)_data).GetHashCode();
}
public bool Equals(Operand operand)

View File

@@ -18,17 +18,17 @@ namespace ARMeilleure.Signal
public IntPtr sa_restorer;
}
static class UnixSignalHandlerRegistration
static partial class UnixSignalHandlerRegistration
{
private const int SIGSEGV = 11;
private const int SIGBUS = 10;
private const int SA_SIGINFO = 0x00000004;
[DllImport("libc", SetLastError = true)]
private static extern int sigaction(int signum, ref SigAction sigAction, out SigAction oldAction);
[LibraryImport("libc", SetLastError = true)]
private static partial int sigaction(int signum, ref SigAction sigAction, out SigAction oldAction);
[DllImport("libc", SetLastError = true)]
private static extern int sigemptyset(ref SigSet set);
[LibraryImport("libc", SetLastError = true)]
private static partial int sigemptyset(ref SigSet set);
public static SigAction RegisterExceptionHandler(IntPtr action)
{

View File

@@ -3,19 +3,19 @@ using System.Runtime.InteropServices;
namespace ARMeilleure.Signal
{
unsafe class WindowsSignalHandlerRegistration
unsafe partial class WindowsSignalHandlerRegistration
{
[DllImport("kernel32.dll")]
private static extern IntPtr AddVectoredExceptionHandler(uint first, IntPtr handler);
[LibraryImport("kernel32.dll")]
private static partial IntPtr AddVectoredExceptionHandler(uint first, IntPtr handler);
[DllImport("kernel32.dll")]
private static extern ulong RemoveVectoredExceptionHandler(IntPtr handle);
[LibraryImport("kernel32.dll")]
private static partial ulong RemoveVectoredExceptionHandler(IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
[LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")]
private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[LibraryImport("kernel32.dll", SetLastError = true)]
private static partial IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName);
private static IntPtr _getCurrentThreadIdPtr;

View File

@@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
namespace ARMeilleure.Translation.Cache
{
static class JitUnwindWindows
static partial class JitUnwindWindows
{
private const int MaxUnwindCodesArraySize = 32; // Must be an even value.
@@ -42,14 +42,15 @@ namespace ARMeilleure.Translation.Cache
private unsafe delegate RuntimeFunction* GetRuntimeFunctionCallback(ulong controlPc, IntPtr context);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static unsafe extern bool RtlInstallFunctionTableCallback(
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static unsafe partial bool RtlInstallFunctionTableCallback(
ulong tableIdentifier,
ulong baseAddress,
uint length,
GetRuntimeFunctionCallback callback,
IntPtr context,
string outOfProcessCallbackDll);
[MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll);
private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback;

View File

@@ -25,10 +25,7 @@ namespace ARMeilleure.Translation
public static Delegate GetDelegate(MethodInfo info)
{
if (info == null)
{
throw new ArgumentNullException(nameof(info));
}
ArgumentNullException.ThrowIfNull(info);
Type[] parameters = info.GetParameters().Select(pI => pI.ParameterType).ToArray();
Type returnType = info.ReturnType;

View File

@@ -35,10 +35,7 @@ namespace ARMeilleure.Translation
public static IntPtr GetDelegateFuncPtr(MethodInfo info)
{
if (info == null)
{
throw new ArgumentNullException(nameof(info));
}
ArgumentNullException.ThrowIfNull(info);
string key = GetKey(info);
@@ -52,10 +49,7 @@ namespace ARMeilleure.Translation
public static int GetDelegateIndex(MethodInfo info)
{
if (info == null)
{
throw new ArgumentNullException(nameof(info));
}
ArgumentNullException.ThrowIfNull(info);
string key = GetKey(info);

View File

@@ -67,10 +67,7 @@ namespace ARMeilleure.Translation
/// <returns>True if the value was added, false if the start key was already in the dictionary</returns>
public bool AddOrUpdate(K start, K end, V value, Func<K, V, V> updateFactoryCallback)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
ArgumentNullException.ThrowIfNull(value);
return BSTInsert(start, end, value, updateFactoryCallback, out IntervalTreeNode<K, V> node);
}
@@ -85,10 +82,7 @@ namespace ARMeilleure.Translation
/// <returns><paramref name="value"/> if <paramref name="start"/> is not yet on the tree, or the existing value otherwise</returns>
public V GetOrAdd(K start, K end, V value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
ArgumentNullException.ThrowIfNull(value);
BSTInsert(start, end, value, null, out IntervalTreeNode<K, V> node);
return node.Value;
@@ -152,10 +146,7 @@ namespace ARMeilleure.Translation
/// <returns>Node reference in the tree</returns>
private IntervalTreeNode<K, V> GetNode(K key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
ArgumentNullException.ThrowIfNull(key);
IntervalTreeNode<K, V> node = _root;
while (node != null)

View File

@@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 3713; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 4159; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";

View File

@@ -140,7 +140,7 @@ namespace ARMeilleure.Translation
}
}
Array.Clear(localDefs, 0, localDefs.Length);
Array.Clear(localDefs);
}
}

View File

@@ -30,10 +30,7 @@ namespace ARMeilleure.Translation
{
get
{
if (_disposed)
{
throw new ObjectDisposedException(null);
}
ObjectDisposedException.ThrowIf(_disposed, this);
return _dispatchStub.Value;
}
@@ -47,10 +44,7 @@ namespace ARMeilleure.Translation
{
get
{
if (_disposed)
{
throw new ObjectDisposedException(null);
}
ObjectDisposedException.ThrowIf(_disposed, this);
return _slowDispatchStub.Value;
}
@@ -64,10 +58,7 @@ namespace ARMeilleure.Translation
{
get
{
if (_disposed)
{
throw new ObjectDisposedException(null);
}
ObjectDisposedException.ThrowIf(_disposed, this);
return _dispatchLoop.Value;
}
@@ -81,7 +72,9 @@ namespace ARMeilleure.Translation
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
public TranslatorStubs(Translator translator)
{
_translator = translator ?? throw new ArgumentNullException(nameof(translator));
ArgumentNullException.ThrowIfNull(translator);
_translator = translator;
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
}

56
Directory.Packages.props Normal file
View File

@@ -0,0 +1,56 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="0.10.18" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.18" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
<PackageVersion Include="DynamicData" Version="7.12.11" />
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageVersion Include="LibHac" Version="0.17.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageVersion Include="OpenTK.Core" Version="4.7.5" />
<PackageVersion Include="OpenTK.Graphics" Version="4.7.5" />
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.5" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.24.2-build21" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.1" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
<PackageVersion Include="System.Management" Version="7.0.0" />
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
</ItemGroup>
</Project>

View File

@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" />
<PackageReference Include="OpenTK.OpenAL" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,178 @@
using Ryujinx.Common.Memory;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public static partial class SoundIo
{
private const string LibraryName = "libsoundio";
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void OnDeviceChangeNativeDelegate(IntPtr ctx);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void OnBackendDisconnectedDelegate(IntPtr ctx, SoundIoError err);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void OnEventsSignalDelegate(IntPtr ctx);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void EmitRtPrioWarningDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void JackCallbackDelegate(IntPtr msg);
[StructLayout(LayoutKind.Sequential)]
public struct SoundIoStruct
{
public IntPtr UserData;
public IntPtr OnDeviceChange;
public IntPtr OnBackendDisconnected;
public IntPtr OnEventsSignal;
public SoundIoBackend CurrentBackend;
public IntPtr ApplicationName;
public IntPtr EmitRtPrioWarning;
public IntPtr JackInfoCallback;
public IntPtr JackErrorCallback;
}
public struct SoundIoChannelLayout
{
public IntPtr Name;
public int ChannelCount;
public Array24<SoundIoChannelId> Channels;
public static IntPtr GetDefault(int channelCount)
{
return soundio_channel_layout_get_default(channelCount);
}
public static unsafe SoundIoChannelLayout GetDefaultValue(int channelCount)
{
return Unsafe.AsRef<SoundIoChannelLayout>((SoundIoChannelLayout*)GetDefault(channelCount));
}
}
public struct SoundIoSampleRateRange
{
public int Min;
public int Max;
}
public struct SoundIoDevice
{
public IntPtr SoundIo;
public IntPtr Id;
public IntPtr Name;
public SoundIoDeviceAim Aim;
public IntPtr Layouts;
public int LayoutCount;
public SoundIoChannelLayout CurrentLayout;
public IntPtr Formats;
public int FormatCount;
public SoundIoFormat CurrentFormat;
public IntPtr SampleRates;
public int SampleRateCount;
public int SampleRateCurrent;
public double SoftwareLatencyMin;
public double SoftwareLatencyMax;
public double SoftwareLatencyCurrent;
public bool IsRaw;
public int RefCount;
public SoundIoError ProbeError;
}
public struct SoundIoOutStream
{
public IntPtr Device;
public SoundIoFormat Format;
public int SampleRate;
public SoundIoChannelLayout Layout;
public double SoftwareLatency;
public float Volume;
public IntPtr UserData;
public IntPtr WriteCallback;
public IntPtr UnderflowCallback;
public IntPtr ErrorCallback;
public IntPtr Name;
public bool NonTerminalHint;
public int BytesPerFrame;
public int BytesPerSample;
public SoundIoError LayoutError;
}
public struct SoundIoChannelArea
{
public IntPtr Pointer;
public int Step;
}
[LibraryImport(LibraryName)]
public static partial IntPtr soundio_create();
[LibraryImport(LibraryName)]
public static partial SoundIoError soundio_connect(IntPtr ctx);
[LibraryImport(LibraryName)]
public static partial void soundio_disconnect(IntPtr ctx);
[LibraryImport(LibraryName)]
public static partial void soundio_flush_events(IntPtr ctx);
[LibraryImport(LibraryName)]
public static partial int soundio_output_device_count(IntPtr ctx);
[LibraryImport(LibraryName)]
public static partial int soundio_default_output_device_index(IntPtr ctx);
[LibraryImport(LibraryName)]
public static partial IntPtr soundio_get_output_device(IntPtr ctx, int index);
[LibraryImport(LibraryName)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool soundio_device_supports_format(IntPtr devCtx, SoundIoFormat format);
[LibraryImport(LibraryName)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool soundio_device_supports_layout(IntPtr devCtx, IntPtr layout);
[LibraryImport(LibraryName)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool soundio_device_supports_sample_rate(IntPtr devCtx, int sampleRate);
[LibraryImport(LibraryName)]
public static partial IntPtr soundio_outstream_create(IntPtr devCtx);
[LibraryImport(LibraryName)]
public static partial SoundIoError soundio_outstream_open(IntPtr outStreamCtx);
[LibraryImport(LibraryName)]
public static partial SoundIoError soundio_outstream_start(IntPtr outStreamCtx);
[LibraryImport(LibraryName)]
public static partial SoundIoError soundio_outstream_begin_write(IntPtr outStreamCtx, IntPtr areas, IntPtr frameCount);
[LibraryImport(LibraryName)]
public static partial SoundIoError soundio_outstream_end_write(IntPtr outStreamCtx);
[LibraryImport(LibraryName)]
public static partial SoundIoError soundio_outstream_pause(IntPtr devCtx, [MarshalAs(UnmanagedType.Bool)] bool pause);
[LibraryImport(LibraryName)]
public static partial SoundIoError soundio_outstream_set_volume(IntPtr devCtx, double volume);
[LibraryImport(LibraryName)]
public static partial void soundio_outstream_destroy(IntPtr streamCtx);
[LibraryImport(LibraryName)]
public static partial void soundio_destroy(IntPtr ctx);
[LibraryImport(LibraryName)]
public static partial IntPtr soundio_channel_layout_get_default(int channelCount);
[LibraryImport(LibraryName)]
public static partial IntPtr soundio_strerror(SoundIoError err);
}
}

View File

@@ -0,0 +1,13 @@
namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public enum SoundIoBackend : int
{
None = 0,
Jack = 1,
PulseAudio = 2,
Alsa = 3,
CoreAudio = 4,
Wasapi = 5,
Dummy = 6
}
}

View File

@@ -0,0 +1,75 @@
namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public enum SoundIoChannelId
{
Invalid = 0,
FrontLeft = 1,
FrontRight = 2,
FrontCenter = 3,
Lfe = 4,
BackLeft = 5,
BackRight = 6,
FrontLeftCenter = 7,
FrontRightCenter = 8,
BackCenter = 9,
SideLeft = 10,
SideRight = 11,
TopCenter = 12,
TopFrontLeft = 13,
TopFrontCenter = 14,
TopFrontRight = 15,
TopBackLeft = 16,
TopBackCenter = 17,
TopBackRight = 18,
BackLeftCenter = 19,
BackRightCenter = 20,
FrontLeftWide = 21,
FrontRightWide = 22,
FrontLeftHigh = 23,
FrontCenterHigh = 24,
FrontRightHigh = 25,
TopFrontLeftCenter = 26,
TopFrontRightCenter = 27,
TopSideLeft = 28,
TopSideRight = 29,
LeftLfe = 30,
RightLfe = 31,
Lfe2 = 32,
BottomCenter = 33,
BottomLeftCenter = 34,
BottomRightCenter = 35,
MsMid = 36,
MsSide = 37,
AmbisonicW = 38,
AmbisonicX = 39,
AmbisonicY = 40,
AmbisonicZ = 41,
XyX = 42,
XyY = 43,
HeadphonesLeft = 44,
HeadphonesRight = 45,
ClickTrack = 46,
ForeignLanguage = 47,
HearingImpaired = 48,
Narration = 49,
Haptic = 50,
DialogCentricMix = 51,
Aux = 52,
Aux0 = 53,
Aux1 = 54,
Aux2 = 55,
Aux3 = 56,
Aux4 = 57,
Aux5 = 58,
Aux6 = 59,
Aux7 = 60,
Aux8 = 61,
Aux9 = 62,
Aux10 = 63,
Aux11 = 64,
Aux12 = 65,
Aux13 = 66,
Aux14 = 67,
Aux15 = 68,
}
}

View File

@@ -0,0 +1,107 @@
using System;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public class SoundIoContext : IDisposable
{
private IntPtr _context;
private Action<SoundIoError> _onBackendDisconnect;
private OnBackendDisconnectedDelegate _onBackendDisconnectNative;
public IntPtr Context => _context;
internal SoundIoContext(IntPtr context)
{
_context = context;
_onBackendDisconnect = null;
_onBackendDisconnectNative = null;
}
public SoundIoError Connect() => soundio_connect(_context);
public void Disconnect() => soundio_disconnect(_context);
public void FlushEvents() => soundio_flush_events(_context);
public int OutputDeviceCount => soundio_output_device_count(_context);
public int DefaultOutputDeviceIndex => soundio_default_output_device_index(_context);
public Action<SoundIoError> OnBackendDisconnect
{
get { return _onBackendDisconnect; }
set
{
_onBackendDisconnect = value;
if (_onBackendDisconnect == null)
{
_onBackendDisconnectNative = null;
}
else
{
_onBackendDisconnectNative = (ctx, err) => _onBackendDisconnect(err);
}
GetContext().OnBackendDisconnected = Marshal.GetFunctionPointerForDelegate(_onBackendDisconnectNative);
}
}
private ref SoundIoStruct GetContext()
{
unsafe
{
return ref Unsafe.AsRef<SoundIoStruct>((SoundIoStruct*)_context);
}
}
public SoundIoDeviceContext GetOutputDevice(int index)
{
IntPtr deviceContext = soundio_get_output_device(_context, index);
if (deviceContext == IntPtr.Zero)
{
return null;
}
return new SoundIoDeviceContext(deviceContext);
}
public static SoundIoContext Create()
{
IntPtr context = soundio_create();
if (context == IntPtr.Zero)
{
return null;
}
return new SoundIoContext(context);
}
protected virtual void Dispose(bool disposing)
{
IntPtr currentContext = Interlocked.Exchange(ref _context, IntPtr.Zero);
if (currentContext != IntPtr.Zero)
{
soundio_destroy(currentContext);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~SoundIoContext()
{
Dispose(false);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public enum SoundIoDeviceAim
{
SoundIoDeviceAimInput = 0,
SoundIoDeviceAimOutput = 1
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public class SoundIoDeviceContext
{
private readonly IntPtr _context;
public IntPtr Context => _context;
internal SoundIoDeviceContext(IntPtr context)
{
_context = context;
}
private ref SoundIoDevice GetDeviceContext()
{
unsafe
{
return ref Unsafe.AsRef<SoundIoDevice>((SoundIoDevice*)_context);
}
}
public bool IsRaw => GetDeviceContext().IsRaw;
public string Id => Marshal.PtrToStringAnsi(GetDeviceContext().Id);
public bool SupportsSampleRate(int sampleRate) => soundio_device_supports_sample_rate(_context, sampleRate);
public bool SupportsFormat(SoundIoFormat format) => soundio_device_supports_format(_context, format);
public bool SupportsChannelCount(int channelCount) => soundio_device_supports_layout(_context, SoundIoChannelLayout.GetDefault(channelCount));
public SoundIoOutStreamContext CreateOutStream()
{
IntPtr context = soundio_outstream_create(_context);
if (context == IntPtr.Zero)
{
return null;
}
return new SoundIoOutStreamContext(context);
}
}
}

View File

@@ -0,0 +1,22 @@
namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public enum SoundIoError
{
None = 0,
NoMem = 1,
InitAudioBackend = 2,
SystemResources = 3,
OpeningDevice = 4,
NoSuchDevice = 5,
Invalid = 6,
BackendUnavailable = 7,
Streaming = 8,
IncompatibleDevice = 9,
NoSuchClient = 10,
IncompatibleBackend = 11,
BackendDisconnected = 12,
Interrupted = 13,
Underflow = 14,
EncodingString = 15,
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Runtime.InteropServices;
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
namespace Ryujinx.Audio.Backends.SoundIo.Native
{
internal class SoundIoException : Exception
{
internal SoundIoException(SoundIoError error) : base(Marshal.PtrToStringAnsi(soundio_strerror(error))) { }
}
}

View File

@@ -0,0 +1,25 @@
namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public enum SoundIoFormat
{
Invalid = 0,
S8 = 1,
U8 = 2,
S16LE = 3,
S16BE = 4,
U16LE = 5,
U16BE = 6,
S24LE = 7,
S24BE = 8,
U24LE = 9,
U24BE = 10,
S32LE = 11,
S32BE = 12,
U32LE = 13,
U32BE = 14,
Float32LE = 15,
Float32BE = 16,
Float64LE = 17,
Float64BE = 18,
}
}

View File

@@ -0,0 +1,164 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
namespace Ryujinx.Audio.Backends.SoundIo.Native
{
public class SoundIoOutStreamContext : IDisposable
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private unsafe delegate void WriteCallbackDelegate(IntPtr ctx, int frameCountMin, int frameCountMax);
private IntPtr _context;
private IntPtr _nameStored;
private Action<int, int> _writeCallback;
private WriteCallbackDelegate _writeCallbackNative;
public IntPtr Context => _context;
internal SoundIoOutStreamContext(IntPtr context)
{
_context = context;
_nameStored = IntPtr.Zero;
_writeCallback = null;
_writeCallbackNative = null;
}
private ref SoundIoOutStream GetOutContext()
{
unsafe
{
return ref Unsafe.AsRef<SoundIoOutStream>((SoundIoOutStream*)_context);
}
}
public string Name
{
get => Marshal.PtrToStringAnsi(GetOutContext().Name);
set
{
var context = GetOutContext();
if (_nameStored != IntPtr.Zero && context.Name == _nameStored)
{
Marshal.FreeHGlobal(_nameStored);
}
_nameStored = Marshal.StringToHGlobalAnsi(value);
GetOutContext().Name = _nameStored;
}
}
public SoundIoChannelLayout Layout
{
get => GetOutContext().Layout;
set => GetOutContext().Layout = value;
}
public SoundIoFormat Format
{
get => GetOutContext().Format;
set => GetOutContext().Format = value;
}
public int SampleRate
{
get => GetOutContext().SampleRate;
set => GetOutContext().SampleRate = value;
}
public float Volume
{
get => GetOutContext().Volume;
set => GetOutContext().Volume = value;
}
public int BytesPerFrame
{
get => GetOutContext().BytesPerFrame;
set => GetOutContext().BytesPerFrame = value;
}
public int BytesPerSample
{
get => GetOutContext().BytesPerSample;
set => GetOutContext().BytesPerSample = value;
}
public Action<int, int> WriteCallback
{
get { return _writeCallback; }
set
{
_writeCallback = value;
if (_writeCallback == null)
{
_writeCallbackNative = null;
}
else
{
_writeCallbackNative = (ctx, frameCountMin, frameCountMax) => _writeCallback(frameCountMin, frameCountMax);
}
GetOutContext().WriteCallback = Marshal.GetFunctionPointerForDelegate(_writeCallbackNative);
}
}
private static void CheckError(SoundIoError error)
{
if (error != SoundIoError.None)
{
throw new SoundIoException(error);
}
}
public void Open() => CheckError(soundio_outstream_open(_context));
public void Start() => CheckError(soundio_outstream_start(_context));
public void Pause(bool pause) => CheckError(soundio_outstream_pause(_context, pause));
public void SetVolume(double volume) => CheckError(soundio_outstream_set_volume(_context, volume));
public Span<SoundIoChannelArea> BeginWrite(ref int frameCount)
{
IntPtr arenas = default;
int nativeFrameCount = frameCount;
unsafe
{
var frameCountPtr = &nativeFrameCount;
var arenasPtr = &arenas;
CheckError(soundio_outstream_begin_write(_context, (IntPtr)arenasPtr, (IntPtr)frameCountPtr));
frameCount = *frameCountPtr;
return new Span<SoundIoChannelArea>((void*)arenas, Layout.ChannelCount);
}
}
public void EndWrite() => CheckError(soundio_outstream_end_write(_context));
protected virtual void Dispose(bool disposing)
{
if (_context != IntPtr.Zero)
{
soundio_outstream_destroy(_context);
_context = IntPtr.Zero;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~SoundIoOutStreamContext()
{
Dispose(false);
}
}
}

View File

@@ -1,38 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace SoundIOSharp
{
public static class MarshalEx
{
public static double ReadDouble(IntPtr handle, int offset = 0)
{
return BitConverter.Int64BitsToDouble(Marshal.ReadInt64(handle, offset));
}
public static void WriteDouble(IntPtr handle, double value)
{
WriteDouble(handle, 0, value);
}
public static void WriteDouble(IntPtr handle, int offset, double value)
{
Marshal.WriteInt64(handle, offset, BitConverter.DoubleToInt64Bits(value));
}
public static float ReadFloat(IntPtr handle, int offset = 0)
{
return BitConverter.Int32BitsToSingle(Marshal.ReadInt32(handle, offset));
}
public static void WriteFloat(IntPtr handle, float value)
{
WriteFloat(handle, 0, value);
}
public static void WriteFloat(IntPtr handle, int offset, float value)
{
Marshal.WriteInt32(handle, offset, BitConverter.SingleToInt32Bits(value));
}
}
}

View File

@@ -1,386 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace SoundIOSharp
{
public class SoundIO : IDisposable
{
Pointer<SoundIo> handle;
public SoundIO()
{
handle = Natives.soundio_create();
}
internal SoundIO(Pointer<SoundIo> handle)
{
this.handle = handle;
}
public void Dispose ()
{
foreach (var h in allocated_hglobals)
{
Marshal.FreeHGlobal(h);
}
Natives.soundio_destroy(handle);
}
// Equality (based on handle)
public override bool Equals(object other)
{
var d = other as SoundIO;
return d != null && this.handle == d.handle;
}
public override int GetHashCode()
{
return (int)(IntPtr)handle;
}
public static bool operator == (SoundIO obj1, SoundIO obj2)
{
return obj1 is null ? obj2 is null : obj1.Equals(obj2);
}
public static bool operator != (SoundIO obj1, SoundIO obj2)
{
return obj1 is null ? obj2 is object : !obj1.Equals(obj2);
}
// fields
// FIXME: this should be taken care in more centralized/decent manner... we don't want to write
// this kind of code anywhere we need string marshaling.
List<IntPtr> allocated_hglobals = new List<IntPtr>();
public string ApplicationName {
get { return Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(handle, app_name_offset)); }
set
{
unsafe
{
var existing = Marshal.ReadIntPtr(handle, app_name_offset);
if (allocated_hglobals.Contains (existing))
{
allocated_hglobals.Remove(existing);
Marshal.FreeHGlobal(existing);
}
var ptr = Marshal.StringToHGlobalAnsi(value);
Marshal.WriteIntPtr(handle, app_name_offset, ptr);
allocated_hglobals.Add(ptr);
}
}
}
static readonly int app_name_offset = (int)Marshal.OffsetOf<SoundIo>("app_name");
public SoundIOBackend CurrentBackend
{
get { return (SoundIOBackend)Marshal.ReadInt32(handle, current_backend_offset); }
}
static readonly int current_backend_offset = (int)Marshal.OffsetOf<SoundIo>("current_backend");
// emit_rtprio_warning
public Action EmitRealtimePriorityWarning
{
get { return emit_rtprio_warning; }
set
{
emit_rtprio_warning = value;
var ptr = Marshal.GetFunctionPointerForDelegate(on_devices_change);
Marshal.WriteIntPtr(handle, emit_rtprio_warning_offset, ptr);
}
}
static readonly int emit_rtprio_warning_offset = (int)Marshal.OffsetOf<SoundIo>("emit_rtprio_warning");
Action emit_rtprio_warning;
// jack_error_callback
public Action<string> JackErrorCallback
{
get { return jack_error_callback; }
set
{
jack_error_callback = value;
if (value == null)
{
jack_error_callback = null;
}
else
{
jack_error_callback_native = msg => jack_error_callback(msg);
}
var ptr = Marshal.GetFunctionPointerForDelegate(jack_error_callback_native);
Marshal.WriteIntPtr(handle, jack_error_callback_offset, ptr);
}
}
static readonly int jack_error_callback_offset = (int)Marshal.OffsetOf<SoundIo>("jack_error_callback");
Action<string> jack_error_callback;
delegate void jack_error_delegate(string message);
jack_error_delegate jack_error_callback_native;
// jack_info_callback
public Action<string> JackInfoCallback
{
get { return jack_info_callback; }
set
{
jack_info_callback = value;
if (value == null)
{
jack_info_callback = null;
}
else
{
jack_info_callback_native = msg => jack_info_callback(msg);
}
var ptr = Marshal.GetFunctionPointerForDelegate(jack_info_callback_native);
Marshal.WriteIntPtr(handle, jack_info_callback_offset, ptr);
}
}
static readonly int jack_info_callback_offset = (int)Marshal.OffsetOf<SoundIo>("jack_info_callback");
Action<string> jack_info_callback;
delegate void jack_info_delegate(string message);
jack_info_delegate jack_info_callback_native;
// on_backend_disconnect
public Action<int> OnBackendDisconnect
{
get { return on_backend_disconnect; }
set
{
on_backend_disconnect = value;
if (value == null)
{
on_backend_disconnect_native = null;
}
else
{
on_backend_disconnect_native = (sio, err) => on_backend_disconnect(err);
}
var ptr = Marshal.GetFunctionPointerForDelegate(on_backend_disconnect_native);
Marshal.WriteIntPtr(handle, on_backend_disconnect_offset, ptr);
}
}
static readonly int on_backend_disconnect_offset = (int)Marshal.OffsetOf<SoundIo>("on_backend_disconnect");
Action<int> on_backend_disconnect;
delegate void on_backend_disconnect_delegate(IntPtr handle, int errorCode);
on_backend_disconnect_delegate on_backend_disconnect_native;
// on_devices_change
public Action OnDevicesChange
{
get { return on_devices_change; }
set
{
on_devices_change = value;
if (value == null)
{
on_devices_change_native = null;
}
else
{
on_devices_change_native = sio => on_devices_change();
}
var ptr = Marshal.GetFunctionPointerForDelegate(on_devices_change_native);
Marshal.WriteIntPtr(handle, on_devices_change_offset, ptr);
}
}
static readonly int on_devices_change_offset = (int)Marshal.OffsetOf<SoundIo>("on_devices_change");
Action on_devices_change;
delegate void on_devices_change_delegate(IntPtr handle);
on_devices_change_delegate on_devices_change_native;
// on_events_signal
public Action OnEventsSignal
{
get { return on_events_signal; }
set
{
on_events_signal = value;
if (value == null)
{
on_events_signal_native = null;
}
else
{
on_events_signal_native = sio => on_events_signal();
}
var ptr = Marshal.GetFunctionPointerForDelegate(on_events_signal_native);
Marshal.WriteIntPtr(handle, on_events_signal_offset, ptr);
}
}
static readonly int on_events_signal_offset = (int)Marshal.OffsetOf<SoundIo>("on_events_signal");
Action on_events_signal;
delegate void on_events_signal_delegate(IntPtr handle);
on_events_signal_delegate on_events_signal_native;
// functions
public int BackendCount
{
get { return Natives.soundio_backend_count(handle); }
}
public int InputDeviceCount
{
get { return Natives.soundio_input_device_count(handle); }
}
public int OutputDeviceCount
{
get { return Natives.soundio_output_device_count(handle); }
}
public int DefaultInputDeviceIndex
{
get { return Natives.soundio_default_input_device_index(handle); }
}
public int DefaultOutputDeviceIndex
{
get { return Natives.soundio_default_output_device_index(handle); }
}
public SoundIOBackend GetBackend(int index)
{
return (SoundIOBackend)Natives.soundio_get_backend(handle, index);
}
public SoundIODevice GetInputDevice(int index)
{
return new SoundIODevice(Natives.soundio_get_input_device(handle, index));
}
public SoundIODevice GetOutputDevice(int index)
{
return new SoundIODevice(Natives.soundio_get_output_device(handle, index));
}
public void Connect()
{
var ret = (SoundIoError)Natives.soundio_connect(handle);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
public void ConnectBackend(SoundIOBackend backend)
{
var ret = (SoundIoError)Natives.soundio_connect_backend(handle, (SoundIoBackend)backend);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
public void Disconnect()
{
Natives.soundio_disconnect(handle);
}
public void FlushEvents()
{
Natives.soundio_flush_events(handle);
}
public void WaitEvents()
{
Natives.soundio_wait_events(handle);
}
public void Wakeup()
{
Natives.soundio_wakeup(handle);
}
public void ForceDeviceScan()
{
Natives.soundio_force_device_scan(handle);
}
public SoundIORingBuffer CreateRingBuffer(int capacity)
{
return new SoundIORingBuffer(Natives.soundio_ring_buffer_create(handle, capacity));
}
// static methods
public static string VersionString
{
get { return Marshal.PtrToStringAnsi(Natives.soundio_version_string()); }
}
public static int VersionMajor
{
get { return Natives.soundio_version_major(); }
}
public static int VersionMinor
{
get { return Natives.soundio_version_minor(); }
}
public static int VersionPatch
{
get { return Natives.soundio_version_patch(); }
}
public static string GetBackendName(SoundIOBackend backend)
{
return Marshal.PtrToStringAnsi(Natives.soundio_backend_name((SoundIoBackend)backend));
}
public static bool HaveBackend(SoundIOBackend backend)
{
return Natives.soundio_have_backend((SoundIoBackend)backend);
}
public static int GetBytesPerSample(SoundIOFormat format)
{
return Natives.soundio_get_bytes_per_sample((SoundIoFormat)format);
}
public static int GetBytesPerFrame(SoundIOFormat format, int channelCount)
{
return Natives.soundio_get_bytes_per_frame((SoundIoFormat)format, channelCount);
}
public static int GetBytesPerSecond(SoundIOFormat format, int channelCount, int sampleRate)
{
return Natives.soundio_get_bytes_per_second((SoundIoFormat)format, channelCount, sampleRate);
}
public static string GetSoundFormatName(SoundIOFormat format)
{
return Marshal.PtrToStringAnsi(Natives.soundio_format_string((SoundIoFormat)format));
}
}
}

View File

@@ -1,13 +0,0 @@
namespace SoundIOSharp
{
public enum SoundIOBackend
{
None,
Jack,
PulseAudio,
Alsa,
CoreAudio,
Wasapi,
Dummy
}
}

View File

@@ -1,30 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace SoundIOSharp
{
public struct SoundIOChannelArea
{
internal SoundIOChannelArea(Pointer<SoundIoChannelArea> handle)
{
this.handle = handle;
}
Pointer<SoundIoChannelArea> handle;
public IntPtr Pointer
{
get { return Marshal.ReadIntPtr(handle, ptr_offset); }
set { Marshal.WriteIntPtr(handle, ptr_offset, value); }
}
static readonly int ptr_offset = (int)Marshal.OffsetOf<SoundIoChannelArea>("ptr");
public int Step
{
get { return Marshal.ReadInt32(handle, step_offset); }
}
static readonly int step_offset = (int)Marshal.OffsetOf<SoundIoChannelArea>("step");
}
}

View File

@@ -1,34 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace SoundIOSharp
{
public struct SoundIOChannelAreas
{
static readonly int native_size = Marshal.SizeOf<SoundIoChannelArea>();
internal SoundIOChannelAreas(IntPtr head, int channelCount, int frameCount)
{
this.head = head;
this.channel_count = channelCount;
this.frame_count = frameCount;
}
IntPtr head;
int channel_count;
int frame_count;
public bool IsEmpty
{
get { return head == IntPtr.Zero; }
}
public SoundIOChannelArea GetArea(int channel)
{
return new SoundIOChannelArea(head + native_size * channel);
}
public int ChannelCount => channel_count;
public int FrameCount => frame_count;
}
}

View File

@@ -1,75 +0,0 @@
namespace SoundIOSharp
{
public enum SoundIOChannelId
{
Invalid,
FrontLeft,
FrontRight,
FrontCenter,
Lfe,
BackLeft,
BackRight,
FrontLeftCenter,
FrontRightCenter,
BackCenter,
SideLeft,
SideRight,
TopCenter,
TopFrontLeft,
TopFrontCenter,
TopFrontRight,
TopBackLeft,
TopBackCenter,
TopBackRight,
BackLeftCenter,
BackRightCenter,
FrontLeftWide,
FrontRightWide,
FrontLeftHigh,
FrontCenterHigh,
FrontRightHigh,
TopFrontLeftCenter,
TopFrontRightCenter,
TopSideLeft,
TopSideRight,
LeftLfe,
RightLfe,
Lfe2,
BottomCenter,
BottomLeftCenter,
BottomRightCenter,
MsMid,
MsSide,
AmbisonicW,
AmbisonicX,
AmbisonicY,
AmbisonicZ,
XyX,
XyY,
HeadphonesLeft,
HeadphonesRight,
ClickTrack,
ForeignLanguage,
HearingImpaired,
Narration,
Haptic,
DialogCentricMix,
Aux,
Aux0,
Aux1,
Aux2,
Aux3,
Aux4,
Aux5,
Aux6,
Aux7,
Aux8,
Aux9,
Aux10,
Aux11,
Aux12,
Aux13,
Aux14,
Aux15
}
}

View File

@@ -1,116 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace SoundIOSharp
{
public readonly struct SoundIOChannelLayout
{
public static int BuiltInCount
{
get { return Natives.soundio_channel_layout_builtin_count(); }
}
public static SoundIOChannelLayout GetBuiltIn(int index)
{
return new SoundIOChannelLayout(Natives.soundio_channel_layout_get_builtin(index));
}
public static SoundIOChannelLayout GetDefault(int channelCount)
{
var handle = Natives.soundio_channel_layout_get_default(channelCount);
return new SoundIOChannelLayout (handle);
}
public static SoundIOChannelId ParseChannelId(string name)
{
var ptr = Marshal.StringToHGlobalAnsi(name);
try
{
return (SoundIOChannelId)Natives.soundio_parse_channel_id(ptr, name.Length);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
// instance members
internal SoundIOChannelLayout(Pointer<SoundIoChannelLayout> handle)
{
this.handle = handle;
}
readonly Pointer<SoundIoChannelLayout> handle;
public bool IsNull
{
get { return handle.Handle == IntPtr.Zero; }
}
internal IntPtr Handle
{
get { return handle; }
}
public int ChannelCount
{
get { return IsNull ? 0 : Marshal.ReadInt32((IntPtr)handle + channel_count_offset); }
}
static readonly int channel_count_offset = (int)Marshal.OffsetOf<SoundIoChannelLayout>("channel_count");
public string Name
{
get { return IsNull ? null : Marshal.PtrToStringAnsi(Marshal.ReadIntPtr((IntPtr)handle + name_offset)); }
}
static readonly int name_offset = (int)Marshal.OffsetOf<SoundIoChannelLayout>("name");
public IEnumerable<SoundIOChannelId> Channels
{
get
{
if (IsNull) yield break;
for (int i = 0; i < 24; i++)
{
yield return (SoundIOChannelId)Marshal.ReadInt32((IntPtr)handle + channels_offset + sizeof(SoundIoChannelId) * i);
}
}
}
static readonly int channels_offset = (int)Marshal.OffsetOf<SoundIoChannelLayout>("channels");
public override bool Equals(object other)
{
if (!(other is SoundIOChannelLayout)) return false;
var s = (SoundIOChannelLayout) other;
return handle == s.handle || Natives.soundio_channel_layout_equal(handle, s.handle);
}
public override int GetHashCode()
{
return handle.GetHashCode();
}
public string DetectBuiltInName()
{
if (IsNull) throw new InvalidOperationException();
return Natives.soundio_channel_layout_detect_builtin(handle) ? Name : null;
}
public int FindChannel(SoundIOChannelId channel)
{
if (IsNull) throw new InvalidOperationException();
return Natives.soundio_channel_layout_find_channel(handle, (SoundIoChannelId)channel);
}
}
}

View File

@@ -1,267 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace SoundIOSharp
{
public class SoundIODevice
{
public static SoundIOChannelLayout BestMatchingChannelLayout(SoundIODevice device1, SoundIODevice device2)
{
var ptr1 = Marshal.ReadIntPtr(device1.handle, layouts_offset);
var ptr2 = Marshal.ReadIntPtr(device2.handle, layouts_offset);
return new SoundIOChannelLayout(Natives.soundio_best_matching_channel_layout(ptr1, device1.LayoutCount, ptr2, device2.LayoutCount));
}
internal SoundIODevice(Pointer<SoundIoDevice> handle)
{
this.handle = handle;
}
readonly Pointer<SoundIoDevice> handle;
// Equality (based on handle and native func)
public override bool Equals(object other)
{
var d = other as SoundIODevice;
return d != null && (this.handle == d.handle || Natives.soundio_device_equal (this.handle, d.handle));
}
public override int GetHashCode()
{
return (int)(IntPtr)handle;
}
public static bool operator == (SoundIODevice obj1, SoundIODevice obj2)
{
return obj1 is null ? obj2 is null : obj1.Equals(obj2);
}
public static bool operator != (SoundIODevice obj1, SoundIODevice obj2)
{
return obj1 is null ? obj2 is object : !obj1.Equals(obj2);
}
// fields
public SoundIODeviceAim Aim
{
get { return (SoundIODeviceAim)Marshal.ReadInt32(handle, aim_offset); }
}
static readonly int aim_offset = (int)Marshal.OffsetOf<SoundIoDevice>("aim");
public SoundIOFormat CurrentFormat
{
get { return (SoundIOFormat)Marshal.ReadInt32(handle, current_format_offset); }
}
static readonly int current_format_offset = (int)Marshal.OffsetOf<SoundIoDevice>("current_format");
public SoundIOChannelLayout CurrentLayout
{
get { return new SoundIOChannelLayout((IntPtr)handle + current_layout_offset); }
}
static readonly int current_layout_offset = (int)Marshal.OffsetOf<SoundIoDevice>("current_layout");
public int FormatCount
{
get { return Marshal.ReadInt32(handle, format_count_offset); }
}
static readonly int format_count_offset = (int)Marshal.OffsetOf<SoundIoDevice>("format_count");
public IEnumerable<SoundIOFormat> Formats
{
get
{
var ptr = Marshal.ReadIntPtr(handle, formats_offset);
for (int i = 0; i < FormatCount; i++)
{
yield return (SoundIOFormat)Marshal.ReadInt32(ptr, i);
}
}
}
static readonly int formats_offset = (int)Marshal.OffsetOf<SoundIoDevice>("formats");
public string Id
{
get { return Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(handle, id_offset)); }
}
static readonly int id_offset = (int)Marshal.OffsetOf<SoundIoDevice>("id");
public bool IsRaw
{
get { return Marshal.ReadInt32(handle, is_raw_offset) != 0; }
}
static readonly int is_raw_offset = (int)Marshal.OffsetOf<SoundIoDevice>("is_raw");
public int LayoutCount
{
get { return Marshal.ReadInt32(handle, layout_count_offset); }
}
static readonly int layout_count_offset = (int)Marshal.OffsetOf<SoundIoDevice>("layout_count");
public IEnumerable<SoundIOChannelLayout> Layouts
{
get
{
var ptr = Marshal.ReadIntPtr (handle, layouts_offset);
for (int i = 0; i < LayoutCount; i++)
{
yield return new SoundIOChannelLayout(ptr + i * Marshal.SizeOf<SoundIoChannelLayout>());
}
}
}
static readonly int layouts_offset = (int)Marshal.OffsetOf<SoundIoDevice>("layouts");
public string Name
{
get { return Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(handle, name_offset)); }
}
static readonly int name_offset = (int)Marshal.OffsetOf<SoundIoDevice>("name");
public int ProbeError
{
get { return Marshal.ReadInt32(handle, probe_error_offset); }
}
static readonly int probe_error_offset = (int)Marshal.OffsetOf<SoundIoDevice>("probe_error");
public int ReferenceCount
{
get { return Marshal.ReadInt32(handle, ref_count_offset); }
}
static readonly int ref_count_offset = (int)Marshal.OffsetOf<SoundIoDevice>("ref_count");
public int SampleRateCount
{
get { return Marshal.ReadInt32(handle, sample_rate_count_offset); }
}
static readonly int sample_rate_count_offset = (int)Marshal.OffsetOf<SoundIoDevice>("sample_rate_count");
public IEnumerable<SoundIOSampleRateRange> SampleRates
{
get
{
var ptr = Marshal.ReadIntPtr(handle, sample_rates_offset);
for (int i = 0; i < SampleRateCount; i++)
{
yield return new SoundIOSampleRateRange(Marshal.ReadInt32(ptr, i * 2), Marshal.ReadInt32(ptr, i * 2 + 1));
}
}
}
static readonly int sample_rates_offset = (int)Marshal.OffsetOf<SoundIoDevice>("sample_rates");
public double SoftwareLatencyCurrent
{
get { return MarshalEx.ReadDouble(handle, software_latency_current_offset); }
set { MarshalEx.WriteDouble(handle, software_latency_current_offset, value); }
}
static readonly int software_latency_current_offset = (int)Marshal.OffsetOf<SoundIoDevice>("software_latency_current");
public double SoftwareLatencyMin
{
get { return MarshalEx.ReadDouble(handle, software_latency_min_offset); }
set { MarshalEx.WriteDouble(handle, software_latency_min_offset, value); }
}
static readonly int software_latency_min_offset = (int)Marshal.OffsetOf<SoundIoDevice>("software_latency_min");
public double SoftwareLatencyMax
{
get { return MarshalEx.ReadDouble(handle, software_latency_max_offset); }
set { MarshalEx.WriteDouble(handle, software_latency_max_offset, value); }
}
static readonly int software_latency_max_offset = (int)Marshal.OffsetOf<SoundIoDevice>("software_latency_max");
public SoundIO SoundIO
{
get { return new SoundIO(Marshal.ReadIntPtr(handle, soundio_offset)); }
}
static readonly int soundio_offset = (int)Marshal.OffsetOf<SoundIoDevice>("soundio");
// functions
public void AddReference()
{
Natives.soundio_device_ref(handle);
}
public void RemoveReference()
{
Natives.soundio_device_unref(handle);
}
public void SortDeviceChannelLayouts()
{
Natives.soundio_device_sort_channel_layouts(handle);
}
public static readonly SoundIOFormat S16NE = BitConverter.IsLittleEndian ? SoundIOFormat.S16LE : SoundIOFormat.S16BE;
public static readonly SoundIOFormat U16NE = BitConverter.IsLittleEndian ? SoundIOFormat.U16LE : SoundIOFormat.U16BE;
public static readonly SoundIOFormat S24NE = BitConverter.IsLittleEndian ? SoundIOFormat.S24LE : SoundIOFormat.S24BE;
public static readonly SoundIOFormat U24NE = BitConverter.IsLittleEndian ? SoundIOFormat.U24LE : SoundIOFormat.U24BE;
public static readonly SoundIOFormat S32NE = BitConverter.IsLittleEndian ? SoundIOFormat.S32LE : SoundIOFormat.S32BE;
public static readonly SoundIOFormat U32NE = BitConverter.IsLittleEndian ? SoundIOFormat.U32LE : SoundIOFormat.U32BE;
public static readonly SoundIOFormat Float32NE = BitConverter.IsLittleEndian ? SoundIOFormat.Float32LE : SoundIOFormat.Float32BE;
public static readonly SoundIOFormat Float64NE = BitConverter.IsLittleEndian ? SoundIOFormat.Float64LE : SoundIOFormat.Float64BE;
public static readonly SoundIOFormat S16FE = !BitConverter.IsLittleEndian ? SoundIOFormat.S16LE : SoundIOFormat.S16BE;
public static readonly SoundIOFormat U16FE = !BitConverter.IsLittleEndian ? SoundIOFormat.U16LE : SoundIOFormat.U16BE;
public static readonly SoundIOFormat S24FE = !BitConverter.IsLittleEndian ? SoundIOFormat.S24LE : SoundIOFormat.S24BE;
public static readonly SoundIOFormat U24FE = !BitConverter.IsLittleEndian ? SoundIOFormat.U24LE : SoundIOFormat.U24BE;
public static readonly SoundIOFormat S32FE = !BitConverter.IsLittleEndian ? SoundIOFormat.S32LE : SoundIOFormat.S32BE;
public static readonly SoundIOFormat U32FE = !BitConverter.IsLittleEndian ? SoundIOFormat.U32LE : SoundIOFormat.U32BE;
public static readonly SoundIOFormat Float32FE = !BitConverter.IsLittleEndian ? SoundIOFormat.Float32LE : SoundIOFormat.Float32BE;
public static readonly SoundIOFormat Float64FE = !BitConverter.IsLittleEndian ? SoundIOFormat.Float64LE : SoundIOFormat.Float64BE;
public bool SupportsFormat(SoundIOFormat format)
{
return Natives.soundio_device_supports_format(handle, (SoundIoFormat)format);
}
public bool SupportsSampleRate(int sampleRate)
{
return Natives.soundio_device_supports_sample_rate(handle, sampleRate);
}
public bool SupportsChannelCount(int channelCount)
{
return Natives.soundio_device_supports_layout(handle, SoundIOChannelLayout.GetDefault(channelCount).Handle);
}
public int GetNearestSampleRate(int sampleRate)
{
return Natives.soundio_device_nearest_sample_rate(handle, sampleRate);
}
public SoundIOInStream CreateInStream()
{
return new SoundIOInStream(Natives.soundio_instream_create(handle));
}
public SoundIOOutStream CreateOutStream()
{
return new SoundIOOutStream(Natives.soundio_outstream_create(handle));
}
}
}

View File

@@ -1,8 +0,0 @@
namespace SoundIOSharp
{
public enum SoundIODeviceAim // soundio.h (228, 6)
{
Input,
Output
}
}

View File

@@ -1,10 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace SoundIOSharp
{
public class SoundIOException : Exception
{
internal SoundIOException(SoundIoError errorCode) : base (Marshal.PtrToStringAnsi(Natives.soundio_strerror((int) errorCode))) { }
}
}

View File

@@ -1,25 +0,0 @@
namespace SoundIOSharp
{
public enum SoundIOFormat
{
Invalid,
S8,
U8,
S16LE,
S16BE,
U16LE,
U16BE,
S24LE,
S24BE,
U24LE,
U24BE,
S32LE,
S32BE,
U32LE,
U32BE,
Float32LE,
Float32BE,
Float64LE,
Float64BE
}
}

View File

@@ -1,293 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace SoundIOSharp
{
public class SoundIOInStream : IDisposable
{
internal SoundIOInStream(Pointer<SoundIoInStream> handle)
{
this.handle = handle;
}
Pointer<SoundIoInStream> handle;
public void Dispose()
{
Natives.soundio_instream_destroy(handle);
}
// Equality (based on handle)
public override bool Equals(object other)
{
var d = other as SoundIOInStream;
return d != null && (this.handle == d.handle);
}
public override int GetHashCode()
{
return (int)(IntPtr)handle;
}
public static bool operator == (SoundIOInStream obj1, SoundIOInStream obj2)
{
return obj1 is null ? obj2 is null : obj1.Equals(obj2);
}
public static bool operator != (SoundIOInStream obj1, SoundIOInStream obj2)
{
return obj1 is null ? obj2 is object : !obj1.Equals(obj2);
}
// fields
public SoundIODevice Device
{
get { return new SoundIODevice(Marshal.ReadIntPtr(handle, device_offset)); }
}
static readonly int device_offset = (int)Marshal.OffsetOf<SoundIoInStream>("device");
public SoundIOFormat Format
{
get { return (SoundIOFormat)Marshal.ReadInt32(handle, format_offset); }
set { Marshal.WriteInt32(handle, format_offset, (int) value); }
}
static readonly int format_offset = (int)Marshal.OffsetOf<SoundIoInStream>("format");
public int SampleRate
{
get { return Marshal.ReadInt32(handle, sample_rate_offset); }
set { Marshal.WriteInt32(handle, sample_rate_offset, value); }
}
static readonly int sample_rate_offset = (int)Marshal.OffsetOf<SoundIoInStream>("sample_rate");
public SoundIOChannelLayout Layout
{
get { return new SoundIOChannelLayout ((IntPtr) handle + layout_offset); }
set
{
unsafe
{
Buffer.MemoryCopy((void*)((IntPtr)handle + layout_offset), (void*)value.Handle, Marshal.SizeOf<SoundIoChannelLayout>(), Marshal.SizeOf<SoundIoChannelLayout>());
}
}
}
static readonly int layout_offset = (int)Marshal.OffsetOf<SoundIoInStream>("layout");
public double SoftwareLatency
{
get { return MarshalEx.ReadDouble(handle, software_latency_offset); }
set { MarshalEx.WriteDouble(handle, software_latency_offset, value); }
}
static readonly int software_latency_offset = (int)Marshal.OffsetOf<SoundIoInStream>("software_latency");
// error_callback
public Action ErrorCallback
{
get { return error_callback; }
set
{
error_callback = value;
error_callback_native = _ => error_callback();
var ptr = Marshal.GetFunctionPointerForDelegate(error_callback_native);
Marshal.WriteIntPtr(handle, error_callback_offset, ptr);
}
}
static readonly int error_callback_offset = (int)Marshal.OffsetOf<SoundIoInStream>("error_callback");
Action error_callback;
delegate void error_callback_delegate(IntPtr handle);
error_callback_delegate error_callback_native;
// read_callback
public Action<int,int> ReadCallback
{
get { return read_callback; }
set
{
read_callback = value;
read_callback_native = (_, minFrameCount, maxFrameCount) => read_callback(minFrameCount, maxFrameCount);
var ptr = Marshal.GetFunctionPointerForDelegate(read_callback_native);
Marshal.WriteIntPtr(handle, read_callback_offset, ptr);
}
}
static readonly int read_callback_offset = (int)Marshal.OffsetOf<SoundIoInStream>("read_callback");
Action<int, int> read_callback;
delegate void read_callback_delegate(IntPtr handle, int min, int max);
read_callback_delegate read_callback_native;
// overflow_callback
public Action OverflowCallback
{
get { return overflow_callback; }
set
{
overflow_callback = value;
overflow_callback_native = _ => overflow_callback();
var ptr = Marshal.GetFunctionPointerForDelegate(overflow_callback_native);
Marshal.WriteIntPtr(handle, overflow_callback_offset, ptr);
}
}
static readonly int overflow_callback_offset = (int)Marshal.OffsetOf<SoundIoInStream>("overflow_callback");
Action overflow_callback;
delegate void overflow_callback_delegate(IntPtr handle);
overflow_callback_delegate overflow_callback_native;
// FIXME: this should be taken care in more centralized/decent manner... we don't want to write
// this kind of code anywhere we need string marshaling.
List<IntPtr> allocated_hglobals = new List<IntPtr>();
public string Name
{
get { return Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(handle, name_offset)); }
set
{
unsafe
{
var existing = Marshal.ReadIntPtr(handle, name_offset);
if (allocated_hglobals.Contains(existing))
{
allocated_hglobals.Remove(existing);
Marshal.FreeHGlobal(existing);
}
var ptr = Marshal.StringToHGlobalAnsi(value);
Marshal.WriteIntPtr(handle, name_offset, ptr);
allocated_hglobals.Add(ptr);
}
}
}
static readonly int name_offset = (int)Marshal.OffsetOf<SoundIoInStream>("name");
public bool NonTerminalHint
{
get { return Marshal.ReadInt32(handle, non_terminal_hint_offset) != 0; }
}
static readonly int non_terminal_hint_offset = (int)Marshal.OffsetOf<SoundIoInStream>("non_terminal_hint");
public int BytesPerFrame
{
get { return Marshal.ReadInt32(handle, bytes_per_frame_offset); }
}
static readonly int bytes_per_frame_offset = (int)Marshal.OffsetOf<SoundIoInStream>("bytes_per_frame");
public int BytesPerSample
{
get { return Marshal.ReadInt32(handle, bytes_per_sample_offset); }
}
static readonly int bytes_per_sample_offset = (int)Marshal.OffsetOf<SoundIoInStream>("bytes_per_sample");
public string LayoutErrorMessage
{
get
{
var code = (SoundIoError)Marshal.ReadInt32(handle, layout_error_offset);
return code == SoundIoError.SoundIoErrorNone ? null : Marshal.PtrToStringAnsi(Natives.soundio_strerror((int)code));
}
}
static readonly int layout_error_offset = (int)Marshal.OffsetOf<SoundIoInStream>("layout_error");
// functions
public void Open()
{
var ret = (SoundIoError)Natives.soundio_instream_open(handle);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
public void Start()
{
var ret = (SoundIoError)Natives.soundio_instream_start(handle);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
public SoundIOChannelAreas BeginRead(ref int frameCount)
{
IntPtr ptrs = default;
int nativeFrameCount = frameCount;
unsafe
{
var frameCountPtr = &nativeFrameCount;
var ptrptr = &ptrs;
var ret = (SoundIoError)Natives.soundio_instream_begin_read(handle, (IntPtr)ptrptr, (IntPtr)frameCountPtr);
frameCount = *frameCountPtr;
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
return new SoundIOChannelAreas(ptrs, Layout.ChannelCount, frameCount);
}
}
public void EndRead()
{
var ret = (SoundIoError)Natives.soundio_instream_end_read(handle);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
public void Pause(bool pause)
{
var ret = (SoundIoError)Natives.soundio_instream_pause(handle, pause);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
public double GetLatency()
{
unsafe
{
double* dptr = null;
IntPtr p = new IntPtr(dptr);
var ret = (SoundIoError)Natives.soundio_instream_get_latency(handle, p);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
dptr = (double*)p;
return *dptr;
}
}
}
}

View File

@@ -1,331 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace SoundIOSharp
{
public class SoundIOOutStream : IDisposable
{
internal SoundIOOutStream (Pointer<SoundIoOutStream> handle)
{
this.handle = handle;
}
Pointer<SoundIoOutStream> handle;
public void Dispose ()
{
Natives.soundio_outstream_destroy (handle);
}
// Equality (based on handle)
public override bool Equals (object other)
{
var d = other as SoundIOOutStream;
return d != null && (this.handle == d.handle);
}
public override int GetHashCode ()
{
return (int)(IntPtr)handle;
}
public static bool operator == (SoundIOOutStream obj1, SoundIOOutStream obj2)
{
return obj1 is null ? obj2 is null : obj1.Equals(obj2);
}
public static bool operator != (SoundIOOutStream obj1, SoundIOOutStream obj2)
{
return obj1 is null ? obj2 is object : !obj1.Equals(obj2);
}
// fields
public SoundIODevice Device
{
get { return new SoundIODevice(Marshal.ReadIntPtr(handle, device_offset)); }
}
static readonly int device_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("device");
public SoundIOFormat Format
{
get { return (SoundIOFormat) Marshal.ReadInt32(handle, format_offset); }
set { Marshal.WriteInt32(handle, format_offset, (int) value); }
}
static readonly int format_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("format");
public int SampleRate
{
get { return Marshal.ReadInt32(handle, sample_rate_offset); }
set { Marshal.WriteInt32(handle, sample_rate_offset, value); }
}
static readonly int sample_rate_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("sample_rate");
public SoundIOChannelLayout Layout
{
get { unsafe { return new SoundIOChannelLayout((IntPtr) (void*)((IntPtr)handle + layout_offset)); } }
set
{
unsafe
{
Buffer.MemoryCopy((void*)value.Handle, (void*)((IntPtr)handle + layout_offset), Marshal.SizeOf<SoundIoChannelLayout>(), Marshal.SizeOf<SoundIoChannelLayout>());
}
}
}
static readonly int layout_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("layout");
public double SoftwareLatency
{
get { return MarshalEx.ReadDouble (handle, software_latency_offset); }
set { MarshalEx.WriteDouble (handle, software_latency_offset, value); }
}
static readonly int software_latency_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("software_latency");
public float Volume
{
get { return MarshalEx.ReadFloat(handle, volume_offset); }
set { MarshalEx.WriteFloat(handle, volume_offset, value); }
}
static readonly int volume_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("volume");
// error_callback
public Action ErrorCallback
{
get { return error_callback; }
set
{
error_callback = value;
if (value == null)
{
error_callback_native = null;
}
else
{
error_callback_native = stream => error_callback();
}
var ptr = Marshal.GetFunctionPointerForDelegate(error_callback_native);
Marshal.WriteIntPtr(handle, error_callback_offset, ptr);
}
}
static readonly int error_callback_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("error_callback");
Action error_callback;
delegate void error_callback_delegate (IntPtr handle);
error_callback_delegate error_callback_native;
// write_callback
public Action<int, int> WriteCallback
{
get { return write_callback; }
set
{
write_callback = value;
if (value == null)
{
write_callback_native = null;
}
else
{
write_callback_native = (h, frame_count_min, frame_count_max) => write_callback(frame_count_min, frame_count_max);
}
var ptr = Marshal.GetFunctionPointerForDelegate (write_callback_native);
Marshal.WriteIntPtr (handle, write_callback_offset, ptr);
}
}
static readonly int write_callback_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("write_callback");
Action<int, int> write_callback;
delegate void write_callback_delegate(IntPtr handle, int min, int max);
write_callback_delegate write_callback_native;
// underflow_callback
public Action UnderflowCallback
{
get { return underflow_callback; }
set
{
underflow_callback = value;
if (value == null)
{
underflow_callback_native = null;
}
else
{
underflow_callback_native = h => underflow_callback();
}
var ptr = Marshal.GetFunctionPointerForDelegate (underflow_callback_native);
Marshal.WriteIntPtr (handle, underflow_callback_offset, ptr);
}
}
static readonly int underflow_callback_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("underflow_callback");
Action underflow_callback;
delegate void underflow_callback_delegate(IntPtr handle);
underflow_callback_delegate underflow_callback_native;
// FIXME: this should be taken care in more centralized/decent manner... we don't want to write
// this kind of code anywhere we need string marshaling.
List<IntPtr> allocated_hglobals = new List<IntPtr>();
public string Name {
get { return Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(handle, name_offset)); }
set
{
unsafe
{
var existing = Marshal.ReadIntPtr(handle, name_offset);
if (allocated_hglobals.Contains(existing))
{
allocated_hglobals.Remove(existing);
Marshal.FreeHGlobal(existing);
}
var ptr = Marshal.StringToHGlobalAnsi(value);
Marshal.WriteIntPtr(handle, name_offset, ptr);
allocated_hglobals.Add(ptr);
}
}
}
static readonly int name_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("name");
public bool NonTerminalHint
{
get { return Marshal.ReadInt32(handle, non_terminal_hint_offset) != 0; }
}
static readonly int non_terminal_hint_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("non_terminal_hint");
public int BytesPerFrame
{
get { return Marshal.ReadInt32(handle, bytes_per_frame_offset); }
}
static readonly int bytes_per_frame_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("bytes_per_frame");
public int BytesPerSample
{
get { return Marshal.ReadInt32(handle, bytes_per_sample_offset); }
}
static readonly int bytes_per_sample_offset = (int)Marshal.OffsetOf<SoundIoOutStream>("bytes_per_sample");
public string LayoutErrorMessage
{
get
{
var code = (SoundIoError)Marshal.ReadInt32(handle, layout_error_offset);
return code == SoundIoError.SoundIoErrorNone ? null : Marshal.PtrToStringAnsi(Natives.soundio_strerror((int)code));
}
}
static readonly int layout_error_offset = (int)Marshal.OffsetOf<SoundIoOutStream> ("layout_error");
// functions
public void Open ()
{
var ret = (SoundIoError)Natives.soundio_outstream_open(handle);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
public void Start ()
{
var ret = (SoundIoError)Natives.soundio_outstream_start(handle);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
public SoundIOChannelAreas BeginWrite(ref int frameCount)
{
IntPtr ptrs = default;
int nativeFrameCount = frameCount;
unsafe
{
var frameCountPtr = &nativeFrameCount;
var ptrptr = &ptrs;
var ret = (SoundIoError)Natives.soundio_outstream_begin_write(handle, (IntPtr)ptrptr, (IntPtr)frameCountPtr);
frameCount = *frameCountPtr;
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
return new SoundIOChannelAreas(ptrs, Layout.ChannelCount, frameCount);
}
}
public void EndWrite ()
{
var ret = (SoundIoError)Natives.soundio_outstream_end_write(handle);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
public void ClearBuffer ()
{
_ = Natives.soundio_outstream_clear_buffer(handle);
}
public void Pause (bool pause)
{
var ret = (SoundIoError)Natives.soundio_outstream_pause(handle, pause);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
public double GetLatency ()
{
unsafe
{
double* dptr = null;
IntPtr p = new IntPtr(dptr);
var ret = (SoundIoError)Natives.soundio_outstream_get_latency(handle, p);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
dptr = (double*)p;
return *dptr;
}
}
public void SetVolume (double volume)
{
var ret = (SoundIoError)Natives.soundio_outstream_set_volume(handle, volume);
if (ret != SoundIoError.SoundIoErrorNone)
{
throw new SoundIOException(ret);
}
}
}
}

View File

@@ -1,58 +0,0 @@
using System;
namespace SoundIOSharp
{
public class SoundIORingBuffer : IDisposable
{
internal SoundIORingBuffer(IntPtr handle)
{
this.handle = handle;
}
IntPtr handle;
public int Capacity
{
get { return Natives.soundio_ring_buffer_capacity(handle); }
}
public void Clear()
{
Natives.soundio_ring_buffer_clear(handle);
}
public void Dispose()
{
Natives.soundio_ring_buffer_destroy(handle);
}
public int FillCount
{
get { return Natives.soundio_ring_buffer_fill_count(handle); }
}
public int FreeCount
{
get { return Natives.soundio_ring_buffer_free_count(handle); }
}
public IntPtr ReadPointer
{
get { return Natives.soundio_ring_buffer_read_ptr(handle); }
}
public IntPtr WritePointer
{
get { return Natives.soundio_ring_buffer_write_ptr(handle); }
}
public void AdvanceReadPointer(int count)
{
Natives.soundio_ring_buffer_advance_read_ptr(handle, count);
}
public void AdvanceWritePointer(int count)
{
Natives.soundio_ring_buffer_advance_write_ptr(handle, count);
}
}
}

View File

@@ -1,14 +0,0 @@
namespace SoundIOSharp
{
public readonly struct SoundIOSampleRateRange
{
internal SoundIOSampleRateRange(int min, int max)
{
Min = min;
Max = max;
}
public readonly int Min;
public readonly int Max;
}
}

View File

@@ -1,643 +0,0 @@
// This source file is generated by nclang PInvokeGenerator.
using System;
using System.Runtime.InteropServices;
using delegate0 = SoundIOSharp.Delegates.delegate0;
using delegate1 = SoundIOSharp.Delegates.delegate1;
using delegate2 = SoundIOSharp.Delegates.delegate2;
using delegate3 = SoundIOSharp.Delegates.delegate3;
using delegate4 = SoundIOSharp.Delegates.delegate4;
using delegate5 = SoundIOSharp.Delegates.delegate5;
using delegate6 = SoundIOSharp.Delegates.delegate6;
using delegate7 = SoundIOSharp.Delegates.delegate7;
using delegate8 = SoundIOSharp.Delegates.delegate8;
using delegate9 = SoundIOSharp.Delegates.delegate9;
namespace SoundIOSharp
{
enum SoundIoError // soundio.h (72, 6)
{
SoundIoErrorNone = 0,
SoundIoErrorNoMem = 1,
SoundIoErrorInitAudioBackend = 2,
SoundIoErrorSystemResources = 3,
SoundIoErrorOpeningDevice = 4,
SoundIoErrorNoSuchDevice = 5,
SoundIoErrorInvalid = 6,
SoundIoErrorBackendUnavailable = 7,
SoundIoErrorStreaming = 8,
SoundIoErrorIncompatibleDevice = 9,
SoundIoErrorNoSuchClient = 10,
SoundIoErrorIncompatibleBackend = 11,
SoundIoErrorBackendDisconnected = 12,
SoundIoErrorInterrupted = 13,
SoundIoErrorUnderflow = 14,
SoundIoErrorEncodingString = 15,
}
enum SoundIoChannelId // soundio.h (106, 6)
{
SoundIoChannelIdInvalid = 0,
SoundIoChannelIdFrontLeft = 1,
SoundIoChannelIdFrontRight = 2,
SoundIoChannelIdFrontCenter = 3,
SoundIoChannelIdLfe = 4,
SoundIoChannelIdBackLeft = 5,
SoundIoChannelIdBackRight = 6,
SoundIoChannelIdFrontLeftCenter = 7,
SoundIoChannelIdFrontRightCenter = 8,
SoundIoChannelIdBackCenter = 9,
SoundIoChannelIdSideLeft = 10,
SoundIoChannelIdSideRight = 11,
SoundIoChannelIdTopCenter = 12,
SoundIoChannelIdTopFrontLeft = 13,
SoundIoChannelIdTopFrontCenter = 14,
SoundIoChannelIdTopFrontRight = 15,
SoundIoChannelIdTopBackLeft = 16,
SoundIoChannelIdTopBackCenter = 17,
SoundIoChannelIdTopBackRight = 18,
SoundIoChannelIdBackLeftCenter = 19,
SoundIoChannelIdBackRightCenter = 20,
SoundIoChannelIdFrontLeftWide = 21,
SoundIoChannelIdFrontRightWide = 22,
SoundIoChannelIdFrontLeftHigh = 23,
SoundIoChannelIdFrontCenterHigh = 24,
SoundIoChannelIdFrontRightHigh = 25,
SoundIoChannelIdTopFrontLeftCenter = 26,
SoundIoChannelIdTopFrontRightCenter = 27,
SoundIoChannelIdTopSideLeft = 28,
SoundIoChannelIdTopSideRight = 29,
SoundIoChannelIdLeftLfe = 30,
SoundIoChannelIdRightLfe = 31,
SoundIoChannelIdLfe2 = 32,
SoundIoChannelIdBottomCenter = 33,
SoundIoChannelIdBottomLeftCenter = 34,
SoundIoChannelIdBottomRightCenter = 35,
SoundIoChannelIdMsMid = 36,
SoundIoChannelIdMsSide = 37,
SoundIoChannelIdAmbisonicW = 38,
SoundIoChannelIdAmbisonicX = 39,
SoundIoChannelIdAmbisonicY = 40,
SoundIoChannelIdAmbisonicZ = 41,
SoundIoChannelIdXyX = 42,
SoundIoChannelIdXyY = 43,
SoundIoChannelIdHeadphonesLeft = 44,
SoundIoChannelIdHeadphonesRight = 45,
SoundIoChannelIdClickTrack = 46,
SoundIoChannelIdForeignLanguage = 47,
SoundIoChannelIdHearingImpaired = 48,
SoundIoChannelIdNarration = 49,
SoundIoChannelIdHaptic = 50,
SoundIoChannelIdDialogCentricMix = 51,
SoundIoChannelIdAux = 52,
SoundIoChannelIdAux0 = 53,
SoundIoChannelIdAux1 = 54,
SoundIoChannelIdAux2 = 55,
SoundIoChannelIdAux3 = 56,
SoundIoChannelIdAux4 = 57,
SoundIoChannelIdAux5 = 58,
SoundIoChannelIdAux6 = 59,
SoundIoChannelIdAux7 = 60,
SoundIoChannelIdAux8 = 61,
SoundIoChannelIdAux9 = 62,
SoundIoChannelIdAux10 = 63,
SoundIoChannelIdAux11 = 64,
SoundIoChannelIdAux12 = 65,
SoundIoChannelIdAux13 = 66,
SoundIoChannelIdAux14 = 67,
SoundIoChannelIdAux15 = 68,
}
enum SoundIoChannelLayoutId // soundio.h (189, 6)
{
SoundIoChannelLayoutIdMono = 0,
SoundIoChannelLayoutIdStereo = 1,
SoundIoChannelLayoutId2Point1 = 2,
SoundIoChannelLayoutId3Point0 = 3,
SoundIoChannelLayoutId3Point0Back = 4,
SoundIoChannelLayoutId3Point1 = 5,
SoundIoChannelLayoutId4Point0 = 6,
SoundIoChannelLayoutIdQuad = 7,
SoundIoChannelLayoutIdQuadSide = 8,
SoundIoChannelLayoutId4Point1 = 9,
SoundIoChannelLayoutId5Point0Back = 10,
SoundIoChannelLayoutId5Point0Side = 11,
SoundIoChannelLayoutId5Point1 = 12,
SoundIoChannelLayoutId5Point1Back = 13,
SoundIoChannelLayoutId6Point0Side = 14,
SoundIoChannelLayoutId6Point0Front = 15,
SoundIoChannelLayoutIdHexagonal = 16,
SoundIoChannelLayoutId6Point1 = 17,
SoundIoChannelLayoutId6Point1Back = 18,
SoundIoChannelLayoutId6Point1Front = 19,
SoundIoChannelLayoutId7Point0 = 20,
SoundIoChannelLayoutId7Point0Front = 21,
SoundIoChannelLayoutId7Point1 = 22,
SoundIoChannelLayoutId7Point1Wide = 23,
SoundIoChannelLayoutId7Point1WideBack = 24,
SoundIoChannelLayoutIdOctagonal = 25,
}
enum SoundIoBackend // soundio.h (218, 6)
{
SoundIoBackendNone = 0,
SoundIoBackendJack = 1,
SoundIoBackendPulseAudio = 2,
SoundIoBackendAlsa = 3,
SoundIoBackendCoreAudio = 4,
SoundIoBackendWasapi = 5,
SoundIoBackendDummy = 6,
}
enum SoundIoDeviceAim // soundio.h (228, 6)
{
SoundIoDeviceAimInput = 0,
SoundIoDeviceAimOutput = 1,
}
enum SoundIoFormat // soundio.h (235, 6)
{
SoundIoFormatInvalid = 0,
SoundIoFormatS8 = 1,
SoundIoFormatU8 = 2,
SoundIoFormatS16LE = 3,
SoundIoFormatS16BE = 4,
SoundIoFormatU16LE = 5,
SoundIoFormatU16BE = 6,
SoundIoFormatS24LE = 7,
SoundIoFormatS24BE = 8,
SoundIoFormatU24LE = 9,
SoundIoFormatU24BE = 10,
SoundIoFormatS32LE = 11,
SoundIoFormatS32BE = 12,
SoundIoFormatU32LE = 13,
SoundIoFormatU32BE = 14,
SoundIoFormatFloat32LE = 15,
SoundIoFormatFloat32BE = 16,
SoundIoFormatFloat64LE = 17,
SoundIoFormatFloat64BE = 18,
}
[StructLayout(LayoutKind.Sequential)]
struct SoundIoChannelLayout // soundio.h (306, 8)
{
[CTypeDetails("Pointer<byte>")] public System.IntPtr @name;
public int @channel_count;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)]
[CTypeDetails("ConstArrayOf<SoundIoChannelId>")] public SoundIoChannelId[] @channels;
}
[StructLayout(LayoutKind.Sequential)]
struct SoundIoSampleRateRange // soundio.h (313, 8)
{
public int @min;
public int @max;
}
[StructLayout(LayoutKind.Sequential)]
struct SoundIoChannelArea // soundio.h (319, 8)
{
[CTypeDetails("Pointer<byte>")] public System.IntPtr @ptr;
public int @step;
}
[StructLayout(LayoutKind.Sequential)]
struct SoundIo // soundio.h (328, 8)
{
[CTypeDetails("Pointer<void>")] public System.IntPtr @userdata;
[CTypeDetails("Pointer<void (SoundIo *)>")] public delegate0 @on_devices_change;
[CTypeDetails("Pointer<void (SoundIo *, int)>")] public delegate1 @on_backend_disconnect;
[CTypeDetails("Pointer<void (SoundIo *)>")] public Delegates.delegate0 @on_events_signal;
public SoundIoBackend @current_backend;
[CTypeDetails("Pointer<byte>")] public System.IntPtr @app_name;
[CTypeDetails("Pointer<void ()>")] public delegate2 @emit_rtprio_warning;
[CTypeDetails("Pointer<void (const char *)>")] public delegate3 @jack_info_callback;
[CTypeDetails("Pointer<void (const char *)>")] public Delegates.delegate3 @jack_error_callback;
}
[StructLayout(LayoutKind.Sequential)]
struct SoundIoDevice // soundio.h (387, 8)
{
[CTypeDetails("Pointer<SoundIo>")] public System.IntPtr @soundio;
[CTypeDetails("Pointer<byte>")] public System.IntPtr @id;
[CTypeDetails("Pointer<byte>")] public System.IntPtr @name;
public SoundIoDeviceAim @aim;
[CTypeDetails("Pointer<SoundIoChannelLayout>")] public System.IntPtr @layouts;
public int @layout_count;
public SoundIoChannelLayout @current_layout;
[CTypeDetails("Pointer<SoundIoFormat>")] public System.IntPtr @formats;
public int @format_count;
public SoundIoFormat @current_format;
[CTypeDetails("Pointer<SoundIoSampleRateRange>")] public System.IntPtr @sample_rates;
public int @sample_rate_count;
public int @sample_rate_current;
public double @software_latency_min;
public double @software_latency_max;
public double @software_latency_current;
public bool @is_raw;
public int @ref_count;
public int @probe_error;
}
[StructLayout(LayoutKind.Sequential)]
struct SoundIoOutStream // soundio.h (497, 8)
{
[CTypeDetails("Pointer<SoundIoDevice>")] public System.IntPtr @device;
public SoundIoFormat @format;
public int @sample_rate;
public SoundIoChannelLayout @layout;
public double @software_latency;
public float @volume;
[CTypeDetails("Pointer<void>")] public System.IntPtr @userdata;
[CTypeDetails("Pointer<void (SoundIoOutStream *, int, int)>")] public delegate4 @write_callback;
[CTypeDetails("Pointer<void (SoundIoOutStream *)>")] public delegate5 @underflow_callback;
[CTypeDetails("Pointer<void (SoundIoOutStream *, int)>")] public delegate6 @error_callback;
[CTypeDetails("Pointer<byte>")] public System.IntPtr @name;
public bool @non_terminal_hint;
public int @bytes_per_frame;
public int @bytes_per_sample;
public int @layout_error;
}
[StructLayout(LayoutKind.Sequential)]
struct SoundIoInStream // soundio.h (600, 8)
{
[CTypeDetails("Pointer<SoundIoDevice>")] public System.IntPtr @device;
public SoundIoFormat @format;
public int @sample_rate;
public SoundIoChannelLayout @layout;
public double @software_latency;
[CTypeDetails("Pointer<void>")] public System.IntPtr @userdata;
[CTypeDetails("Pointer<void (SoundIoInStream *, int, int)>")] public delegate7 @read_callback;
[CTypeDetails("Pointer<void (SoundIoInStream *)>")] public delegate8 @overflow_callback;
[CTypeDetails("Pointer<void (SoundIoInStream *, int)>")] public delegate9 @error_callback;
[CTypeDetails("Pointer<byte>")] public System.IntPtr @name;
public bool @non_terminal_hint;
public int @bytes_per_frame;
public int @bytes_per_sample;
public int @layout_error;
}
[StructLayout(LayoutKind.Sequential)]
struct SoundIoRingBuffer // soundio.h (1170, 8)
{
}
partial class Natives
{
const string LibraryName = "libsoundio";
// function soundio_version_string - soundio.h (682, 28)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_version_string();
// function soundio_version_major - soundio.h (684, 20)
[DllImport(LibraryName)]
internal static extern int soundio_version_major();
// function soundio_version_minor - soundio.h (686, 20)
[DllImport(LibraryName)]
internal static extern int soundio_version_minor();
// function soundio_version_patch - soundio.h (688, 20)
[DllImport(LibraryName)]
internal static extern int soundio_version_patch();
// function soundio_create - soundio.h (694, 32)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_create();
// function soundio_destroy - soundio.h (695, 21)
[DllImport(LibraryName)]
internal static extern void soundio_destroy([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_connect - soundio.h (705, 20)
[DllImport(LibraryName)]
internal static extern int soundio_connect([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_connect_backend - soundio.h (717, 20)
[DllImport(LibraryName)]
internal static extern int soundio_connect_backend([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio, SoundIoBackend @backend);
// function soundio_disconnect - soundio.h (718, 21)
[DllImport(LibraryName)]
internal static extern void soundio_disconnect([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_strerror - soundio.h (721, 28)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_strerror(int @error);
// function soundio_backend_name - soundio.h (723, 28)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_backend_name(SoundIoBackend @backend);
// function soundio_backend_count - soundio.h (726, 20)
[DllImport(LibraryName)]
internal static extern int soundio_backend_count([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_get_backend - soundio.h (729, 36)
[DllImport(LibraryName)]
internal static extern SoundIoBackend soundio_get_backend([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio, int @index);
// function soundio_have_backend - soundio.h (732, 21)
[DllImport(LibraryName)]
internal static extern bool soundio_have_backend(SoundIoBackend @backend);
// function soundio_flush_events - soundio.h (756, 21)
[DllImport(LibraryName)]
internal static extern void soundio_flush_events([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_wait_events - soundio.h (760, 21)
[DllImport(LibraryName)]
internal static extern void soundio_wait_events([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_wakeup - soundio.h (763, 21)
[DllImport(LibraryName)]
internal static extern void soundio_wakeup([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_force_device_scan - soundio.h (780, 21)
[DllImport(LibraryName)]
internal static extern void soundio_force_device_scan([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_channel_layout_equal - soundio.h (787, 21)
[DllImport(LibraryName)]
internal static extern bool soundio_channel_layout_equal([CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @a, [CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @b);
// function soundio_get_channel_name - soundio.h (791, 28)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_get_channel_name(SoundIoChannelId @id);
// function soundio_parse_channel_id - soundio.h (795, 38)
[DllImport(LibraryName)]
internal static extern SoundIoChannelId soundio_parse_channel_id([CTypeDetails("Pointer<byte>")]System.IntPtr @str, int @str_len);
// function soundio_channel_layout_builtin_count - soundio.h (798, 20)
[DllImport(LibraryName)]
internal static extern int soundio_channel_layout_builtin_count();
// function soundio_channel_layout_get_builtin - soundio.h (803, 51)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_channel_layout_get_builtin(int @index);
// function soundio_channel_layout_get_default - soundio.h (806, 51)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_channel_layout_get_default(int @channel_count);
// function soundio_channel_layout_find_channel - soundio.h (809, 20)
[DllImport(LibraryName)]
internal static extern int soundio_channel_layout_find_channel([CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @layout, SoundIoChannelId @channel);
// function soundio_channel_layout_detect_builtin - soundio.h (814, 21)
[DllImport(LibraryName)]
internal static extern bool soundio_channel_layout_detect_builtin([CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @layout);
// function soundio_best_matching_channel_layout - soundio.h (819, 51)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_best_matching_channel_layout([CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @preferred_layouts, int @preferred_layout_count, [CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @available_layouts, int @available_layout_count);
// function soundio_sort_channel_layouts - soundio.h (824, 21)
[DllImport(LibraryName)]
internal static extern void soundio_sort_channel_layouts([CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @layouts, int @layout_count);
// function soundio_get_bytes_per_sample - soundio.h (830, 20)
[DllImport(LibraryName)]
internal static extern int soundio_get_bytes_per_sample(SoundIoFormat @format);
// function soundio_get_bytes_per_frame - soundio.h (833, 19)
[DllImport(LibraryName)]
internal static extern int soundio_get_bytes_per_frame(SoundIoFormat @format, int @channel_count);
// function soundio_get_bytes_per_second - soundio.h (838, 19)
[DllImport(LibraryName)]
internal static extern int soundio_get_bytes_per_second(SoundIoFormat @format, int @channel_count, int @sample_rate);
// function soundio_format_string - soundio.h (845, 29)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_format_string(SoundIoFormat @format);
// function soundio_input_device_count - soundio.h (861, 20)
[DllImport(LibraryName)]
internal static extern int soundio_input_device_count([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_output_device_count - soundio.h (864, 20)
[DllImport(LibraryName)]
internal static extern int soundio_output_device_count([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_get_input_device - soundio.h (870, 38)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_get_input_device([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio, int @index);
// function soundio_get_output_device - soundio.h (875, 38)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_get_output_device([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio, int @index);
// function soundio_default_input_device_index - soundio.h (880, 20)
[DllImport(LibraryName)]
internal static extern int soundio_default_input_device_index([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_default_output_device_index - soundio.h (885, 20)
[DllImport(LibraryName)]
internal static extern int soundio_default_output_device_index([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio);
// function soundio_device_ref - soundio.h (888, 21)
[DllImport(LibraryName)]
internal static extern void soundio_device_ref([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device);
// function soundio_device_unref - soundio.h (891, 21)
[DllImport(LibraryName)]
internal static extern void soundio_device_unref([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device);
// function soundio_device_equal - soundio.h (895, 21)
[DllImport(LibraryName)]
internal static extern bool soundio_device_equal([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @a, [CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @b);
// function soundio_device_sort_channel_layouts - soundio.h (900, 21)
[DllImport(LibraryName)]
internal static extern void soundio_device_sort_channel_layouts([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device);
// function soundio_device_supports_format - soundio.h (904, 21)
[DllImport(LibraryName)]
internal static extern bool soundio_device_supports_format([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device, SoundIoFormat @format);
// function soundio_device_supports_layout - soundio.h (909, 21)
[DllImport(LibraryName)]
internal static extern bool soundio_device_supports_layout([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device, [CTypeDetails("Pointer<SoundIoChannelLayout>")]System.IntPtr @layout);
// function soundio_device_supports_sample_rate - soundio.h (914, 21)
[DllImport(LibraryName)]
internal static extern bool soundio_device_supports_sample_rate([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device, int @sample_rate);
// function soundio_device_nearest_sample_rate - soundio.h (919, 20)
[DllImport(LibraryName)]
internal static extern int soundio_device_nearest_sample_rate([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device, int @sample_rate);
// function soundio_outstream_create - soundio.h (929, 41)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_outstream_create([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device);
// function soundio_outstream_destroy - soundio.h (931, 21)
[DllImport(LibraryName)]
internal static extern void soundio_outstream_destroy([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream);
// function soundio_outstream_open - soundio.h (954, 20)
[DllImport(LibraryName)]
internal static extern int soundio_outstream_open([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream);
// function soundio_outstream_start - soundio.h (965, 20)
[DllImport(LibraryName)]
internal static extern int soundio_outstream_start([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream);
// function soundio_outstream_begin_write - soundio.h (997, 20)
[DllImport(LibraryName)]
internal static extern int soundio_outstream_begin_write([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream, [CTypeDetails("Pointer<System.IntPtr>")]System.IntPtr @areas, [CTypeDetails("Pointer<int>")]System.IntPtr @frame_count);
// function soundio_outstream_end_write - soundio.h (1009, 20)
[DllImport(LibraryName)]
internal static extern int soundio_outstream_end_write([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream);
// function soundio_outstream_clear_buffer - soundio.h (1024, 20)
[DllImport(LibraryName)]
internal static extern int soundio_outstream_clear_buffer([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream);
// function soundio_outstream_pause - soundio.h (1045, 20)
[DllImport(LibraryName)]
internal static extern int soundio_outstream_pause([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream, bool @pause);
// function soundio_outstream_get_latency - soundio.h (1058, 20)
[DllImport(LibraryName)]
internal static extern int soundio_outstream_get_latency([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream, [CTypeDetails("Pointer<double>")]System.IntPtr @out_latency);
// function soundio_outstream_set_volume - soundio.h (1061, 20)
[DllImport(LibraryName)]
internal static extern int soundio_outstream_set_volume([CTypeDetails("Pointer<SoundIoOutStream>")]System.IntPtr @outstream, double @volume);
// function soundio_instream_create - soundio.h (1071, 40)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_instream_create([CTypeDetails("Pointer<SoundIoDevice>")]System.IntPtr @device);
// function soundio_instream_destroy - soundio.h (1073, 21)
[DllImport(LibraryName)]
internal static extern void soundio_instream_destroy([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream);
// function soundio_instream_open - soundio.h (1093, 20)
[DllImport(LibraryName)]
internal static extern int soundio_instream_open([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream);
// function soundio_instream_start - soundio.h (1102, 20)
[DllImport(LibraryName)]
internal static extern int soundio_instream_start([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream);
// function soundio_instream_begin_read - soundio.h (1133, 20)
[DllImport(LibraryName)]
internal static extern int soundio_instream_begin_read([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream, [CTypeDetails("Pointer<System.IntPtr>")]System.IntPtr @areas, [CTypeDetails("Pointer<int>")]System.IntPtr @frame_count);
// function soundio_instream_end_read - soundio.h (1143, 20)
[DllImport(LibraryName)]
internal static extern int soundio_instream_end_read([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream);
// function soundio_instream_pause - soundio.h (1156, 20)
[DllImport(LibraryName)]
internal static extern int soundio_instream_pause([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream, bool @pause);
// function soundio_instream_get_latency - soundio.h (1166, 20)
[DllImport(LibraryName)]
internal static extern int soundio_instream_get_latency([CTypeDetails("Pointer<SoundIoInStream>")]System.IntPtr @instream, [CTypeDetails("Pointer<double>")]System.IntPtr @out_latency);
// function soundio_ring_buffer_create - soundio.h (1181, 42)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_ring_buffer_create([CTypeDetails("Pointer<SoundIo>")]System.IntPtr @soundio, int @requested_capacity);
// function soundio_ring_buffer_destroy - soundio.h (1182, 21)
[DllImport(LibraryName)]
internal static extern void soundio_ring_buffer_destroy([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
// function soundio_ring_buffer_capacity - soundio.h (1186, 20)
[DllImport(LibraryName)]
internal static extern int soundio_ring_buffer_capacity([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
// function soundio_ring_buffer_write_ptr - soundio.h (1189, 22)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_ring_buffer_write_ptr([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
// function soundio_ring_buffer_advance_write_ptr - soundio.h (1191, 21)
[DllImport(LibraryName)]
internal static extern void soundio_ring_buffer_advance_write_ptr([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer, int @count);
// function soundio_ring_buffer_read_ptr - soundio.h (1194, 22)
[DllImport(LibraryName)]
internal static extern System.IntPtr soundio_ring_buffer_read_ptr([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
// function soundio_ring_buffer_advance_read_ptr - soundio.h (1196, 21)
[DllImport(LibraryName)]
internal static extern void soundio_ring_buffer_advance_read_ptr([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer, int @count);
// function soundio_ring_buffer_fill_count - soundio.h (1199, 20)
[DllImport(LibraryName)]
internal static extern int soundio_ring_buffer_fill_count([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
// function soundio_ring_buffer_free_count - soundio.h (1202, 20)
[DllImport(LibraryName)]
internal static extern int soundio_ring_buffer_free_count([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
// function soundio_ring_buffer_clear - soundio.h (1205, 21)
[DllImport(LibraryName)]
internal static extern void soundio_ring_buffer_clear([CTypeDetails("Pointer<SoundIoRingBuffer>")]System.IntPtr @ring_buffer);
}
class Delegates
{
public delegate void delegate0(System.IntPtr p0);
public delegate void delegate1(System.IntPtr p0, int p1);
public delegate void delegate2();
public delegate void delegate3(System.IntPtr p0);
public delegate void delegate4(System.IntPtr p0, int p1, int p2);
public delegate void delegate5(System.IntPtr p0);
public delegate void delegate6(System.IntPtr p0, int p1);
public delegate void delegate7(System.IntPtr p0, int p1, int p2);
public delegate void delegate8(System.IntPtr p0);
public delegate void delegate9(System.IntPtr p0, int p1);
}
public struct Pointer<T>
{
public IntPtr Handle;
public static implicit operator IntPtr(Pointer<T> value) { return value.Handle; }
public static implicit operator Pointer<T>(IntPtr value) { return new Pointer<T>(value); }
public Pointer(IntPtr handle)
{
Handle = handle;
}
public override bool Equals(object obj)
{
return obj is Pointer<T> && this == (Pointer<T>)obj;
}
public override int GetHashCode()
{
return (int)Handle;
}
public static bool operator ==(Pointer<T> p1, Pointer<T> p2)
{
return p1.Handle == p2.Handle;
}
public static bool operator !=(Pointer<T> p1, Pointer<T> p2)
{
return p1.Handle != p2.Handle;
}
}
public struct ArrayOf<T> { }
public struct ConstArrayOf<T> { }
public class CTypeDetailsAttribute : Attribute
{
public CTypeDetailsAttribute(string value)
{
Value = value;
}
public string Value { get; set; }
}
}

View File

@@ -1,19 +1,20 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Backends.SoundIo.Native;
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Memory;
using SoundIOSharp;
using System;
using System.Collections.Concurrent;
using System.Threading;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
namespace Ryujinx.Audio.Backends.SoundIo
{
public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly SoundIO _audioContext;
private readonly SoundIODevice _audioDevice;
private readonly SoundIoContext _audioContext;
private readonly SoundIoDeviceContext _audioDevice;
private readonly ManualResetEvent _updateRequiredEvent;
private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
@@ -21,7 +22,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
public SoundIoHardwareDeviceDriver()
{
_audioContext = new SoundIO();
_audioContext = SoundIoContext.Create();
_updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true);
_sessions = new ConcurrentDictionary<SoundIoHardwareDeviceSession, byte>();
@@ -29,24 +30,23 @@ namespace Ryujinx.Audio.Backends.SoundIo
_audioContext.Connect();
_audioContext.FlushEvents();
_audioDevice = FindNonRawDefaultAudioDevice(_audioContext, true);
_audioDevice = FindValidAudioDevice(_audioContext, true);
}
public static bool IsSupported => IsSupportedInternal();
private static bool IsSupportedInternal()
{
SoundIO context = null;
SoundIODevice device = null;
SoundIOOutStream stream = null;
SoundIoContext context = null;
SoundIoDeviceContext device = null;
SoundIoOutStreamContext stream = null;
bool backendDisconnected = false;
try
{
context = new SoundIO();
context.OnBackendDisconnect = (i) =>
context = SoundIoContext.Create();
context.OnBackendDisconnect = err =>
{
backendDisconnected = true;
};
@@ -64,7 +64,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
return false;
}
device = FindNonRawDefaultAudioDevice(context);
device = FindValidAudioDevice(context);
if (device == null || backendDisconnected)
{
@@ -86,30 +86,23 @@ namespace Ryujinx.Audio.Backends.SoundIo
}
finally
{
if (stream != null)
{
stream.Dispose();
}
if (context != null)
{
context.Dispose();
}
stream?.Dispose();
context?.Dispose();
}
}
private static SoundIODevice FindNonRawDefaultAudioDevice(SoundIO audioContext, bool fallback = false)
private static SoundIoDeviceContext FindValidAudioDevice(SoundIoContext audioContext, bool fallback = false)
{
SoundIODevice defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex);
SoundIoDeviceContext defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex);
if (!defaultAudioDevice.IsRaw)
{
return defaultAudioDevice;
}
for (int i = 0; i < audioContext.BackendCount; i++)
for (int i = 0; i < audioContext.OutputDeviceCount; i++)
{
SoundIODevice audioDevice = audioContext.GetOutputDevice(i);
SoundIoDeviceContext audioDevice = audioContext.GetOutputDevice(i);
if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw)
{
@@ -161,22 +154,22 @@ namespace Ryujinx.Audio.Backends.SoundIo
return _sessions.TryRemove(session, out _);
}
public static SoundIOFormat GetSoundIoFormat(SampleFormat format)
public static SoundIoFormat GetSoundIoFormat(SampleFormat format)
{
return format switch
{
SampleFormat.PcmInt8 => SoundIOFormat.S8,
SampleFormat.PcmInt16 => SoundIOFormat.S16LE,
SampleFormat.PcmInt24 => SoundIOFormat.S24LE,
SampleFormat.PcmInt32 => SoundIOFormat.S32LE,
SampleFormat.PcmFloat => SoundIOFormat.Float32LE,
SampleFormat.PcmInt8 => SoundIoFormat.S8,
SampleFormat.PcmInt16 => SoundIoFormat.S16LE,
SampleFormat.PcmInt24 => SoundIoFormat.S24LE,
SampleFormat.PcmInt32 => SoundIoFormat.S32LE,
SampleFormat.PcmFloat => SoundIoFormat.Float32LE,
_ => throw new ArgumentException ($"Unsupported sample format {format}"),
};
}
internal SoundIOOutStream OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount)
internal SoundIoOutStreamContext OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount)
{
SoundIOFormat driverSampleFormat = GetSoundIoFormat(requestedSampleFormat);
SoundIoFormat driverSampleFormat = GetSoundIoFormat(requestedSampleFormat);
if (!_audioDevice.SupportsSampleRate((int)requestedSampleRate))
{
@@ -193,10 +186,10 @@ namespace Ryujinx.Audio.Backends.SoundIo
throw new ArgumentException($"This sound device does not support channel count {requestedChannelCount}");
}
SoundIOOutStream result = _audioDevice.CreateOutStream();
SoundIoOutStreamContext result = _audioDevice.CreateOutStream();
result.Name = "Ryujinx";
result.Layout = SoundIOChannelLayout.GetDefault((int)requestedChannelCount);
result.Layout = SoundIoChannelLayout.GetDefaultValue((int)requestedChannelCount);
result.Format = driverSampleFormat;
result.SampleRate = (int)requestedSampleRate;

View File

@@ -1,11 +1,12 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Backends.SoundIo.Native;
using Ryujinx.Audio.Common;
using Ryujinx.Memory;
using SoundIOSharp;
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
namespace Ryujinx.Audio.Backends.SoundIo
{
@@ -13,7 +14,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
{
private SoundIoHardwareDeviceDriver _driver;
private ConcurrentQueue<SoundIoAudioBuffer> _queuedBuffers;
private SoundIOOutStream _outputStream;
private SoundIoOutStreamContext _outputStream;
private DynamicRingBuffer _ringBuffer;
private ulong _playedSampleCount;
private ManualResetEvent _updateRequiredEvent;
@@ -106,9 +107,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
return;
}
SoundIOChannelAreas areas = _outputStream.BeginWrite(ref frameCount);
Span<SoundIoChannelArea> areas = _outputStream.BeginWrite(ref frameCount);
int channelCount = areas.ChannelCount;
int channelCount = areas.Length;
byte[] samples = new byte[frameCount * bytesPerFrame];
@@ -117,12 +118,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
// This is a huge ugly block of code, but we save
// a significant amount of time over the generic
// loop that handles other channel counts.
// TODO: Is this still right in 2021?
// TODO: Is this still right in 2022?
// Mono
if (channelCount == 1)
{
SoundIOChannelArea area = areas.GetArea(0);
ref SoundIoChannelArea area = ref areas[0];
fixed (byte* srcptr = samples)
{
@@ -167,8 +168,8 @@ namespace Ryujinx.Audio.Backends.SoundIo
// Stereo
else if (channelCount == 2)
{
SoundIOChannelArea area1 = areas.GetArea(0);
SoundIOChannelArea area2 = areas.GetArea(1);
ref SoundIoChannelArea area1 = ref areas[0];
ref SoundIoChannelArea area2 = ref areas[1];
fixed (byte* srcptr = samples)
{
@@ -233,12 +234,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
// Surround
else if (channelCount == 6)
{
SoundIOChannelArea area1 = areas.GetArea(0);
SoundIOChannelArea area2 = areas.GetArea(1);
SoundIOChannelArea area3 = areas.GetArea(2);
SoundIOChannelArea area4 = areas.GetArea(3);
SoundIOChannelArea area5 = areas.GetArea(4);
SoundIOChannelArea area6 = areas.GetArea(5);
ref SoundIoChannelArea area1 = ref areas[0];
ref SoundIoChannelArea area2 = ref areas[1];
ref SoundIoChannelArea area3 = ref areas[2];
ref SoundIoChannelArea area4 = ref areas[3];
ref SoundIoChannelArea area5 = ref areas[4];
ref SoundIoChannelArea area6 = ref areas[5];
fixed (byte* srcptr = samples)
{
@@ -367,24 +368,18 @@ namespace Ryujinx.Audio.Backends.SoundIo
// Every other channel count
else
{
SoundIOChannelArea[] channels = new SoundIOChannelArea[channelCount];
// Obtain the channel area for each channel
for (int i = 0; i < channelCount; i++)
{
channels[i] = areas.GetArea(i);
}
fixed (byte* srcptr = samples)
{
for (int frame = 0; frame < frameCount; frame++)
for (int channel = 0; channel < areas.ChannelCount; channel++)
{
for (int channel = 0; channel < areas.Length; channel++)
{
// Copy channel by channel, frame by frame. This is slow!
Unsafe.CopyBlockUnaligned((byte*)channels[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample);
Unsafe.CopyBlockUnaligned((byte*)areas[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample);
channels[channel].Pointer += channels[channel].Step;
areas[channel].Pointer += areas[channel].Step;
}
}
}
}

View File

@@ -51,6 +51,40 @@ namespace Ryujinx.Audio.Backends.CompatLayer
};
}
private SampleFormat SelectHardwareSampleFormat(SampleFormat targetSampleFormat)
{
if (_realDriver.SupportsSampleFormat(targetSampleFormat))
{
return targetSampleFormat;
}
// Attempt conversion from PCM16.
if (targetSampleFormat == SampleFormat.PcmInt16)
{
// Prefer PCM32 if we need to convert.
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt32))
{
return SampleFormat.PcmInt32;
}
// If not supported, PCM float provides the best quality with a cost lower than PCM24.
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmFloat))
{
return SampleFormat.PcmFloat;
}
// TODO: Implement PCM24 conversion.
// If nothing is truly supported, attempt PCM8 at the cost of loosing quality.
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt8))
{
return SampleFormat.PcmInt8;
}
}
throw new ArgumentException("No valid sample format configuration found!");
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
{
if (channelCount == 0)
@@ -77,15 +111,26 @@ namespace Ryujinx.Audio.Backends.CompatLayer
throw new NotImplementedException();
}
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, sampleFormat, sampleRate, hardwareChannelCount, volume);
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume);
if (hardwareChannelCount == channelCount)
if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
{
return realSession;
}
if (hardwareSampleFormat != sampleFormat)
{
Logger.Warning?.Print(LogClass.Audio, $"{sampleFormat} isn't supported by the audio device, conversion to {hardwareSampleFormat} will happen.");
if (hardwareSampleFormat < sampleFormat)
{
Logger.Warning?.Print(LogClass.Audio, $"{hardwareSampleFormat} has lower quality than {sampleFormat}, expect some loss in audio fidelity.");
}
}
if (direction == Direction.Input)
{
Logger.Warning?.Print(LogClass.Audio, $"The selected audio backend doesn't support the requested audio input configuration, fallback to dummy...");
@@ -103,7 +148,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
}
// If we need to do post processing before sending to the hardware device, wrap around it.
return new CompatLayerHardwareDeviceSession(realSessionOutputBase, channelCount);
return new CompatLayerHardwareDeviceSession(realSessionOutputBase, sampleFormat, channelCount);
}
public bool SupportsChannelCount(uint channelCount)

View File

@@ -1,5 +1,6 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Renderer.Dsp;
using System;
using System.Runtime.InteropServices;
@@ -8,11 +9,13 @@ namespace Ryujinx.Audio.Backends.CompatLayer
class CompatLayerHardwareDeviceSession : HardwareDeviceSessionOutputBase
{
private HardwareDeviceSessionOutputBase _realSession;
private SampleFormat _userSampleFormat;
private uint _userChannelCount;
public CompatLayerHardwareDeviceSession(HardwareDeviceSessionOutputBase realSession, uint userChannelCount) : base(realSession.MemoryManager, realSession.RequestedSampleFormat, realSession.RequestedSampleRate, userChannelCount)
public CompatLayerHardwareDeviceSession(HardwareDeviceSessionOutputBase realSession, SampleFormat userSampleFormat, uint userChannelCount) : base(realSession.MemoryManager, realSession.RequestedSampleFormat, realSession.RequestedSampleRate, userChannelCount)
{
_realSession = realSession;
_userSampleFormat = userSampleFormat;
_userChannelCount = userChannelCount;
}
@@ -38,53 +41,86 @@ namespace Ryujinx.Audio.Backends.CompatLayer
public override void QueueBuffer(AudioBuffer buffer)
{
SampleFormat realSampleFormat = _realSession.RequestedSampleFormat;
if (_userSampleFormat != realSampleFormat)
{
if (_userSampleFormat != SampleFormat.PcmInt16)
{
throw new NotImplementedException("Converting formats other than PCM16 is not supported.");
}
int userSampleCount = buffer.Data.Length / BackendHelper.GetSampleSize(_userSampleFormat);
ReadOnlySpan<short> samples = MemoryMarshal.Cast<byte, short>(buffer.Data);
byte[] convertedSamples = new byte[BackendHelper.GetSampleSize(realSampleFormat) * userSampleCount];
switch (realSampleFormat)
{
case SampleFormat.PcmInt8:
PcmHelper.Convert(MemoryMarshal.Cast<byte, sbyte>(convertedSamples), samples);
break;
case SampleFormat.PcmInt32:
PcmHelper.Convert(MemoryMarshal.Cast<byte, int>(convertedSamples), samples);
break;
case SampleFormat.PcmFloat:
PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast<byte, float>(convertedSamples), samples);
break;
default:
throw new NotImplementedException($"Sample format conversion from {_userSampleFormat} to {realSampleFormat} not implemented.");
}
buffer.Data = convertedSamples;
}
_realSession.QueueBuffer(buffer);
}
public override bool RegisterBuffer(AudioBuffer buffer, byte[] samples)
{
if (RequestedSampleFormat != SampleFormat.PcmInt16)
{
throw new NotImplementedException("Downmixing formats other than PCM16 is not supported.");
}
if (samples == null)
{
return false;
}
short[] downmixedBufferPCM16;
ReadOnlySpan<short> samplesPCM16 = MemoryMarshal.Cast<byte, short>(samples);
if (_userChannelCount == 6)
if (_userChannelCount != _realSession.RequestedChannelCount)
{
downmixedBufferPCM16 = Downmixing.DownMixSurroundToStereo(samplesPCM16);
if (_realSession.RequestedChannelCount == 1)
if (_userSampleFormat != SampleFormat.PcmInt16)
{
downmixedBufferPCM16 = Downmixing.DownMixStereoToMono(downmixedBufferPCM16);
throw new NotImplementedException("Downmixing formats other than PCM16 is not supported.");
}
}
else if (_userChannelCount == 2 && _realSession.RequestedChannelCount == 1)
{
downmixedBufferPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16);
}
else
{
throw new NotImplementedException($"Downmixing from {_userChannelCount} to {_realSession.RequestedChannelCount} not implemented.");
}
byte[] downmixedBuffer = MemoryMarshal.Cast<short, byte>(downmixedBufferPCM16).ToArray();
ReadOnlySpan<short> samplesPCM16 = MemoryMarshal.Cast<byte, short>(samples);
if (_userChannelCount == 6)
{
samplesPCM16 = Downmixing.DownMixSurroundToStereo(samplesPCM16);
if (_realSession.RequestedChannelCount == 1)
{
samplesPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16);
}
}
else if (_userChannelCount == 2 && _realSession.RequestedChannelCount == 1)
{
samplesPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16);
}
else
{
throw new NotImplementedException($"Downmixing from {_userChannelCount} to {_realSession.RequestedChannelCount} not implemented.");
}
samples = MemoryMarshal.Cast<short, byte>(samplesPCM16).ToArray();
}
AudioBuffer fakeBuffer = new AudioBuffer
{
BufferTag = buffer.BufferTag,
DataPointer = buffer.DataPointer,
DataSize = (ulong)downmixedBuffer.Length
DataSize = (ulong)samples.Length
};
bool result = _realSession.RegisterBuffer(fakeBuffer, downmixedBuffer);
bool result = _realSession.RegisterBuffer(fakeBuffer, samples);
if (result)
{

View File

@@ -48,6 +48,11 @@ namespace Ryujinx.Audio.Renderer.Common
/// <summary>
/// Effect to capture mixes (via auxiliary buffers).
/// </summary>
CaptureBuffer
CaptureBuffer,
/// <summary>
/// Effect applying a compressor filter (DRC).
/// </summary>
Compressor,
}
}

View File

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

View File

@@ -23,7 +23,7 @@ namespace Ryujinx.Audio.Renderer.Common
if (size != 0)
{
ulong alignedOffset = BitUtils.AlignUp(Offset, align);
ulong alignedOffset = BitUtils.AlignUp<ulong>(Offset, (ulong)align);
if (alignedOffset + size <= (ulong)BackingMemory.Length)
{
@@ -55,7 +55,7 @@ namespace Ryujinx.Audio.Renderer.Common
public static ulong GetTargetSize<T>(ulong currentSize, ulong count, int align) where T : unmanaged
{
return BitUtils.AlignUp(currentSize, align) + (ulong)Unsafe.SizeOf<T>() * count;
return BitUtils.AlignUp<ulong>(currentSize, (ulong)align) + (ulong)Unsafe.SizeOf<T>() * count;
}
}
}

View File

@@ -31,6 +31,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
LimiterVersion1,
LimiterVersion2,
GroupedBiquadFilter,
CaptureBuffer
CaptureBuffer,
Compressor
}
}

View File

@@ -0,0 +1,173 @@
using System;
using System.Diagnostics;
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
public class CompressorCommand : ICommand
{
private const int FixedPointPrecision = 15;
public bool Enabled { get; set; }
public int NodeId { get; }
public CommandType CommandType => CommandType.Compressor;
public uint EstimatedProcessingTime { get; set; }
public CompressorParameter Parameter => _parameter;
public Memory<CompressorState> State { get; }
public ushort[] OutputBufferIndices { get; }
public ushort[] InputBufferIndices { get; }
public bool IsEffectEnabled { get; }
private CompressorParameter _parameter;
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
{
Enabled = true;
NodeId = nodeId;
_parameter = parameter;
State = state;
IsEffectEnabled = isEnabled;
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
for (int i = 0; i < _parameter.ChannelCount; i++)
{
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
}
}
public void Process(CommandList context)
{
ref CompressorState state = ref State.Span[0];
if (IsEffectEnabled)
{
if (_parameter.Status == Server.Effect.UsageState.Invalid)
{
state = new CompressorState(ref _parameter);
}
else if (_parameter.Status == Server.Effect.UsageState.New)
{
state.UpdateParameter(ref _parameter);
}
}
ProcessCompressor(context, ref state);
}
private unsafe void ProcessCompressor(CommandList context, ref CompressorState state)
{
Debug.Assert(_parameter.IsChannelCountValid());
if (IsEffectEnabled && _parameter.IsChannelCountValid())
{
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<float> channelInput = stackalloc float[Parameter.ChannelCount];
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
float unknown4 = state.Unknown4;
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
float previousCompressionEmaAlpha = state.PreviousCompressionEmaAlpha;
for (int i = 0; i < _parameter.ChannelCount; i++)
{
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
}
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
}
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
float z = 0.0f;
bool unknown10OutOfRange = false;
if (newMean < 1.0e-10f)
{
z = 1.0f;
unknown10OutOfRange = state.Unknown10 < -100.0f;
}
if (y >= state.Unknown10 || unknown10OutOfRange)
{
float tmpGain;
if (y >= state.Unknown14)
{
tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
}
else
{
tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction);
}
z = FloatingPointHelper.DecibelToLinearExtended(tmpGain);
}
float unknown4New = z;
float compressionEmaAlpha;
if ((unknown4 - z) <= 0.08f)
{
compressionEmaAlpha = Parameter.ReleaseCoefficient;
if ((unknown4 - z) >= -0.08f)
{
if (MathF.Abs(compressionGainAverage.Read() - z) >= 0.001f)
{
unknown4New = unknown4;
}
compressionEmaAlpha = previousCompressionEmaAlpha;
}
}
else
{
compressionEmaAlpha = Parameter.AttackCoefficient;
}
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
{
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
}
unknown4 = unknown4New;
previousCompressionEmaAlpha = compressionEmaAlpha;
}
state.InputMovingAverage = inputMovingAverage;
state.Unknown4 = unknown4;
state.CompressionGainAverage = compressionGainAverage;
state.PreviousCompressionEmaAlpha = previousCompressionEmaAlpha;
}
else
{
for (int i = 0; i < Parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
}
}
}
}
}
}

View File

@@ -90,32 +90,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float inputCoefficient = Parameter.ReleaseCoefficient;
if (sampleInputMax > state.DectectorAverage[channelIndex])
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
inputCoefficient = Parameter.AttackCoefficient;
}
state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;
if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
if (detectorValue > Parameter.Threshold)
{
attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
attenuation = Parameter.Threshold / detectorValue;
}
float outputCoefficient = Parameter.ReleaseCoefficient;
if (state.CompressionGain[channelIndex] > attenuation)
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
}
state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;

View File

@@ -101,32 +101,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float inputCoefficient = Parameter.ReleaseCoefficient;
if (sampleInputMax > state.DectectorAverage[channelIndex])
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
inputCoefficient = Parameter.AttackCoefficient;
}
state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;
if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
if (detectorValue > Parameter.Threshold)
{
attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
attenuation = Parameter.Threshold / detectorValue;
}
float outputCoefficient = Parameter.ReleaseCoefficient;
if (state.CompressionGain[channelIndex] > attenuation)
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
}
state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
@@ -144,7 +143,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax);
statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], state.CompressionGain[channelIndex]);
statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], compressionGain);
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp.Effect
{
public struct ExponentialMovingAverage
{
private float _mean;
public ExponentialMovingAverage(float mean)
{
_mean = mean;
}
public float Read()
{
return _mean;
}
public float Update(float value, float alpha)
{
_mean += alpha * (value - _mean);
return _mean;
}
}
}

View File

@@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Renderer.Dsp
return (float)value / (1 << qBits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ConvertFloat(float value, int qBits)
{
return value / (1 << qBits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ToFixed(float value, int qBits)
{

View File

@@ -1,4 +1,5 @@
using System;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp
@@ -46,6 +47,53 @@ namespace Ryujinx.Audio.Renderer.Dsp
return MathF.Pow(10, x);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Log10(float x)
{
// NOTE: Nintendo uses an approximation of log10, we don't.
// As such, we support the same ranges as Nintendo to avoid unexpected behaviours.
return MathF.Pow(10, MathF.Max(x, 1.0e-10f));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float MeanSquare(ReadOnlySpan<float> inputs)
{
float res = 0.0f;
foreach (float input in inputs)
{
res += (input * input);
}
res /= inputs.Length;
return res;
}
/// <summary>
/// Map decibel to linear.
/// </summary>
/// <param name="db">The decibel value to convert</param>
/// <returns>Converted linear value/returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DecibelToLinear(float db)
{
return MathF.Pow(10.0f, db / 20.0f);
}
/// <summary>
/// Map decibel to linear in [0, 2] range.
/// </summary>
/// <param name="db">The decibel value to convert</param>
/// <returns>Converted linear value in [0, 2] range</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DecibelToLinearExtended(float db)
{
float tmp = MathF.Log2(DecibelToLinear(db));
return MathF.Truncate(tmp) + MathF.Pow(2.0f, tmp - MathF.Truncate(tmp));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{

View File

@@ -1,4 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp
@@ -23,6 +24,44 @@ namespace Ryujinx.Audio.Renderer.Dsp
return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf<T>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ConvertSampleToPcmFloat(short sample)
{
return (float)sample / short.MaxValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short ConvertSampleToPcmInt16(float sample)
{
return Saturate(sample * short.MaxValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TOutput ConvertSample<TInput, TOutput>(TInput value) where TInput: INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput>
{
TInput conversionRate = TInput.CreateSaturating(TOutput.MaxValue / TOutput.CreateSaturating(TInput.MaxValue));
return TOutput.CreateSaturating(value * conversionRate);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Convert<TInput, TOutput>(Span<TOutput> output, ReadOnlySpan<TInput> input) where TInput : INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput>
{
for (int i = 0; i < input.Length; i++)
{
output[i] = ConvertSample<TInput, TOutput>(input[i]);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ConvertSampleToPcmFloat(Span<float> output, ReadOnlySpan<short> input)
{
for (int i = 0; i < input.Length; i++)
{
output[i] = ConvertSampleToPcmFloat(input[i]);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Decode(Span<short> output, ReadOnlySpan<short> input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount)
{
@@ -53,7 +92,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
for (int i = 0; i < decodedCount; i++)
{
output[i] = (short)(input[i * channelCount + channelIndex] * short.MaxValue);
output[i] = ConvertSampleToPcmInt16(input[i * channelCount + channelIndex]);
}
return decodedCount;

View File

@@ -0,0 +1,51 @@
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Parameter.Effect;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class CompressorState
{
public ExponentialMovingAverage InputMovingAverage;
public float Unknown4;
public ExponentialMovingAverage CompressionGainAverage;
public float CompressorGainReduction;
public float Unknown10;
public float Unknown14;
public float PreviousCompressionEmaAlpha;
public float MakeupGain;
public float OutputGain;
public CompressorState(ref CompressorParameter parameter)
{
InputMovingAverage = new ExponentialMovingAverage(0.0f);
Unknown4 = 1.0f;
CompressionGainAverage = new ExponentialMovingAverage(1.0f);
UpdateParameter(ref parameter);
}
public void UpdateParameter(ref CompressorParameter parameter)
{
float threshold = parameter.Threshold;
float ratio = 1.0f / parameter.Ratio;
float attackCoefficient = parameter.AttackCoefficient;
float makeupGain;
if (parameter.MakeupGainEnabled)
{
makeupGain = (threshold * 0.5f * (ratio - 1.0f)) - 3.0f;
}
else
{
makeupGain = 0.0f;
}
PreviousCompressionEmaAlpha = attackCoefficient;
MakeupGain = makeupGain;
CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax;
Unknown10 = threshold - 1.5f;
Unknown14 = threshold + 1.5f;
OutputGain = FloatingPointHelper.DecibelToLinearExtended(parameter.OutputGain + makeupGain);
}
}
}

View File

@@ -1,3 +1,4 @@
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
@@ -5,20 +6,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class LimiterState
{
public float[] DectectorAverage;
public float[] CompressionGain;
public ExponentialMovingAverage[] DetectorAverage;
public ExponentialMovingAverage[] CompressionGainAverage;
public float[] DelayedSampleBuffer;
public int[] DelayedSampleBufferPosition;
public LimiterState(ref LimiterParameter parameter, ulong workBuffer)
{
DectectorAverage = new float[parameter.ChannelCount];
CompressionGain = new float[parameter.ChannelCount];
DetectorAverage = new ExponentialMovingAverage[parameter.ChannelCount];
CompressionGainAverage = new ExponentialMovingAverage[parameter.ChannelCount];
DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax];
DelayedSampleBufferPosition = new int[parameter.ChannelCount];
DectectorAverage.AsSpan().Fill(0.0f);
CompressionGain.AsSpan().Fill(1.0f);
DetectorAverage.AsSpan().Fill(new ExponentialMovingAverage(0.0f));
CompressionGainAverage.AsSpan().Fill(new ExponentialMovingAverage(1.0f));
DelayedSampleBufferPosition.AsSpan().Fill(0);
DelayedSampleBuffer.AsSpan().Fill(0.0f);

View File

@@ -0,0 +1,115 @@
using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Compressor"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CompressorParameter
{
/// <summary>
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Input;
/// <summary>
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Output;
/// <summary>
/// The maximum number of channels supported.
/// </summary>
public ushort ChannelCountMax;
/// <summary>
/// The total channel count used.
/// </summary>
public ushort ChannelCount;
/// <summary>
/// The target sample rate.
/// </summary>
/// <remarks>This is in kHz.</remarks>
public int SampleRate;
/// <summary>
/// The threshold.
/// </summary>
public float Threshold;
/// <summary>
/// The compressor ratio.
/// </summary>
public float Ratio;
/// <summary>
/// The attack time.
/// <remarks>This is in microseconds.</remarks>
/// </summary>
public int AttackTime;
/// <summary>
/// The release time.
/// <remarks>This is in microseconds.</remarks>
/// </summary>
public int ReleaseTime;
/// <summary>
/// The input gain.
/// </summary>
public float InputGain;
/// <summary>
/// The attack coefficient.
/// </summary>
public float AttackCoefficient;
/// <summary>
/// The release coefficient.
/// </summary>
public float ReleaseCoefficient;
/// <summary>
/// The output gain.
/// </summary>
public float OutputGain;
/// <summary>
/// The current usage status of the effect on the client side.
/// </summary>
public UsageState Status;
/// <summary>
/// Indicate if the makeup gain should be used.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool MakeupGainEnabled;
/// <summary>
/// Reserved/padding.
/// </summary>
private Array2<byte> _reserved;
/// <summary>
/// Check if the <see cref="ChannelCount"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
public bool IsChannelCountValid()
{
return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
}
/// <summary>
/// Check if the <see cref="ChannelCountMax"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
public bool IsChannelCountMaxValid()
{
return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
}
}
}

View File

@@ -167,7 +167,7 @@ namespace Ryujinx.Audio.Renderer.Server
return ResultCode.WorkBufferTooSmall;
}
_depopBuffer = workBufferAllocator.Allocate<float>((ulong)BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment);
_depopBuffer = workBufferAllocator.Allocate<float>(BitUtils.AlignUp<ulong>(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment);
if (_depopBuffer.IsEmpty)
{
@@ -772,7 +772,7 @@ namespace Ryujinx.Audio.Renderer.Server
size = WorkBufferAllocator.GetTargetSize<float>(size, Constants.TargetSampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount) * (parameter.SinkCount + parameter.SubMixBufferCount), 0x10);
// Depop buffer
size = WorkBufferAllocator.GetTargetSize<float>(size, (ulong)BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment);
size = WorkBufferAllocator.GetTargetSize<float>(size, BitUtils.AlignUp<ulong>(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment);
// Voice
size = WorkBufferAllocator.GetTargetSize<VoiceState>(size, parameter.VoiceCount, VoiceState.Alignment);
@@ -804,10 +804,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
ulong performanceMetricsPerFramesSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC;
size += BitUtils.AlignUp(performanceMetricsPerFramesSize, Constants.PerformanceMetricsPerFramesSizeAlignment);
size += BitUtils.AlignUp<ulong>(performanceMetricsPerFramesSize, Constants.PerformanceMetricsPerFramesSizeAlignment);
}
return BitUtils.AlignUp(size, Constants.WorkBufferAlignment);
return BitUtils.AlignUp<ulong>(size, Constants.WorkBufferAlignment);
}
public ResultCode QuerySystemEvent(out IWritableEvent systemEvent)

View File

@@ -44,7 +44,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP.
/// A new version of the command estimator was added to address timing changes caused by the voice changes.
/// Additionally, the rendering limit percent was incremented to 80%.
///
///
/// </summary>
/// <remarks>This was added in system update 6.0.0</remarks>
public const int Revision5 = 5 << 24;
@@ -93,6 +93,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <summary>
/// REV11:
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
/// A new effect was added: Compressor. This effect is effectively implemented with a DRC.
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
/// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands.
/// </summary>

View File

@@ -469,6 +469,18 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
{
if (parameter.IsChannelCountValid())
{
CompressorCommand command = new CompressorCommand(bufferOffset, parameter, state, isEnabled, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
AddCommand(command);
}
}
/// <summary>
/// Generate a new <see cref="VolumeCommand"/>.
/// </summary>

View File

@@ -606,6 +606,17 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId)
{
Debug.Assert(effect.Type == EffectType.Compressor);
_commandBuffer.GenerateCompressorEffect(bufferOffset,
effect.Parameter,
effect.State,
effect.IsEnabled,
nodeId);
}
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
{
int nodeId = mix.NodeId;
@@ -650,6 +661,9 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.CaptureBuffer:
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
break;
case EffectType.Compressor:
GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId);
break;
default:
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
}

View File

@@ -179,5 +179,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public uint Estimate(CompressorCommand command)
{
return 0;
}
}
}

View File

@@ -543,5 +543,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public uint Estimate(CompressorCommand command)
{
return 0;
}
}
}

View File

@@ -747,5 +747,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public virtual uint Estimate(CompressorCommand command)
{
return 0;
}
}
}

View File

@@ -232,5 +232,79 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
}
public override uint Estimate(CompressorCommand command)
{
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
if (_sampleCount == 160)
{
if (command.Enabled)
{
switch (command.Parameter.ChannelCount)
{
case 1:
return 34431;
case 2:
return 44253;
case 4:
return 63827;
case 6:
return 83361;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
else
{
switch (command.Parameter.ChannelCount)
{
case 1:
return (uint)630.12f;
case 2:
return (uint)638.27f;
case 4:
return (uint)705.86f;
case 6:
return (uint)782.02f;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
}
if (command.Enabled)
{
switch (command.Parameter.ChannelCount)
{
case 1:
return 51095;
case 2:
return 65693;
case 4:
return 95383;
case 6:
return 124510;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
else
{
switch (command.Parameter.ChannelCount)
{
case 1:
return (uint)840.14f;
case 2:
return (uint)826.1f;
case 4:
return (uint)901.88f;
case 6:
return (uint)965.29f;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
}
}
}

View File

@@ -262,6 +262,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return PerformanceDetailType.Limiter;
case EffectType.CaptureBuffer:
return PerformanceDetailType.CaptureBuffer;
case EffectType.Compressor:
return PerformanceDetailType.Compressor;
default:
throw new NotImplementedException($"{Type}");
}

View File

@@ -0,0 +1,67 @@
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server.MemoryPool;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Server.Effect
{
/// <summary>
/// Server state for a compressor effect.
/// </summary>
public class CompressorEffect : BaseEffect
{
/// <summary>
/// The compressor parameter.
/// </summary>
public CompressorParameter Parameter;
/// <summary>
/// The compressor state.
/// </summary>
public Memory<CompressorState> State { get; }
/// <summary>
/// Create a new <see cref="CompressorEffect"/>.
/// </summary>
public CompressorEffect()
{
State = new CompressorState[1];
}
public override EffectType TargetEffectType => EffectType.Compressor;
public override ulong GetWorkBuffer(int index)
{
return GetSingleBuffer();
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
// Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised.
updateErrorInfo = new BehaviourParameter.ErrorInfo();
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Debug.Assert(IsTypeValid(ref parameter));
UpdateParameterBase(ref parameter);
Parameter = MemoryMarshal.Cast<byte, CompressorParameter>(parameter.SpecificData)[0];
IsEnabled = parameter.IsEnabled;
updateErrorInfo = new BehaviourParameter.ErrorInfo();
}
public override void UpdateForCommandGeneration()
{
UpdateUsageStateForCommandGeneration();
Parameter.Status = UsageState.Enabled;
}
}
}

View File

@@ -35,5 +35,6 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(LimiterCommandVersion2 command);
uint Estimate(GroupedBiquadFilterCommand command);
uint Estimate(CaptureBufferCommand command);
uint Estimate(CompressorCommand command);
}
}

View File

@@ -240,6 +240,10 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.CaptureBuffer:
effect = new CaptureBufferEffect();
break;
case EffectType.Compressor:
effect = new CompressorEffect();
break;
default:
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
}

View File

@@ -4,6 +4,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sty="using:FluentAvalonia.Styling">
<Application.Styles>
<sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False" />
<sty:FluentAvaloniaTheme PreferSystemTheme="False" />
</Application.Styles>
</Application>

View File

@@ -5,8 +5,9 @@ using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.Styling;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Ui.Common.Configuration;
@@ -58,11 +59,11 @@ namespace Ryujinx.Ava
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance["DialogThemeRestartMessage"],
LocaleManager.Instance["DialogThemeRestartSubMessage"],
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["DialogRestartRequiredMessage"]);
LocaleManager.Instance[LocaleKeys.DialogThemeRestartMessage],
LocaleManager.Instance[LocaleKeys.DialogThemeRestartSubMessage],
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.DialogRestartRequiredMessage]);
if (result == UserResult.Yes)
{

View File

@@ -11,13 +11,14 @@ using Ryujinx.Audio.Integration;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.System;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu;
@@ -125,7 +126,7 @@ namespace Ryujinx.Ava
_inputManager = inputManager;
_accountManager = accountManager;
_userChannelPersistence = userChannelPersistence;
_renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" };
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
_lastCursorMoveTime = Stopwatch.GetTimestamp();
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
@@ -431,10 +432,10 @@ namespace Ryujinx.Ava
if (userError == UserError.NoFirmware)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"],
string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"], firmwareVersion.VersionString),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage],
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedMessage], firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
"");
if (result != UserResult.Yes)
@@ -462,11 +463,11 @@ namespace Ryujinx.Ava
_parent.RefreshFirmwareStatus();
await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString),
string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString),
LocaleManager.Instance["InputDialogOk"],
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstalledMessage], firmwareVersion.VersionString),
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage], firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogOk],
"",
LocaleManager.Instance["RyujinxInfo"]);
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
}
}
else
@@ -868,7 +869,7 @@ namespace Ryujinx.Ava
public void UpdateStatus()
{
// Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance["Docked"] : LocaleManager.Instance["Handheld"];
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
float scale = GraphicsConfig.ResScale;
if (scale != 1)
@@ -878,11 +879,11 @@ namespace Ryujinx.Ava
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
Device.EnableDeviceVsync,
LocaleManager.Instance["VolumeShort"] + $": {(int)(Device.GetVolume() * 100)}%",
LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%",
Renderer.IsVulkan ? "Vulkan" : "OpenGL",
dockedMode,
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
LocaleManager.Instance["Game"] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
$"GPU: {_renderer.GetHardwareInfo().GpuVendor}"));
}

View File

@@ -280,7 +280,7 @@
"ControllerSettingsRemoveProfileToolTip": "Remove Profile",
"ControllerSettingsSaveProfileToolTip": "Save Profile",
"MenuBarFileToolsTakeScreenshot": "Take Screenshot",
"MenuBarFileToolsHideUi": "Hide Ui",
"MenuBarFileToolsHideUi": "Hide UI",
"GameListContextMenuToggleFavorite": "Toggle Favorite",
"GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game",
"SettingsTabGeneralTheme": "Theme",
@@ -315,7 +315,7 @@
"DialogUpdaterConvertFailedMessage": "Failed to convert the current Ryujinx version.",
"DialogUpdaterCancelUpdateMessage": "Cancelling Update!",
"DialogUpdaterAlreadyOnLatestVersionMessage": "You are already using the most updated version of Ryujinx!",
"DialogUpdaterFailedToGetVersionMessage": "An error has occurred when trying to get release information from Github Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.",
"DialogUpdaterFailedToGetVersionMessage": "An error has occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.",
"DialogUpdaterConvertFailedGithubMessage": "Failed to convert the received Ryujinx version from Github Release.",
"DialogUpdaterDownloadingMessage": "Downloading Update...",
"DialogUpdaterExtractionMessage": "Extracting Update...",

View File

@@ -161,6 +161,7 @@
<Style Selector="MenuItem">
<Setter Property="Height" Value="{DynamicResource MenuItemHeight}" />
<Setter Property="Padding" Value="{DynamicResource MenuItemPadding}" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style Selector="MenuItem:selected /template/ Border#root">
<Setter Property="Background" Value="{DynamicResource ThemeControlBorderColor}" />

View File

@@ -12,8 +12,9 @@ using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
@@ -80,7 +81,7 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(
string.Format(LocaleManager.Instance["DialogMessageCreateSaveErrorMessage"], result.ToStringWithName()));
string.Format(LocaleManager.Instance[LocaleKeys.DialogMessageCreateSaveErrorMessage], result.ToStringWithName()));
});
return false;
@@ -99,7 +100,7 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogMessageFindSaveErrorMessage"], result.ToStringWithName()));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogMessageFindSaveErrorMessage], result.ToStringWithName()));
});
return false;
@@ -150,7 +151,7 @@ namespace Ryujinx.Ava.Common
public static async Task ExtractSection(NcaSectionType ncaSectionType, string titleFilePath,
int programIndex = 0)
{
OpenFolderDialog folderDialog = new() { Title = LocaleManager.Instance["FolderDialogExtractTitle"] };
OpenFolderDialog folderDialog = new() { Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle] };
string destination = await folderDialog.ShowAsync(_owner);
@@ -163,11 +164,11 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.Post(async () =>
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
string.Format(LocaleManager.Instance["DialogNcaExtractionMessage"], ncaSectionType, Path.GetFileName(titleFilePath)),
string.Format(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMessage], ncaSectionType, Path.GetFileName(titleFilePath)),
"",
"",
LocaleManager.Instance["InputDialogCancel"],
LocaleManager.Instance["DialogNcaExtractionTitle"]);
LocaleManager.Instance[LocaleKeys.InputDialogCancel],
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle]);
if (result == UserResult.Cancel)
{
@@ -233,7 +234,7 @@ namespace Ryujinx.Ava.Common
"Extraction failure. The main NCA was not present in the selected file");
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogNcaExtractionMainNcaNotFoundErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
});
return;
}
@@ -274,7 +275,7 @@ namespace Ryujinx.Ava.Common
$"LibHac returned error code: {resultCode.Value.ErrorCode}");
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogNcaExtractionCheckLogErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
});
}
else if (resultCode.Value.IsSuccess())
@@ -282,11 +283,11 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance["DialogNcaExtractionSuccessMessage"],
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage],
"",
LocaleManager.Instance["InputDialogOk"],
LocaleManager.Instance[LocaleKeys.InputDialogOk],
"",
LocaleManager.Instance["DialogNcaExtractionTitle"]);
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle]);
});
}
}

View File

@@ -7,16 +7,16 @@ namespace Ryujinx.Ava.Common.Locale
{
internal class LocaleExtension : MarkupExtension
{
public LocaleExtension(string key)
public LocaleExtension(LocaleKeys key)
{
Key = key;
}
public string Key { get; }
public LocaleKeys Key { get; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
string keyToUse = Key;
LocaleKeys keyToUse = Key;
ReflectionBindingExtension binding = new($"[{keyToUse}]")
{

View File

@@ -1,7 +1,8 @@
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
@@ -13,17 +14,17 @@ namespace Ryujinx.Ava.Common.Locale
{
private const string DefaultLanguageCode = "en_US";
private Dictionary<string, string> _localeStrings;
private ConcurrentDictionary<string, object[]> _dynamicValues;
private Dictionary<LocaleKeys, string> _localeStrings;
private ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
public static LocaleManager Instance { get; } = new LocaleManager();
public Dictionary<string, string> LocaleStrings { get => _localeStrings; set => _localeStrings = value; }
public Dictionary<LocaleKeys, string> LocaleStrings { get => _localeStrings; set => _localeStrings = value; }
public LocaleManager()
{
_localeStrings = new Dictionary<string, string>();
_dynamicValues = new ConcurrentDictionary<string, object[]>();
_localeStrings = new Dictionary<LocaleKeys, string>();
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
Load();
}
@@ -49,7 +50,7 @@ namespace Ryujinx.Ava.Common.Locale
}
}
public string this[string key]
public string this[LocaleKeys key]
{
get
{
@@ -63,7 +64,7 @@ namespace Ryujinx.Ava.Common.Locale
return value;
}
return key;
return key.ToString();
}
set
{
@@ -73,7 +74,7 @@ namespace Ryujinx.Ava.Common.Locale
}
}
public void UpdateDynamicValue(string key, params object[] values)
public void UpdateDynamicValue(LocaleKeys key, params object[] values)
{
_dynamicValues[key] = values;
@@ -98,7 +99,10 @@ namespace Ryujinx.Ava.Common.Locale
foreach (var item in strings)
{
this[item.Key] = item.Value;
if (Enum.TryParse<LocaleKeys>(item.Key, out var key))
{
this[key] = item.Value;
}
}
if (Program.PreviewerDetached)

View File

@@ -0,0 +1,127 @@
using System;
using System.Runtime.Versioning;
using System.Runtime.InteropServices;
using Avalonia;
namespace Ryujinx.Ava.UI.Helper
{
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
[SupportedOSPlatform("macos")]
static partial class MetalHelper
{
private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
private struct Selector
{
public readonly IntPtr NativePtr;
public unsafe Selector(string value)
{
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
byte* data = stackalloc byte[size];
fixed (char* pValue = value)
{
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
}
NativePtr = sel_registerName(data);
}
public static implicit operator Selector(string value) => new Selector(value);
}
private static unsafe IntPtr GetClass(string value)
{
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
byte* data = stackalloc byte[size];
fixed (char* pValue = value)
{
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
}
return objc_getClass(data);
}
private struct NSPoint
{
public double X;
public double Y;
public NSPoint(double x, double y)
{
X = x;
Y = y;
}
}
private struct NSRect
{
public NSPoint Pos;
public NSPoint Size;
public NSRect(double x, double y, double width, double height)
{
Pos = new NSPoint(x, y);
Size = new NSPoint(width, height);
}
}
public static IntPtr GetMetalLayer(out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
{
// Create a new CAMetalLayer.
IntPtr layerClass = GetClass("CAMetalLayer");
IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
objc_msgSend(metalLayer, "init");
// Create a child NSView to render into.
IntPtr nsViewClass = GetClass("NSView");
IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
objc_msgSend(child, "init", new NSRect(0, 0, 0, 0));
// Make its renderer our metal layer.
objc_msgSend(child, "setWantsLayer:", (byte)1);
objc_msgSend(child, "setLayer:", metalLayer);
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
// Ensure the scale factor is up to date.
updateBounds = (Rect rect) => {
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
};
nsView = child;
return metalLayer;
}
public static void DestroyMetalLayer(IntPtr nsView, IntPtr metalLayer)
{
// TODO
}
[LibraryImport(LibObjCImport)]
private static unsafe partial IntPtr sel_registerName(byte* data);
[LibraryImport(LibObjCImport)]
private static unsafe partial IntPtr objc_getClass(byte* data);
[LibraryImport(LibObjCImport)]
private static partial void objc_msgSend(IntPtr receiver, Selector selector);
[LibraryImport(LibObjCImport)]
private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
[LibraryImport(LibObjCImport)]
private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
[LibraryImport(LibObjCImport)]
private static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
[LibraryImport(LibObjCImport)]
private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
[LibraryImport(LibObjCImport, EntryPoint = "objc_msgSend")]
private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
}
}

View File

@@ -4,7 +4,6 @@ using Ryujinx.Input;
using System;
using System.Collections.Generic;
using System.Numerics;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
using Key = Ryujinx.Input.Key;
@@ -13,30 +12,37 @@ namespace Ryujinx.Ava.Input
internal class AvaloniaKeyboard : IKeyboard
{
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private readonly AvaloniaKeyboardDriver _driver;
private readonly AvaloniaKeyboardDriver _driver;
private StandardKeyboardInputConfig _configuration;
private readonly object _userMappingLock = new();
private StandardKeyboardInputConfig _configuration;
private bool HasConfiguration => _configuration != null;
public string Id { get; }
public string Id { get; }
public string Name { get; }
public bool IsConnected => true;
public bool IsConnected => true;
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
private class ButtonMappingEntry
{
public readonly Key From;
public readonly GamepadButtonInputId To;
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
To = to;
From = from;
}
}
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
{
_driver = driver;
Id = id;
Name = name;
_buttonsUserMapping = new List<ButtonMappingEntry>();
}
public void Dispose() { }
_driver = driver;
Id = id;
Name = name;
}
public KeyboardStateSnapshot GetKeyboardStateSnapshot()
{
@@ -46,11 +52,11 @@ namespace Ryujinx.Ava.Input
public GamepadStateSnapshot GetMappedStateSnapshot()
{
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
GamepadStateSnapshot result = default;
GamepadStateSnapshot result = default;
lock (_userMappingLock)
{
if (!HasConfiguration)
if (_configuration == null)
{
return result;
}
@@ -62,17 +68,17 @@ namespace Ryujinx.Ava.Input
continue;
}
// Do not touch state of the button already pressed
// NOTE: Do not touch state of the button already pressed.
if (!result.IsPressed(entry.To))
{
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
}
}
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
}
@@ -114,29 +120,29 @@ namespace Ryujinx.Ava.Input
_buttonsUserMapping.Clear();
// Left joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
// Left JoyCon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
// Finally right joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
// Right JoyCon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
}
}
@@ -190,16 +196,6 @@ namespace Ryujinx.Ava.Input
_driver?.ResetKeys();
}
private class ButtonMappingEntry
{
public readonly Key From;
public readonly GamepadButtonInputId To;
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
To = to;
From = from;
}
}
public void Dispose() { }
}
}

View File

@@ -4,7 +4,6 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Input;
using System;
using System.Collections.Generic;
using AvaKey = Avalonia.Input.Key;
using Key = Ryujinx.Input.Key;
@@ -13,24 +12,23 @@ namespace Ryujinx.Ava.Input
internal class AvaloniaKeyboardDriver : IGamepadDriver
{
private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
private readonly Control _control;
private readonly Control _control;
private readonly HashSet<AvaKey> _pressedKeys;
public event EventHandler<KeyEventArgs> KeyPressed;
public event EventHandler<KeyEventArgs> KeyRelease;
public event EventHandler<string> TextInput;
public string DriverName => "Avalonia";
public event EventHandler<string> TextInput;
public string DriverName => "AvaloniaKeyboardDriver";
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
public AvaloniaKeyboardDriver(Control control)
{
_control = control;
_control = control;
_pressedKeys = new HashSet<AvaKey>();
_control.KeyDown += OnKeyPress;
_control.KeyUp += OnKeyRelease;
_control.KeyDown += OnKeyPress;
_control.KeyUp += OnKeyRelease;
_control.TextInput += Control_TextInput;
}
@@ -41,21 +39,16 @@ namespace Ryujinx.Ava.Input
public event Action<string> OnGamepadConnected
{
add { }
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
add { }
remove { }
}
public void Dispose()
{
Dispose(true);
}
public IGamepad GetGamepad(string id)
{
if (!_keyboardIdentifers[0].Equals(id))
@@ -63,22 +56,20 @@ namespace Ryujinx.Ava.Input
return null;
}
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance["AllKeyboards"]);
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_control.KeyUp -= OnKeyPress;
_control.KeyUp -= OnKeyPress;
_control.KeyDown -= OnKeyRelease;
}
}
protected void OnKeyPress(object sender, KeyEventArgs args)
{
AvaKey key = args.Key;
_pressedKeys.Add(args.Key);
KeyPressed?.Invoke(this, args);
@@ -98,7 +89,7 @@ namespace Ryujinx.Ava.Input
return false;
}
AvaloniaMappingHelper.TryGetAvaKey(key, out var nativeKey);
AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out var nativeKey);
return _pressedKeys.Contains(nativeKey);
}
@@ -107,5 +98,10 @@ namespace Ryujinx.Ava.Input
{
_pressedKeys.Clear();
}
public void Dispose()
{
Dispose(true);
}
}
}

View File

@@ -5,10 +5,9 @@ using AvaKey = Avalonia.Input.Key;
namespace Ryujinx.Ava.Input
{
internal static class AvaloniaMappingHelper
internal static class AvaloniaKeyboardMappingHelper
{
private static readonly AvaKey[] _keyMapping = new AvaKey[(int)Key.Count]
{
private static readonly AvaKey[] _keyMapping = {
// NOTE: Invalid
AvaKey.None,
@@ -149,33 +148,31 @@ namespace Ryujinx.Ava.Input
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
static AvaloniaMappingHelper()
static AvaloniaKeyboardMappingHelper()
{
var inputKeys = Enum.GetValues(typeof(Key));
var inputKeys = Enum.GetValues<Key>();
// Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
// NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
_avaKeyMapping = new Dictionary<AvaKey, Key>();
foreach (var key in inputKeys)
{
if (TryGetAvaKey((Key)key, out var index))
if (TryGetAvaKey(key, out var index))
{
_avaKeyMapping[index] = (Key)key;
_avaKeyMapping[index] = key;
}
}
}
public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
{
var keyExist = (int)key < _keyMapping.Length;
avaKey = AvaKey.None;
bool keyExist = (int)key < _keyMapping.Length;
if (keyExist)
{
avaKey = _keyMapping[(int)key];
}
else
{
avaKey = AvaKey.None;
}
return keyExist;
}

View File

@@ -10,15 +10,12 @@ namespace Ryujinx.Ava.Input
{
private AvaloniaMouseDriver _driver;
public GamepadFeaturesFlag Features => throw new NotImplementedException();
public string Id => "0";
public string Id => "0";
public string Name => "AvaloniaMouse";
public bool IsConnected => true;
public bool[] Buttons => _driver.PressedButtons;
public bool IsConnected => true;
public GamepadFeaturesFlag Features => throw new NotImplementedException();
public bool[] Buttons => _driver.PressedButtons;
public AvaloniaMouse(AvaloniaMouseDriver driver)
{

View File

@@ -11,35 +11,50 @@ namespace Ryujinx.Ava.Input
{
internal class AvaloniaMouseDriver : IGamepadDriver
{
private Control _widget;
private bool _isDisposed;
private Size _size;
private Control _widget;
private bool _isDisposed;
private Size _size;
private readonly Window _window;
public bool[] PressedButtons { get; }
public bool[] PressedButtons { get; }
public Vector2 CurrentPosition { get; private set; }
public Vector2 Scroll { get; private set; }
public Vector2 Scroll { get; private set; }
public string DriverName => "AvaloniaMouseDriver";
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
public AvaloniaMouseDriver(Window window, Control parent)
{
_widget = parent;
_window = window;
_widget.PointerMoved += Parent_PointerMovedEvent;
_widget.PointerPressed += Parent_PointerPressEvent;
_widget.PointerReleased += Parent_PointerReleaseEvent;
_widget.PointerMoved += Parent_PointerMovedEvent;
_widget.PointerPressed += Parent_PointerPressEvent;
_widget.PointerReleased += Parent_PointerReleaseEvent;
_widget.PointerWheelChanged += Parent_ScrollEvent;
_window.PointerMoved += Parent_PointerMovedEvent;
_window.PointerPressed += Parent_PointerPressEvent;
_window.PointerReleased += Parent_PointerReleaseEvent;
_window.PointerMoved += Parent_PointerMovedEvent;
_window.PointerPressed += Parent_PointerPressEvent;
_window.PointerReleased += Parent_PointerReleaseEvent;
_window.PointerWheelChanged += Parent_ScrollEvent;
PressedButtons = new bool[(int)MouseButton.Count];
_size = new Size((int)parent.Bounds.Width, (int)parent.Bounds.Height);
parent.GetObservable(Control.BoundsProperty).Subscribe(Resized);
parent.GetObservable(Visual.BoundsProperty).Subscribe(Resized);
}
public event Action<string> OnGamepadConnected
{
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
remove { }
}
private void Resized(Rect rect)
@@ -59,14 +74,12 @@ namespace Ryujinx.Ava.Input
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
{
var pointerProperties = args.GetCurrentPoint(_widget).Properties;
PressedButtons[(int)pointerProperties.PointerUpdateKind] = true;
PressedButtons[(int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind] = true;
}
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
{
var position = args.GetPosition(_widget);
Point position = args.GetPosition(_widget);
CurrentPosition = new Vector2((float)position.X, (float)position.Y);
}
@@ -96,22 +109,6 @@ namespace Ryujinx.Ava.Input
return _size;
}
public string DriverName => "Avalonia";
public event Action<string> OnGamepadConnected
{
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
remove { }
}
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
public IGamepad GetGamepad(string id)
{
return new AvaloniaMouse(this);
@@ -126,14 +123,14 @@ namespace Ryujinx.Ava.Input
_isDisposed = true;
_widget.PointerMoved -= Parent_PointerMovedEvent;
_widget.PointerPressed -= Parent_PointerPressEvent;
_widget.PointerReleased -= Parent_PointerReleaseEvent;
_widget.PointerMoved -= Parent_PointerMovedEvent;
_widget.PointerPressed -= Parent_PointerPressEvent;
_widget.PointerReleased -= Parent_PointerReleaseEvent;
_widget.PointerWheelChanged -= Parent_ScrollEvent;
_window.PointerMoved -= Parent_PointerMovedEvent;
_window.PointerPressed -= Parent_PointerPressEvent;
_window.PointerReleased -= Parent_PointerReleaseEvent;
_window.PointerMoved -= Parent_PointerMovedEvent;
_window.PointerPressed -= Parent_PointerPressEvent;
_window.PointerReleased -= Parent_PointerReleaseEvent;
_window.PointerWheelChanged -= Parent_ScrollEvent;
_widget = null;

View File

@@ -7,10 +7,12 @@ using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json.Linq;
using Ryujinx.Ava;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -82,7 +84,7 @@ namespace Ryujinx.Modules
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterConvertFailedMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
});
return;
@@ -117,7 +119,7 @@ namespace Ryujinx.Modules
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
}
@@ -135,7 +137,7 @@ namespace Ryujinx.Modules
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
}
@@ -148,7 +150,7 @@ namespace Ryujinx.Modules
Logger.Error?.Print(LogClass.Application, exception.Message);
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdaterFailedToGetVersionMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
});
return;
@@ -163,7 +165,7 @@ namespace Ryujinx.Modules
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterConvertFailedGithubMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
});
return;
@@ -175,7 +177,7 @@ namespace Ryujinx.Modules
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
}
@@ -208,8 +210,8 @@ namespace Ryujinx.Modules
Dispatcher.UIThread.Post(async () =>
{
// Show a message asking the user if they want to update
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance["RyujinxUpdater"],
LocaleManager.Instance["RyujinxUpdaterMessage"],
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
$"{Program.Version} -> {newVersion}");
if (shouldUpdate)
@@ -245,8 +247,8 @@ namespace Ryujinx.Modules
var taskDialog = new TaskDialog()
{
Header = LocaleManager.Instance["RyujinxUpdater"],
SubHeader = LocaleManager.Instance["UpdaterDownloading"],
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
Buttons = { },
ShowProgressBar = true
@@ -270,22 +272,23 @@ namespace Ryujinx.Modules
if (UpdateSuccessful)
{
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance["RyujinxUpdater"],
LocaleManager.Instance["DialogUpdaterCompleteMessage"],
LocaleManager.Instance["DialogUpdaterRestartMessage"]);
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
if (shouldRestart)
{
string ryuName = Path.GetFileName(Environment.ProcessPath);
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
var ryuArg = Environment.GetCommandLineArgs().Skip(1);
if (!OperatingSystem.IsWindows())
if (!Path.Exists(ryuExe))
{
chmod(ryuExe, Convert.ToUInt32("0777", 8));
ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
}
Process.Start(ryuExe, ryuArg);
SetFileExecutable(ryuExe);
Process.Start(ryuExe, CommandLineState.Arguments);
Environment.Exit(0);
}
@@ -355,7 +358,7 @@ namespace Ryujinx.Modules
list[index] = args.Result;
Interlocked.Increment(ref completedRequests);
if (Interlocked.Equals(completedRequests, ConnectionCount))
if (Equals(completedRequests, ConnectionCount))
{
byte[] mergedFileBytes = new byte[_buildSize];
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
@@ -456,23 +459,26 @@ namespace Ryujinx.Modules
worker.Start();
}
[DllImport("libc", SetLastError = true)]
private static extern int chmod(string path, uint mode);
private static void SetUnixPermissions()
private static void SetFileExecutable(string path)
{
string ryuBin = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
const UnixFileMode ExecutableFileMode = UnixFileMode.UserExecute |
UnixFileMode.UserWrite |
UnixFileMode.UserRead |
UnixFileMode.GroupRead |
UnixFileMode.GroupWrite |
UnixFileMode.OtherRead |
UnixFileMode.OtherWrite;
if (!OperatingSystem.IsWindows())
if (!OperatingSystem.IsWindows() && File.Exists(path))
{
chmod(ryuBin, 493);
File.SetUnixFileMode(path, ExecutableFileMode);
}
}
private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
{
// Extract Update
taskDialog.SubHeader = LocaleManager.Instance["UpdaterExtracting"];
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
if (OperatingSystem.IsLinux())
@@ -550,7 +556,7 @@ namespace Ryujinx.Modules
List<string> allFiles = EnumerateFilesToDelete().ToList();
taskDialog.SubHeader = LocaleManager.Instance["UpdaterRenaming"];
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
// Replace old files
@@ -571,13 +577,13 @@ namespace Ryujinx.Modules
}
catch
{
Logger.Warning?.Print(LogClass.Application, string.Format(LocaleManager.Instance["UpdaterRenameFailed"], file));
Logger.Warning?.Print(LogClass.Application, string.Format(LocaleManager.Instance[LocaleKeys.UpdaterRenameFailed], file));
}
}
Dispatcher.UIThread.Post(() =>
{
taskDialog.SubHeader = LocaleManager.Instance["UpdaterAddingFiles"];
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
});
@@ -586,7 +592,7 @@ namespace Ryujinx.Modules
Directory.Delete(UpdateDir, true);
SetUnixPermissions();
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
UpdateSuccessful = true;
@@ -601,8 +607,8 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterArchNotSupportedMessage"],
LocaleManager.Instance["DialogUpdaterArchNotSupportedSubMessage"]);
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]);
}
return false;
@@ -612,8 +618,8 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterNoInternetMessage"],
LocaleManager.Instance["DialogUpdaterNoInternetSubMessage"]);
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]);
}
return false;
@@ -623,8 +629,8 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterDirtyBuildMessage"],
LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]);
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
}
return false;
@@ -636,11 +642,11 @@ namespace Ryujinx.Modules
{
if (ReleaseInformations.IsFlatHubBuild())
{
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterFlatpakNotSupportedMessage"]);
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]);
}
else
{
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]);
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
}
}

View File

@@ -1,12 +1,12 @@
using ARMeilleure.Translation.PTC;
using Avalonia;
using Avalonia.Threading;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.System;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.SystemInfo;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
@@ -20,15 +20,16 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava
{
internal class Program
internal partial class Program
{
public static double WindowScaleFactor { get; set; }
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
public static double WindowScaleFactor { get; set; }
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
[DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
[LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
private const uint MB_ICONWARNING = 0x30;
@@ -81,8 +82,8 @@ namespace Ryujinx.Ava
Console.Title = $"Ryujinx Console {Version}";
// Hook unhandled exception and process exit events.
AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => Exit();
AppDomain.CurrentDomain.UnhandledException += (sender, e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (sender, e) => Exit();
// Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg);

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
@@ -18,27 +18,31 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
<PackageReference Include="Avalonia.Svg" Version="0.10.18" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageReference Include="DynamicData" Version="7.12.8" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.4.2" />
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="Avalonia.Diagnostics" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
<PackageReference Include="Avalonia.Svg" />
<PackageReference Include="Avalonia.Svg.Skia" />
<PackageReference Include="jp2masa.Avalonia.Flexbox" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="XamlNameReferenceGenerator" />
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageReference Include="SPB" Version="0.0.4-build28" />
<PackageReference Include="SharpZipLib" Version="1.4.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
<PackageReference Include="Silk.NET.Vulkan" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
<!--NOTE: DO NOT REMOVE, THIS IS REQUIRED AS A RESULT OF A TRIMMING ISSUE IN AVALONIA -->
<PackageReference Include="System.Drawing.Common" />
</ItemGroup>
<ItemGroup>
@@ -54,17 +58,22 @@
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Ui.LocaleGenerator\Ryujinx.Ui.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<Content Include="..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>alsoft.ini</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="..\distribution\legal\THIRDPARTY.md">
</Content>
<Content Include="..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</ContentWithTargetPath>
</Content>
<Content Include="..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup>
@@ -150,4 +159,7 @@
<EmbeddedResource Include="Assets\Locales\zh_TW.json" />
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Assets\Locales\en_US.json" />
</ItemGroup>
</Project>

View File

@@ -2,8 +2,9 @@ using Avalonia.Controls;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
@@ -11,7 +12,7 @@ using Ryujinx.HLE.Ui;
using System;
using System.Threading;
namespace Ryujinx.Ava.Ui.Applet
namespace Ryujinx.Ava.UI.Applet
{
internal class AvaHostUiHandler : IHostUiHandler
{
@@ -32,15 +33,15 @@ namespace Ryujinx.Ava.Ui.Applet
? args.PlayerCountMin.ToString()
: $"{args.PlayerCountMin}-{args.PlayerCountMax}";
string key = args.PlayerCountMin == args.PlayerCountMax ? "DialogControllerAppletMessage" : "DialogControllerAppletMessagePlayerRange";
LocaleKeys key = args.PlayerCountMin == args.PlayerCountMax ? LocaleKeys.DialogControllerAppletMessage : LocaleKeys.DialogControllerAppletMessagePlayerRange;
string message = string.Format(LocaleManager.Instance[key],
playerCount,
args.SupportedStyles,
string.Join(", ", args.SupportedPlayers),
args.IsDocked ? LocaleManager.Instance["DialogControllerAppletDockModeSet"] : "");
args.IsDocked ? LocaleManager.Instance[LocaleKeys.DialogControllerAppletDockModeSet] : "");
return DisplayMessageDialog(LocaleManager.Instance["DialogControllerAppletTitle"], message);
return DisplayMessageDialog(LocaleManager.Instance[LocaleKeys.DialogControllerAppletTitle], message);
}
public bool DisplayMessageDialog(string title, string message)
@@ -61,9 +62,9 @@ namespace Ryujinx.Ava.Ui.Applet
title,
message,
"",
LocaleManager.Instance["DialogOpenSettingsWindowLabel"],
LocaleManager.Instance[LocaleKeys.DialogOpenSettingsWindowLabel],
"",
LocaleManager.Instance["SettingsButtonClose"],
LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
(int)Symbol.Important,
deferEvent,
async (window) =>
@@ -91,7 +92,7 @@ namespace Ryujinx.Ava.Ui.Applet
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogMessageDialogErrorExceptionMessage], ex));
dialogCloseEvent.Set();
}
@@ -114,7 +115,7 @@ namespace Ryujinx.Ava.Ui.Applet
{
try
{
var response = await SwkbdAppletDialog.ShowInputDialog(_parent, LocaleManager.Instance["SoftwareKeyboard"], args);
var response = await SwkbdAppletDialog.ShowInputDialog(_parent, LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
if (response.Result == UserResult.Ok)
{
@@ -125,7 +126,7 @@ namespace Ryujinx.Ava.Ui.Applet
catch (Exception ex)
{
error = true;
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage], ex));
}
finally
{
@@ -180,7 +181,7 @@ namespace Ryujinx.Ava.Ui.Applet
catch (Exception ex)
{
dialogCloseEvent.Set();
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogErrorAppletErrorExceptionMessage], ex));
}
});

View File

@@ -3,15 +3,16 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Threading;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.Ui;
using System;
using System.Threading;
using HidKey = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.Ui.Applet
namespace Ryujinx.Ava.UI.Applet
{
class AvaloniaDynamicTextInputHandler : IDynamicTextInputHandler
{
@@ -66,9 +67,9 @@ namespace Ryujinx.Ava.Ui.Applet
});
}
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, Avalonia.Input.KeyEventArgs e)
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e)
{
var key = (HidKey)AvaloniaMappingHelper.ToInputKey(e.Key);
var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true))
{
@@ -88,7 +89,7 @@ namespace Ryujinx.Ava.Ui.Applet
private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e)
{
var key = (HidKey)AvaloniaMappingHelper.ToInputKey(e.Key);
var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true))
{

View File

@@ -1,9 +1,9 @@
using Avalonia.Media;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.Ui;
using System;
namespace Ryujinx.Ava.Ui.Applet
namespace Ryujinx.Ava.UI.Applet
{
class AvaloniaHostUiTheme : IHostUiTheme
{

View File

@@ -1,11 +1,10 @@
<Window
x:Class="Ryujinx.Ava.Ui.Applet.ErrorAppletWindow"
x:Class="Ryujinx.Ava.UI.Applet.ErrorAppletWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Title="{locale:Locale ErrorWindowTitle}"
Width="450"
Height="340"

View File

@@ -3,10 +3,10 @@ using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Ava.UI.Windows;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Applet
namespace Ryujinx.Ava.UI.Applet
{
internal partial class ErrorAppletWindow : StyleableWindow
{
@@ -34,7 +34,7 @@ namespace Ryujinx.Ava.Ui.Applet
}
else
{
AddButton(LocaleManager.Instance["InputDialogOk"], 0);
AddButton(LocaleManager.Instance[LocaleKeys.InputDialogOk], 0);
}
}

View File

@@ -1,10 +1,9 @@
<UserControl
x:Class="Ryujinx.Ava.Ui.Controls.SwkbdAppletDialog"
x:Class="Ryujinx.Ava.UI.Controls.SwkbdAppletDialog"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Width="400"
mc:Ignorable="d"
Focusable="True">
@@ -45,7 +44,7 @@
TextWrapping="Wrap" />
<TextBox
Name="Input"
Grid.Row="2"
Grid.Row="3"
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"

Some files were not shown because too many files have changed in this diff Show More