Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5784e9af2 | |||
| 324029d4f9 | |||
| 03179ecafe | |||
| 41b8ecdeb6 | |||
| 663ea382da | |||
| d90961122c | |||
| c715fc4c5e | |||
| 841b295ad0 | |||
| 9e87193725 | |||
| e126021ffe | |||
| 80a56e8893 | |||
| 0e33b19ae0 | |||
| 5043036688 | |||
| b65456b958 | |||
| 076e4d44c3 | |||
| 1ec71b6ea0 | |||
| f95ea04995 | |||
| 371226448a | |||
| 6597b3817c | |||
| 7299356f37 | |||
| 72b2f5d34f | |||
| aeec0f8a38 | |||
| 5ce3015945 | |||
| 5219615418 | |||
| 045f50bc7f | |||
| 94a25b75a0 | |||
| 3b0fe38e86 | |||
| 40c230e0fa | |||
| ba5419b965 | |||
| 39319f09d8 | |||
| ded0b9d093 | |||
| 827ff077e7 | |||
| 38980b2471 | |||
| 57e43682ed | |||
| abfdc3aa7d | |||
| 0ee7c985da | |||
| 91cbe52122 | |||
| 0914e84014 | |||
| 98913986e7 | |||
| 45ecd601be | |||
| f64917a852 | |||
| e11a77d2c6 | |||
| 3dcccabd1d | |||
| ad9ce67b52 | |||
| a1f13a3662 | |||
| 2579a7199b | |||
| b5ed2d408c | |||
| 0090d3d087 | |||
| c573920c01 | |||
| a5dcccfdd2 | |||
| e9b9fc4674 | |||
| 37faf24c3f | |||
| d1e1ea0fef | |||
| 92c162126b | |||
| 0aa6ec4276 | |||
| d176feffad | |||
| 1aba91e993 | |||
| fae65d8a72 | |||
| cde658cb27 | |||
| e6bd1fd1b8 | |||
| ce43139eb7 | |||
| 01de4fa26a | |||
| bcd3c79eca | |||
| 403e36fab2 | |||
| 69fa6b4906 | |||
| f2a680ca89 | |||
| e92164e6a0 | |||
| f3ac088345 | |||
| 2e2dde2f95 | |||
| 2680526e6b | |||
| 57d9ef5a89 | |||
| 98f5d8a713 | |||
| 1c9a1de30d | |||
| 4b9b203c09 | |||
| fe90c4fd7b | |||
| 7b65ff083d | |||
| 45b73ba840 | |||
| 40320a1d84 | |||
| 1be18dc110 | |||
| e6fc3b5662 | |||
| 2d207ec609 | |||
| 9e1b0af259 | |||
| 3db9a25977 | |||
| 3f7b0e0772 | |||
| 9864da7d43 | |||
| 1bb8c27a70 | |||
| 8ffc491546 | |||
| 1e2a92918b | |||
| ff34b47dfb | |||
| 7b50c48df7 | |||
| d12d59f62a | |||
| 045255a0a0 | |||
| f5f30781ae | |||
| 1bd70d73c0 | |||
| eb149ec696 | |||
| 334ef2efdd | |||
| e7c174b426 | |||
| de79897f04 | |||
| efa0b7a056 | |||
| fc505110f1 | |||
| f0fac0c7fb | |||
| f60d5aac3e | |||
| ccdd84a778 | |||
| f119ef798b | |||
| 3dcaaa18be | |||
| 56ac22f737 | |||
| ea7bda25ba | |||
| 58ee9b4197 | |||
| b8885aa03b | |||
| c024e5c69a | |||
| 504175e5b6 | |||
| 3bfaabdbdd | |||
| bafef3d1c9 | |||
| 91df2beee3 | |||
| 0eaf7e1daa | |||
| 4c1a95ed61 | |||
| 01d1b5cdaf | |||
| 85db5f4091 | |||
| aed6011d7c | |||
| 80ac1331b5 | |||
| 1f1c3bddc0 | |||
| dcc5562cd5 | |||
| 1b3d86c02f | |||
| d37f0b29e2 | |||
| 0947f613b1 | |||
| 1b8fe7073b | |||
| bc699ace15 | |||
| f7d59f3e0e | |||
| 2c67bbf609 | |||
| 5692c48ab7 | |||
| 80b4bd3583 | |||
| 22263ccaa4 | |||
| ef8acc9c3d | |||
| 3dcbba38bf | |||
| f4eb7dceaf | |||
| b924c71822 | |||
| 8a497adf85 | |||
| d68856ab12 | |||
| 380658c21d |
@@ -16,6 +16,9 @@
|
||||
[submodule "libressl"]
|
||||
path = externals/libressl
|
||||
url = https://github.com/citra-emu/ext-libressl-portable.git
|
||||
[submodule "libusb"]
|
||||
path = externals/libusb/libusb
|
||||
url = https://github.com/libusb/libusb.git
|
||||
[submodule "discord-rpc"]
|
||||
path = externals/discord-rpc
|
||||
url = https://github.com/discordapp/discord-rpc.git
|
||||
@@ -34,9 +37,6 @@
|
||||
[submodule "xbyak"]
|
||||
path = externals/xbyak
|
||||
url = https://github.com/herumi/xbyak.git
|
||||
[submodule "externals/libusb"]
|
||||
path = externals/libusb
|
||||
url = https://github.com/ameerj/libusb
|
||||
[submodule "opus"]
|
||||
path = externals/opus/opus
|
||||
url = https://github.com/xiph/opus.git
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
path_classifiers:
|
||||
library: "externals"
|
||||
extraction:
|
||||
cpp:
|
||||
prepare:
|
||||
packages:
|
||||
- "libsdl2-dev"
|
||||
- "qtmultimedia5-dev"
|
||||
- "clang-format-10"
|
||||
- "libtbb-dev"
|
||||
- "libjack-jackd2-dev"
|
||||
- "doxygen"
|
||||
- "graphviz"
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,55 @@
|
||||
<RCC>
|
||||
<qresource prefix="controller">
|
||||
<file alias="dual_joycon">dual_joycon.png</file>
|
||||
<file alias="dual_joycon_dark">dual_joycon_dark.png</file>
|
||||
<file alias="dual_joycon_midnight">dual_joycon_midnight.png</file>
|
||||
<file alias="handheld">handheld.png</file>
|
||||
<file alias="handheld_dark">handheld_dark.png</file>
|
||||
<file alias="handheld_midnight">handheld_midnight.png</file>
|
||||
<file alias="pro_controller">pro_controller.png</file>
|
||||
<file alias="pro_controller_dark">pro_controller_dark.png</file>
|
||||
<file alias="pro_controller_midnight">pro_controller_midnight.png</file>
|
||||
<file alias="single_joycon_left">single_joycon_left.png</file>
|
||||
<file alias="single_joycon_left_dark">single_joycon_left_dark.png</file>
|
||||
<file alias="single_joycon_left_midnight">single_joycon_left_midnight.png</file>
|
||||
<file alias="single_joycon_right">single_joycon_right.png</file>
|
||||
<file alias="single_joycon_right_dark">single_joycon_right_dark.png</file>
|
||||
<file alias="single_joycon_right_midnight">single_joycon_right_midnight.png</file>
|
||||
<file alias="single_joycon_left_vertical">single_joycon_left_vertical.png</file>
|
||||
<file alias="single_joycon_left_vertical_dark">single_joycon_left_vertical_dark.png</file>
|
||||
<file alias="single_joycon_left_vertical_midnight">single_joycon_left_vertical_midnight.png</file>
|
||||
<file alias="single_joycon_right_vertical">single_joycon_right_vertical.png</file>
|
||||
<file alias="single_joycon_right_vertical_dark">single_joycon_right_vertical_dark.png</file>
|
||||
<file alias="single_joycon_right_vertical_midnight">single_joycon_right_vertical_midnight.png</file>
|
||||
<file alias="applet_dual_joycon">applet_dual_joycon.png</file>
|
||||
<file alias="applet_dual_joycon_dark">applet_dual_joycon_dark.png</file>
|
||||
<file alias="applet_dual_joycon_midnight">applet_dual_joycon_midnight.png</file>
|
||||
<file alias="applet_handheld">applet_handheld.png</file>
|
||||
<file alias="applet_handheld_dark">applet_handheld_dark.png</file>
|
||||
<file alias="applet_handheld_midnight">applet_handheld_midnight.png</file>
|
||||
<file alias="applet_pro_controller">applet_pro_controller.png</file>
|
||||
<file alias="applet_pro_controller_dark">applet_pro_controller_dark.png</file>
|
||||
<file alias="applet_pro_controller_midnight">applet_pro_controller_midnight.png</file>
|
||||
<file alias="applet_joycon_left">applet_single_joycon_left.png</file>
|
||||
<file alias="applet_joycon_left_dark">applet_single_joycon_left_dark.png</file>
|
||||
<file alias="applet_joycon_left_midnight">applet_single_joycon_left_midnight.png</file>
|
||||
<file alias="applet_joycon_right">applet_single_joycon_right.png</file>
|
||||
<file alias="applet_joycon_right_dark">applet_single_joycon_right_dark.png</file>
|
||||
<file alias="applet_joycon_right_midnight">applet_single_joycon_right_midnight.png</file>
|
||||
<file alias="applet_dual_joycon_disabled">applet_dual_joycon_disabled.png</file>
|
||||
<file alias="applet_dual_joycon_dark_disabled">applet_dual_joycon_dark_disabled.png</file>
|
||||
<file alias="applet_dual_joycon_midnight_disabled">applet_dual_joycon_midnight_disabled.png</file>
|
||||
<file alias="applet_handheld_disabled">applet_handheld_disabled.png</file>
|
||||
<file alias="applet_handheld_dark_disabled">applet_handheld_dark_disabled.png</file>
|
||||
<file alias="applet_handheld_midnight_disabled">applet_handheld_midnight_disabled.png</file>
|
||||
<file alias="applet_pro_controller_disabled">applet_pro_controller_disabled.png</file>
|
||||
<file alias="applet_pro_controller_dark_disabled">applet_pro_controller_dark_disabled.png</file>
|
||||
<file alias="applet_pro_controller_midnight_disabled">applet_pro_controller_midnight_disabled.png</file>
|
||||
<file alias="applet_joycon_left_disabled">applet_single_joycon_left_disabled.png</file>
|
||||
<file alias="applet_joycon_left_dark_disabled">applet_single_joycon_left_dark_disabled.png</file>
|
||||
<file alias="applet_joycon_left_midnight_disabled">applet_single_joycon_left_midnight_disabled.png</file>
|
||||
<file alias="applet_joycon_right_disabled">applet_single_joycon_right_disabled.png</file>
|
||||
<file alias="applet_joycon_right_dark_disabled">applet_single_joycon_right_dark_disabled.png</file>
|
||||
<file alias="applet_joycon_right_midnight_disabled">applet_single_joycon_right_midnight_disabled.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -5,6 +5,7 @@ Icon Name | License | Origin/Author
|
||||
qt_themes/default/icons/16x16/checked.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/failed.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
|
||||
qt_themes/default/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
@@ -12,6 +13,7 @@ qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
|
||||
qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
@@ -19,6 +21,7 @@ qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
|
||||
qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
|
||||
|
After Width: | Height: | Size: 362 B |
|
After Width: | Height: | Size: 362 B |
@@ -2,6 +2,7 @@
|
||||
<qresource prefix="icons/colorful_dark">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
|
||||
|
||||
|
After Width: | Height: | Size: 362 B |
|
After Width: | Height: | Size: 362 B |
@@ -2,6 +2,7 @@
|
||||
<qresource prefix="icons/colorful_midnight_blue">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
|
||||
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
|
||||
|
After Width: | Height: | Size: 349 B |
|
After Width: | Height: | Size: 349 B |
@@ -30,6 +30,250 @@ QPushButton#RendererStatusBarButton:checked {
|
||||
color: #e85c00;
|
||||
}
|
||||
|
||||
QPushButton#RendererStatusBarButton:!checked{
|
||||
QPushButton#RendererStatusBarButton:!checked {
|
||||
color: #0066ff;
|
||||
}
|
||||
|
||||
QPushButton#buttonRefreshDevices {
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
max-width: 20px;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
QWidget#bottomPerGameInput,
|
||||
QWidget#topControllerApplet,
|
||||
QWidget#bottomControllerApplet,
|
||||
QGroupBox#groupPlayer1Connected:checked,
|
||||
QGroupBox#groupPlayer2Connected:checked,
|
||||
QGroupBox#groupPlayer3Connected:checked,
|
||||
QGroupBox#groupPlayer4Connected:checked,
|
||||
QGroupBox#groupPlayer5Connected:checked,
|
||||
QGroupBox#groupPlayer6Connected:checked,
|
||||
QGroupBox#groupPlayer7Connected:checked,
|
||||
QGroupBox#groupPlayer8Connected:checked {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
QWidget#topControllerApplet {
|
||||
border-bottom: 1px solid #828790
|
||||
}
|
||||
|
||||
QWidget#bottomPerGameInput,
|
||||
QWidget#bottomControllerApplet {
|
||||
border-top: 1px solid #828790
|
||||
}
|
||||
|
||||
QWidget#topPerGameInput,
|
||||
QWidget#middleControllerApplet {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
QWidget#topPerGameInput QComboBox,
|
||||
QWidget#middleControllerApplet QComboBox {
|
||||
width: 123px;
|
||||
}
|
||||
|
||||
QWidget#connectedControllers {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#playersSupported,
|
||||
QWidget#controllersSupported,
|
||||
QWidget#controllerSupported1,
|
||||
QWidget#controllerSupported2,
|
||||
QWidget#controllerSupported3,
|
||||
QWidget#controllerSupported4,
|
||||
QWidget#controllerSupported5,
|
||||
QWidget#controllerSupported6 {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QGroupBox#groupPlayer1Connected,
|
||||
QGroupBox#groupPlayer2Connected,
|
||||
QGroupBox#groupPlayer3Connected,
|
||||
QGroupBox#groupPlayer4Connected,
|
||||
QGroupBox#groupPlayer5Connected,
|
||||
QGroupBox#groupPlayer6Connected,
|
||||
QGroupBox#groupPlayer7Connected,
|
||||
QGroupBox#groupPlayer8Connected {
|
||||
border: 1px solid #828790;
|
||||
border-radius: 3px;
|
||||
padding: 0px;
|
||||
min-height: 98px;
|
||||
max-height: 98px;
|
||||
}
|
||||
|
||||
QGroupBox#groupPlayer1Connected:unchecked,
|
||||
QGroupBox#groupPlayer2Connected:unchecked,
|
||||
QGroupBox#groupPlayer3Connected:unchecked,
|
||||
QGroupBox#groupPlayer4Connected:unchecked,
|
||||
QGroupBox#groupPlayer5Connected:unchecked,
|
||||
QGroupBox#groupPlayer6Connected:unchecked,
|
||||
QGroupBox#groupPlayer7Connected:unchecked,
|
||||
QGroupBox#groupPlayer8Connected:unchecked {
|
||||
border: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
QGroupBox#groupPlayer1Connected::title,
|
||||
QGroupBox#groupPlayer2Connected::title,
|
||||
QGroupBox#groupPlayer3Connected::title,
|
||||
QGroupBox#groupPlayer4Connected::title,
|
||||
QGroupBox#groupPlayer5Connected::title,
|
||||
QGroupBox#groupPlayer6Connected::title,
|
||||
QGroupBox#groupPlayer7Connected::title,
|
||||
QGroupBox#groupPlayer8Connected::title {
|
||||
subcontrol-origin: margin;
|
||||
subcontrol-position: top left;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-top: 1px;
|
||||
margin-left: 0px;
|
||||
margin-right: -4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
QCheckBox#checkboxPlayer1Connected,
|
||||
QCheckBox#checkboxPlayer2Connected,
|
||||
QCheckBox#checkboxPlayer3Connected,
|
||||
QCheckBox#checkboxPlayer4Connected,
|
||||
QCheckBox#checkboxPlayer5Connected,
|
||||
QCheckBox#checkboxPlayer6Connected,
|
||||
QCheckBox#checkboxPlayer7Connected,
|
||||
QCheckBox#checkboxPlayer8Connected {
|
||||
spacing: 0px;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox,
|
||||
QWidget#Player2LEDs QCheckBox,
|
||||
QWidget#Player3LEDs QCheckBox,
|
||||
QWidget#Player4LEDs QCheckBox,
|
||||
QWidget#Player5LEDs QCheckBox,
|
||||
QWidget#Player6LEDs QCheckBox,
|
||||
QWidget#Player7LEDs QCheckBox,
|
||||
QWidget#Player8LEDs QCheckBox {
|
||||
spacing: 0px;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox::indicator,
|
||||
QWidget#Player2LEDs QCheckBox::indicator,
|
||||
QWidget#Player3LEDs QCheckBox::indicator,
|
||||
QWidget#Player4LEDs QCheckBox::indicator,
|
||||
QWidget#Player5LEDs QCheckBox::indicator,
|
||||
QWidget#Player6LEDs QCheckBox::indicator,
|
||||
QWidget#Player7LEDs QCheckBox::indicator,
|
||||
QWidget#Player8LEDs QCheckBox::indicator {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
QCheckBox#checkboxPlayer1Connected::indicator,
|
||||
QCheckBox#checkboxPlayer2Connected::indicator,
|
||||
QCheckBox#checkboxPlayer3Connected::indicator,
|
||||
QCheckBox#checkboxPlayer4Connected::indicator,
|
||||
QCheckBox#checkboxPlayer5Connected::indicator,
|
||||
QCheckBox#checkboxPlayer6Connected::indicator,
|
||||
QCheckBox#checkboxPlayer7Connected::indicator,
|
||||
QCheckBox#checkboxPlayer8Connected::indicator {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
QGroupBox#groupPlayer1Connected::indicator,
|
||||
QGroupBox#groupPlayer2Connected::indicator,
|
||||
QGroupBox#groupPlayer3Connected::indicator,
|
||||
QGroupBox#groupPlayer4Connected::indicator,
|
||||
QGroupBox#groupPlayer5Connected::indicator,
|
||||
QGroupBox#groupPlayer6Connected::indicator,
|
||||
QGroupBox#groupPlayer7Connected::indicator,
|
||||
QGroupBox#groupPlayer8Connected::indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player2LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player3LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player4LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player5LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player6LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player7LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player8LEDs QCheckBox::indicator:checked,
|
||||
QGroupBox#groupPlayer1Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer2Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer3Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer4Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer5Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer6Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer7Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer8Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer1Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer2Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer3Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer4Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer5Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer6Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer7Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer8Connected::indicator:checked,
|
||||
QGroupBox#groupConnectedController::indicator:checked {
|
||||
border-radius: 2px;
|
||||
border: 1px solid #929192;
|
||||
background: #39ff14;
|
||||
image: none;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player2LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player3LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player4LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player5LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player6LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player7LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player8LEDs QCheckBox::indicator:unchecked,
|
||||
QGroupBox#groupPlayer1Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer2Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer3Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer4Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer5Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer6Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer7Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer8Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer4Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer5Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer6Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer7Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
|
||||
QGroupBox#groupConnectedController::indicator:unchecked {
|
||||
border-radius: 2px;
|
||||
border: 1px solid #929192;
|
||||
background: transparent;
|
||||
image: none;
|
||||
}
|
||||
|
||||
QWidget#controllerPlayer1,
|
||||
QWidget#controllerPlayer2,
|
||||
QWidget#controllerPlayer3,
|
||||
QWidget#controllerPlayer4,
|
||||
QWidget#controllerPlayer5,
|
||||
QWidget#controllerPlayer6,
|
||||
QWidget#controllerPlayer7,
|
||||
QWidget#controllerPlayer8 {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 362 B |
|
After Width: | Height: | Size: 362 B |
@@ -2,6 +2,7 @@
|
||||
<qresource prefix="icons/qdarkstyle">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
@@ -51,6 +52,6 @@
|
||||
<file>rc/radio_unchecked.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="qdarkstyle">
|
||||
<file>style.qss</file>
|
||||
<file>style.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -40,8 +40,8 @@ QCheckBox:disabled {
|
||||
|
||||
QCheckBox::indicator,
|
||||
QGroupBox::indicator {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
QGroupBox::indicator {
|
||||
@@ -1237,6 +1237,7 @@ QPlainTextEdit:disabled {
|
||||
background-color: #2b2e31;
|
||||
}
|
||||
|
||||
|
||||
QPushButton#TogglableStatusBarButton {
|
||||
min-width: 0px;
|
||||
color: #656565;
|
||||
@@ -1271,6 +1272,305 @@ QPushButton#RendererStatusBarButton:checked {
|
||||
color: #e85c00;
|
||||
}
|
||||
|
||||
QPushButton#RendererStatusBarButton:!checked{
|
||||
color: #00ccdd;
|
||||
QPushButton#RendererStatusBarButton:!checked {
|
||||
color: #00ccdd;
|
||||
}
|
||||
|
||||
QPushButton#buttonRefreshDevices {
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
padding: 0px 0px;
|
||||
}
|
||||
|
||||
QSpinBox#spinboxLStickRange,
|
||||
QSpinBox#spinboxRStickRange {
|
||||
padding: 4px 0px 5px 0px;
|
||||
min-width: 63px;
|
||||
}
|
||||
|
||||
QSpinBox#vibrationSpin {
|
||||
padding: 4px 0px 5px 0px;
|
||||
min-width: 63px;
|
||||
}
|
||||
|
||||
QSpinBox#spinboxLStickRange:up-button,
|
||||
QSpinBox#spinboxRStickRange:up-button,
|
||||
QSpinBox#vibrationSpin:up-button {
|
||||
left: -2px;
|
||||
}
|
||||
|
||||
QSpinBox#spinboxLStickRange:down-button,
|
||||
QSpinBox#spinboxRStickRange:down-button,
|
||||
QSpinBox#vibrationSpin:down-button {
|
||||
right: -1px;
|
||||
}
|
||||
|
||||
QGroupBox#motionGroup::indicator,
|
||||
QGroupBox#vibrationGroup::indicator {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
QGroupBox#motionGroup::title,
|
||||
QGroupBox#vibrationGroup::title {
|
||||
spacing: 2px;
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
}
|
||||
|
||||
QWidget#bottomPerGameInput,
|
||||
QWidget#topControllerApplet,
|
||||
QWidget#bottomControllerApplet,
|
||||
QGroupBox#groupPlayer1Connected:checked,
|
||||
QGroupBox#groupPlayer2Connected:checked,
|
||||
QGroupBox#groupPlayer3Connected:checked,
|
||||
QGroupBox#groupPlayer4Connected:checked,
|
||||
QGroupBox#groupPlayer5Connected:checked,
|
||||
QGroupBox#groupPlayer6Connected:checked,
|
||||
QGroupBox#groupPlayer7Connected:checked,
|
||||
QGroupBox#groupPlayer8Connected:checked {
|
||||
background-color: #232629;
|
||||
}
|
||||
|
||||
QWidget#topPerGameInput,
|
||||
QWidget#middleControllerApplet {
|
||||
background-color: #31363b;
|
||||
}
|
||||
|
||||
QWidget#topPerGameInput QComboBox,
|
||||
QWidget#middleControllerApplet QComboBox {
|
||||
width: 119px;
|
||||
}
|
||||
|
||||
QRadioButton#radioDocked {
|
||||
margin-left: -3px;
|
||||
}
|
||||
|
||||
|
||||
QRadioButton#radioUndocked {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
QWidget#connectedControllers {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#playersSupported,
|
||||
QWidget#controllersSupported,
|
||||
QWidget#controllerSupported1,
|
||||
QWidget#controllerSupported2,
|
||||
QWidget#controllerSupported3,
|
||||
QWidget#controllerSupported4,
|
||||
QWidget#controllerSupported5,
|
||||
QWidget#controllerSupported6 {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QGroupBox#groupPlayer1Connected,
|
||||
QGroupBox#groupPlayer2Connected,
|
||||
QGroupBox#groupPlayer3Connected,
|
||||
QGroupBox#groupPlayer4Connected,
|
||||
QGroupBox#groupPlayer5Connected,
|
||||
QGroupBox#groupPlayer6Connected,
|
||||
QGroupBox#groupPlayer7Connected,
|
||||
QGroupBox#groupPlayer8Connected {
|
||||
border: 1px solid #76797c;
|
||||
border-radius: 3px;
|
||||
padding: 0px;
|
||||
min-height: 98px;
|
||||
max-height: 98px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
QGroupBox#groupPlayer1Connected:unchecked,
|
||||
QGroupBox#groupPlayer2Connected:unchecked,
|
||||
QGroupBox#groupPlayer3Connected:unchecked,
|
||||
QGroupBox#groupPlayer4Connected:unchecked,
|
||||
QGroupBox#groupPlayer5Connected:unchecked,
|
||||
QGroupBox#groupPlayer6Connected:unchecked,
|
||||
QGroupBox#groupPlayer7Connected:unchecked,
|
||||
QGroupBox#groupPlayer8Connected:unchecked {
|
||||
border: 1px solid #54575b;
|
||||
}
|
||||
|
||||
QGroupBox#groupPlayer1Connected::title,
|
||||
QGroupBox#groupPlayer2Connected::title,
|
||||
QGroupBox#groupPlayer3Connected::title,
|
||||
QGroupBox#groupPlayer4Connected::title,
|
||||
QGroupBox#groupPlayer5Connected::title,
|
||||
QGroupBox#groupPlayer6Connected::title,
|
||||
QGroupBox#groupPlayer7Connected::title,
|
||||
QGroupBox#groupPlayer8Connected::title {
|
||||
subcontrol-origin: margin;
|
||||
subcontrol-position: top left;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-top: 1px;
|
||||
margin-left: -2px;
|
||||
margin-right: -4px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
QCheckBox#checkboxPlayer1Connected,
|
||||
QCheckBox#checkboxPlayer2Connected,
|
||||
QCheckBox#checkboxPlayer3Connected,
|
||||
QCheckBox#checkboxPlayer4Connected,
|
||||
QCheckBox#checkboxPlayer5Connected,
|
||||
QCheckBox#checkboxPlayer6Connected,
|
||||
QCheckBox#checkboxPlayer7Connected,
|
||||
QCheckBox#checkboxPlayer8Connected {
|
||||
spacing: 0px;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs,
|
||||
QWidget#Player2LEDs,
|
||||
QWidget#Player3LEDs,
|
||||
QWidget#Player4LEDs,
|
||||
QWidget#Player5LEDs,
|
||||
QWidget#Player6LEDs,
|
||||
QWidget#Player7LEDs,
|
||||
QWidget#Player8LEDs {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox,
|
||||
QWidget#Player2LEDs QCheckBox,
|
||||
QWidget#Player3LEDs QCheckBox,
|
||||
QWidget#Player4LEDs QCheckBox,
|
||||
QWidget#Player5LEDs QCheckBox,
|
||||
QWidget#Player6LEDs QCheckBox,
|
||||
QWidget#Player7LEDs QCheckBox,
|
||||
QWidget#Player8LEDs QCheckBox {
|
||||
spacing: 0px;
|
||||
margin-bottom: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox::indicator,
|
||||
QWidget#Player2LEDs QCheckBox::indicator,
|
||||
QWidget#Player3LEDs QCheckBox::indicator,
|
||||
QWidget#Player4LEDs QCheckBox::indicator,
|
||||
QWidget#Player5LEDs QCheckBox::indicator,
|
||||
QWidget#Player6LEDs QCheckBox::indicator,
|
||||
QWidget#Player7LEDs QCheckBox::indicator,
|
||||
QWidget#Player8LEDs QCheckBox::indicator {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
QCheckBox#checkboxPlayer1Connected::indicator,
|
||||
QCheckBox#checkboxPlayer2Connected::indicator,
|
||||
QCheckBox#checkboxPlayer3Connected::indicator,
|
||||
QCheckBox#checkboxPlayer4Connected::indicator,
|
||||
QCheckBox#checkboxPlayer5Connected::indicator,
|
||||
QCheckBox#checkboxPlayer6Connected::indicator,
|
||||
QCheckBox#checkboxPlayer7Connected::indicator,
|
||||
QCheckBox#checkboxPlayer8Connected::indicator {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
QGroupBox#groupPlayer1Connected::indicator,
|
||||
QGroupBox#groupPlayer2Connected::indicator,
|
||||
QGroupBox#groupPlayer3Connected::indicator,
|
||||
QGroupBox#groupPlayer4Connected::indicator,
|
||||
QGroupBox#groupPlayer5Connected::indicator,
|
||||
QGroupBox#groupPlayer6Connected::indicator,
|
||||
QGroupBox#groupPlayer7Connected::indicator,
|
||||
QGroupBox#groupPlayer8Connected::indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player2LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player3LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player4LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player5LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player6LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player7LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player8LEDs QCheckBox::indicator:checked,
|
||||
QGroupBox#groupPlayer1Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer2Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer3Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer4Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer5Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer6Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer7Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer8Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer1Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer2Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer3Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer4Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer5Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer6Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer7Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer8Connected::indicator:checked,
|
||||
QGroupBox#groupConnectedController::indicator:checked {
|
||||
border-radius: 2px;
|
||||
border: 1px solid #929192;
|
||||
background: #39ff14;
|
||||
image: none;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player2LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player3LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player4LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player5LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player6LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player7LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player8LEDs QCheckBox::indicator:unchecked,
|
||||
QGroupBox#groupPlayer1Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer2Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer3Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer4Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer5Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer6Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer7Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer8Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer4Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer5Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer6Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer7Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
|
||||
QGroupBox#groupConnectedController::indicator:unchecked {
|
||||
border-radius: 2px;
|
||||
border: 1px solid #929192;
|
||||
background: transparent;
|
||||
image: none;
|
||||
}
|
||||
|
||||
QWidget#controllerPlayer1,
|
||||
QWidget#controllerPlayer2,
|
||||
QWidget#controllerPlayer3,
|
||||
QWidget#controllerPlayer4,
|
||||
QWidget#controllerPlayer5,
|
||||
QWidget#controllerPlayer6,
|
||||
QWidget#controllerPlayer7,
|
||||
QWidget#controllerPlayer8 {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* touchscreen mapping widget */
|
||||
TouchScreenPreview {
|
||||
qproperty-dotHighlightColor: #3daee9;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 362 B |
|
After Width: | Height: | Size: 362 B |
@@ -2,6 +2,7 @@
|
||||
<qresource prefix="icons/qdarkstyle_midnight_blue">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
@@ -220,6 +221,6 @@
|
||||
<file>rc/window_undock_pressed@2x.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="qdarkstyle_midnight_blue">
|
||||
<file>style.qss</file>
|
||||
<file>style.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -138,8 +138,6 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qstatusbar
|
||||
|
||||
--------------------------------------------------------------------------- */
|
||||
QStatusBar {
|
||||
border: 1px solid #32414B;
|
||||
/* Fixes Spyder #9120, #9121 */
|
||||
background: #32414B;
|
||||
/* Fixes #205, white vertical borders separating items */
|
||||
}
|
||||
@@ -161,6 +159,7 @@ QStatusBar QToolTip {
|
||||
QStatusBar QLabel {
|
||||
/* Fixes Spyder #9120, #9121 */
|
||||
background: transparent;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
/* QCheckBox --------------------------------------------------------------
|
||||
@@ -239,18 +238,16 @@ QGroupBox {
|
||||
font-weight: bold;
|
||||
border: 1px solid #32414B;
|
||||
border-radius: 4px;
|
||||
margin-top: 12px;
|
||||
padding: 4px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
QGroupBox::title {
|
||||
subcontrol-origin: margin;
|
||||
subcontrol-position: top left;
|
||||
left: 3px;
|
||||
padding-left: 3px;
|
||||
padding-right: 5px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 16px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
QGroupBox::indicator {
|
||||
@@ -367,28 +364,19 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenubar
|
||||
--------------------------------------------------------------------------- */
|
||||
QMenuBar {
|
||||
background-color: #32414B;
|
||||
padding: 2px;
|
||||
border: 1px solid #19232D;
|
||||
color: #F0F0F0;
|
||||
}
|
||||
|
||||
QMenuBar:focus {
|
||||
border: 1px solid #148CD2;
|
||||
}
|
||||
|
||||
QMenuBar::item {
|
||||
background: transparent;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
QMenuBar::item:selected {
|
||||
padding: 4px;
|
||||
background: transparent;
|
||||
border: 0px solid #32414B;
|
||||
}
|
||||
|
||||
QMenuBar::item:pressed {
|
||||
padding: 4px;
|
||||
border: 0px solid #32414B;
|
||||
background-color: #148CD2;
|
||||
color: #F0F0F0;
|
||||
@@ -396,6 +384,7 @@ QMenuBar::item:pressed {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
|
||||
/* QMenu ------------------------------------------------------------------
|
||||
|
||||
https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenu
|
||||
@@ -482,7 +471,7 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcombobox
|
||||
|
||||
--------------------------------------------------------------------------- */
|
||||
QAbstractItemView {
|
||||
alternate-background-color: #19232D;
|
||||
alternate-background-color: #1f2933;
|
||||
color: #F0F0F0;
|
||||
border: 1px solid #32414B;
|
||||
border-radius: 4px;
|
||||
@@ -501,13 +490,13 @@ QAbstractScrollArea {
|
||||
background-color: #19232D;
|
||||
border: 1px solid #32414B;
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
/* fix #159 */
|
||||
min-height: 1.25em;
|
||||
/* fix #159 */
|
||||
color: #F0F0F0;
|
||||
}
|
||||
|
||||
|
||||
QAbstractScrollArea:disabled {
|
||||
color: #787878;
|
||||
}
|
||||
@@ -807,20 +796,22 @@ QAbstractSpinBox {
|
||||
}
|
||||
|
||||
QAbstractSpinBox:up-button {
|
||||
background-color: transparent #19232D;
|
||||
background-color: #505F69;
|
||||
subcontrol-origin: border;
|
||||
subcontrol-position: top right;
|
||||
border-left: 1px solid #32414B;
|
||||
border-bottom: 1px solid #32414B;
|
||||
border-top: 1px solid #32414B;
|
||||
border-right: 1px solid #32414B;
|
||||
border-top-right-radius: 4px;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
margin: 1px;
|
||||
margin: 0px;
|
||||
width: 12px;
|
||||
margin-bottom: -1px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
QAbstractSpinBox::up-arrow, QAbstractSpinBox::up-arrow:disabled, QAbstractSpinBox::up-arrow:off {
|
||||
image: url(":/qss_icons/rc/arrow_up_disabled.png");
|
||||
image: url(":/qss_icons/rc/up_arrow.png");
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
@@ -830,20 +821,23 @@ QAbstractSpinBox::up-arrow:hover {
|
||||
}
|
||||
|
||||
QAbstractSpinBox:down-button {
|
||||
background-color: transparent #19232D;
|
||||
background-color: #505F69;
|
||||
subcontrol-origin: border;
|
||||
subcontrol-position: bottom right;
|
||||
border-left: 1px solid #32414B;
|
||||
border-right: 1px solid #32414B;
|
||||
border-bottom: 1px solid #32414B;
|
||||
border-top: 1px solid #32414B;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
margin: 1px;
|
||||
border-bottom-right-radius: 4px;
|
||||
margin: 0px;
|
||||
width: 12px;
|
||||
margin-top: -1px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
QAbstractSpinBox::down-arrow, QAbstractSpinBox::down-arrow:disabled, QAbstractSpinBox::down-arrow:off {
|
||||
image: url(":/qss_icons/rc/arrow_down_disabled.png");
|
||||
image: url(":/qss_icons/rc/down_arrow.png");
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
@@ -1199,6 +1193,7 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcombobox
|
||||
|
||||
--------------------------------------------------------------------------- */
|
||||
QComboBox {
|
||||
background-color: #0f1922;
|
||||
border: 1px solid #32414B;
|
||||
border-radius: 4px;
|
||||
selection-background-color: #1464A0;
|
||||
@@ -1216,7 +1211,7 @@ QComboBox {
|
||||
QComboBox QAbstractItemView {
|
||||
border: 1px solid #32414B;
|
||||
border-radius: 0;
|
||||
background-color: #19232D;
|
||||
background-color: #0f1922;
|
||||
selection-background-color: #1464A0;
|
||||
}
|
||||
|
||||
@@ -1285,7 +1280,12 @@ QComboBox::drop-down {
|
||||
}
|
||||
|
||||
QComboBox::down-arrow {
|
||||
image: url(":/qss_icons/rc/arrow_down_disabled.png");
|
||||
image: url(":/qss_icons/rc/down_arrow.png");
|
||||
background-color: #505F69;
|
||||
padding: 6px 2px;
|
||||
border: 1px solid #32414B;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
@@ -1559,12 +1559,12 @@ QTabBar::tab:right:!selected {
|
||||
QTabBar::tab:top {
|
||||
background-color: #32414B;
|
||||
color: #F0F0F0;
|
||||
min-width: 36px;
|
||||
margin-left: 2px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
min-width: 5px;
|
||||
border-bottom: 3px solid #32414B;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
@@ -1588,16 +1588,16 @@ QTabBar::tab:top:!selected:hover {
|
||||
|
||||
QTabBar::tab:bottom {
|
||||
color: #F0F0F0;
|
||||
min-width: 36px;
|
||||
border-top: 3px solid #32414B;
|
||||
background-color: #32414B;
|
||||
margin-left: 2px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
min-width: 5px;
|
||||
}
|
||||
|
||||
QTabBar::tab:bottom:selected {
|
||||
@@ -1752,21 +1752,6 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qlistview
|
||||
https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtableview
|
||||
|
||||
--------------------------------------------------------------------------- */
|
||||
QTreeView:branch:selected, QTreeView:branch:hover {
|
||||
background: url(":/qss_icons/rc/transparent.png");
|
||||
}
|
||||
|
||||
QTreeView:branch:has-siblings:!adjoins-item {
|
||||
border-image: url(":/qss_icons/rc/branch_line.png") 0;
|
||||
}
|
||||
|
||||
QTreeView:branch:has-siblings:adjoins-item {
|
||||
border-image: url(":/qss_icons/rc/branch_more.png") 0;
|
||||
}
|
||||
|
||||
QTreeView:branch:!has-children:!has-siblings:adjoins-item {
|
||||
border-image: url(":/qss_icons/rc/branch_end.png") 0;
|
||||
}
|
||||
|
||||
QTreeView:branch:has-children:!has-siblings:closed, QTreeView:branch:closed:has-children:has-siblings {
|
||||
border-image: none;
|
||||
@@ -1900,21 +1885,21 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qheaderview
|
||||
|
||||
--------------------------------------------------------------------------- */
|
||||
QHeaderView {
|
||||
background-color: #32414B;
|
||||
border: 0px transparent #32414B;
|
||||
background-color: #19232D;
|
||||
border: 0px transparent #19232D;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
QHeaderView:disabled {
|
||||
background-color: #32414B;
|
||||
border: 1px transparent #32414B;
|
||||
background-color: #19232D;
|
||||
border: 1px transparent #19232D;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
QHeaderView::section {
|
||||
background-color: #32414B;
|
||||
background-color: #19232D;
|
||||
color: #F0F0F0;
|
||||
padding: 2px;
|
||||
border-radius: 0px;
|
||||
@@ -1934,11 +1919,11 @@ QHeaderView::section:checked:disabled {
|
||||
QHeaderView::section::horizontal {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
border-left: 1px solid #19232D;
|
||||
border-left: 1px solid #32414B;
|
||||
}
|
||||
|
||||
QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one {
|
||||
border-left: 1px solid #32414B;
|
||||
border-left: 1px solid #19232D;
|
||||
}
|
||||
|
||||
QHeaderView::section::horizontal:disabled {
|
||||
@@ -1948,7 +1933,7 @@ QHeaderView::section::horizontal:disabled {
|
||||
QHeaderView::section::vertical {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
border-top: 1px solid #19232D;
|
||||
border-top: 1px solid #32414B;
|
||||
}
|
||||
|
||||
QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one {
|
||||
@@ -1962,7 +1947,7 @@ QHeaderView::section::vertical:disabled {
|
||||
QHeaderView::down-arrow {
|
||||
/* Those settings (border/width/height/background-color) solve bug */
|
||||
/* transparent arrow background and size */
|
||||
background-color: #32414B;
|
||||
background-color: #19232D;
|
||||
border: none;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
@@ -1972,7 +1957,7 @@ QHeaderView::down-arrow {
|
||||
}
|
||||
|
||||
QHeaderView::up-arrow {
|
||||
background-color: #32414B;
|
||||
background-color: #19232D;
|
||||
border: none;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
@@ -2172,3 +2157,348 @@ PlotWidget {
|
||||
/* Fix cut labels in plots #134 */
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
|
||||
QPushButton#TogglableStatusBarButton {
|
||||
min-width: 0px;
|
||||
color: #656565;
|
||||
border: 1px solid transparent;
|
||||
background-color: transparent;
|
||||
padding: 0px 3px 0px 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
QPushButton#TogglableStatusBarButton:checked {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
QPushButton#TogglableStatusBarButton:hover {
|
||||
border: 1px solid #76797C;
|
||||
}
|
||||
|
||||
QPushButton#RendererStatusBarButton {
|
||||
min-width: 0px;
|
||||
color: #656565;
|
||||
border: 1px solid transparent;
|
||||
background-color: transparent;
|
||||
padding: 0px 3px 0px 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
QPushButton#RendererStatusBarButton:hover {
|
||||
border: 1px solid #76797C;
|
||||
}
|
||||
|
||||
QPushButton#RendererStatusBarButton:checked {
|
||||
color: #e85c00;
|
||||
}
|
||||
|
||||
QPushButton#RendererStatusBarButton:!checked {
|
||||
color: #00ccdd;
|
||||
}
|
||||
|
||||
QPushButton#buttonRefreshDevices {
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
max-width: 20px;
|
||||
max-height: 20px;
|
||||
padding: 0px 0px;
|
||||
}
|
||||
|
||||
QSpinBox#spinboxLStickRange,
|
||||
QSpinBox#spinboxRStickRange {
|
||||
min-width: 38px;
|
||||
}
|
||||
|
||||
QGroupBox#motionGroup::indicator,
|
||||
QGroupBox#vibrationGroup::indicator {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
QWidget#bottomPerGameInput QGroupBox#motionGroup,
|
||||
QWidget#bottomPerGameInput QGroupBox#vibrationGroup,
|
||||
QWidget#bottomPerGameInput QGroupBox#inputConfigGroup {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
QGroupBox#motionGroup::title,
|
||||
QGroupBox#vibrationGroup::title {
|
||||
spacing: 2px;
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
}
|
||||
|
||||
QListWidget#selectorList {
|
||||
background-color: #0f1922;
|
||||
}
|
||||
|
||||
QSpinBox,
|
||||
QLineEdit,
|
||||
QTreeView#hotkey_list,
|
||||
QScrollArea#scrollArea QTreeView {
|
||||
background-color: #0f1922;
|
||||
}
|
||||
|
||||
QWidget#bottomPerGameInput,
|
||||
QWidget#topControllerApplet,
|
||||
QWidget#bottomControllerApplet,
|
||||
QGroupBox#groupPlayer1Connected:checked,
|
||||
QGroupBox#groupPlayer2Connected:checked,
|
||||
QGroupBox#groupPlayer3Connected:checked,
|
||||
QGroupBox#groupPlayer4Connected:checked,
|
||||
QGroupBox#groupPlayer5Connected:checked,
|
||||
QGroupBox#groupPlayer6Connected:checked,
|
||||
QGroupBox#groupPlayer7Connected:checked,
|
||||
QGroupBox#groupPlayer8Connected:checked {
|
||||
background-color: #0f1922;
|
||||
}
|
||||
|
||||
QWidget#topPerGameInput,
|
||||
QWidget#middleControllerApplet {
|
||||
background-color: #19232d;
|
||||
}
|
||||
|
||||
QWidget#topPerGameInput QComboBox,
|
||||
QWidget#middleControllerApplet QComboBox {
|
||||
padding-right: 2px;
|
||||
width: 127px;
|
||||
}
|
||||
|
||||
QGroupBox#handheldGroup {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
QRadioButton#radioDocked {
|
||||
margin-left: -1px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
QRadioButton#radioDocked::indicator {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
|
||||
QRadioButton#radioUndocked {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
QWidget#connectedControllers {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#playersSupported,
|
||||
QWidget#controllersSupported,
|
||||
QWidget#controllerSupported1,
|
||||
QWidget#controllerSupported2,
|
||||
QWidget#controllerSupported3,
|
||||
QWidget#controllerSupported4,
|
||||
QWidget#controllerSupported5,
|
||||
QWidget#controllerSupported6 {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QGroupBox#groupPlayer1Connected,
|
||||
QGroupBox#groupPlayer2Connected,
|
||||
QGroupBox#groupPlayer3Connected,
|
||||
QGroupBox#groupPlayer4Connected,
|
||||
QGroupBox#groupPlayer5Connected,
|
||||
QGroupBox#groupPlayer6Connected,
|
||||
QGroupBox#groupPlayer7Connected,
|
||||
QGroupBox#groupPlayer8Connected {
|
||||
border: 1px solid #76797c;
|
||||
border-radius: 3px;
|
||||
padding: 0px;
|
||||
min-height: 98px;
|
||||
max-height: 98px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
|
||||
QGroupBox#groupPlayer1Connected:unchecked,
|
||||
QGroupBox#groupPlayer2Connected:unchecked,
|
||||
QGroupBox#groupPlayer3Connected:unchecked,
|
||||
QGroupBox#groupPlayer4Connected:unchecked,
|
||||
QGroupBox#groupPlayer5Connected:unchecked,
|
||||
QGroupBox#groupPlayer6Connected:unchecked,
|
||||
QGroupBox#groupPlayer7Connected:unchecked,
|
||||
QGroupBox#groupPlayer8Connected:unchecked {
|
||||
border: 1px solid #32414b;
|
||||
}
|
||||
|
||||
QGroupBox#groupPlayer1Connected::title,
|
||||
QGroupBox#groupPlayer2Connected::title,
|
||||
QGroupBox#groupPlayer3Connected::title,
|
||||
QGroupBox#groupPlayer4Connected::title,
|
||||
QGroupBox#groupPlayer5Connected::title,
|
||||
QGroupBox#groupPlayer6Connected::title,
|
||||
QGroupBox#groupPlayer7Connected::title,
|
||||
QGroupBox#groupPlayer8Connected::title {
|
||||
subcontrol-origin: margin;
|
||||
subcontrol-position: top left;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-top: 1px;
|
||||
margin-left: -2px;
|
||||
margin-right: -4px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
QCheckBox#checkboxPlayer1Connected,
|
||||
QCheckBox#checkboxPlayer2Connected,
|
||||
QCheckBox#checkboxPlayer3Connected,
|
||||
QCheckBox#checkboxPlayer4Connected,
|
||||
QCheckBox#checkboxPlayer5Connected,
|
||||
QCheckBox#checkboxPlayer6Connected,
|
||||
QCheckBox#checkboxPlayer7Connected,
|
||||
QCheckBox#checkboxPlayer8Connected {
|
||||
spacing: 0px;
|
||||
}
|
||||
|
||||
QWidget#connectedControllers QLabel {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs,
|
||||
QWidget#Player2LEDs,
|
||||
QWidget#Player3LEDs,
|
||||
QWidget#Player4LEDs,
|
||||
QWidget#Player5LEDs,
|
||||
QWidget#Player6LEDs,
|
||||
QWidget#Player7LEDs,
|
||||
QWidget#Player8LEDs {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox,
|
||||
QWidget#Player2LEDs QCheckBox,
|
||||
QWidget#Player3LEDs QCheckBox,
|
||||
QWidget#Player4LEDs QCheckBox,
|
||||
QWidget#Player5LEDs QCheckBox,
|
||||
QWidget#Player6LEDs QCheckBox,
|
||||
QWidget#Player7LEDs QCheckBox,
|
||||
QWidget#Player8LEDs QCheckBox,
|
||||
QCheckBox#checkboxPlayer1Connected,
|
||||
QCheckBox#checkboxPlayer2Connected,
|
||||
QCheckBox#checkboxPlayer3Connected,
|
||||
QCheckBox#checkboxPlayer4Connected,
|
||||
QCheckBox#checkboxPlayer5Connected,
|
||||
QCheckBox#checkboxPlayer6Connected,
|
||||
QCheckBox#checkboxPlayer7Connected,
|
||||
QCheckBox#checkboxPlayer8Connected {
|
||||
spacing: 0px;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox::indicator,
|
||||
QWidget#Player2LEDs QCheckBox::indicator,
|
||||
QWidget#Player3LEDs QCheckBox::indicator,
|
||||
QWidget#Player4LEDs QCheckBox::indicator,
|
||||
QWidget#Player5LEDs QCheckBox::indicator,
|
||||
QWidget#Player6LEDs QCheckBox::indicator,
|
||||
QWidget#Player7LEDs QCheckBox::indicator,
|
||||
QWidget#Player8LEDs QCheckBox::indicator {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
|
||||
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
QCheckBox#checkboxPlayer1Connected::indicator,
|
||||
QCheckBox#checkboxPlayer2Connected::indicator,
|
||||
QCheckBox#checkboxPlayer3Connected::indicator,
|
||||
QCheckBox#checkboxPlayer4Connected::indicator,
|
||||
QCheckBox#checkboxPlayer5Connected::indicator,
|
||||
QCheckBox#checkboxPlayer6Connected::indicator,
|
||||
QCheckBox#checkboxPlayer7Connected::indicator,
|
||||
QCheckBox#checkboxPlayer8Connected::indicator {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player2LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player3LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player4LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player5LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player6LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player7LEDs QCheckBox::indicator:checked,
|
||||
QWidget#Player8LEDs QCheckBox::indicator:checked,
|
||||
QGroupBox#groupPlayer1Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer2Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer3Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer4Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer5Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer6Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer7Connected::indicator:checked,
|
||||
QGroupBox#groupPlayer8Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer1Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer2Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer3Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer4Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer5Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer6Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer7Connected::indicator:checked,
|
||||
QCheckBox#checkboxPlayer8Connected::indicator:checked,
|
||||
QGroupBox#groupConnectedController::indicator:checked {
|
||||
border-radius: 2px;
|
||||
border: 1px solid #929192;
|
||||
background: #39ff14;
|
||||
image: none;
|
||||
}
|
||||
|
||||
QWidget#Player1LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player2LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player3LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player4LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player5LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player6LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player7LEDs QCheckBox::indicator:unchecked,
|
||||
QWidget#Player8LEDs QCheckBox::indicator:unchecked,
|
||||
QGroupBox#groupPlayer1Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer2Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer3Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer4Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer5Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer6Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer7Connected::indicator:unchecked,
|
||||
QGroupBox#groupPlayer8Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer4Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer5Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer6Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer7Connected::indicator:unchecked,
|
||||
QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
|
||||
QGroupBox#groupConnectedController::indicator:unchecked {
|
||||
border-radius: 2px;
|
||||
border: 1px solid #929192;
|
||||
background: #19232d;
|
||||
image: none;
|
||||
}
|
||||
|
||||
QWidget#controllerPlayer1,
|
||||
QWidget#controllerPlayer2,
|
||||
QWidget#controllerPlayer3,
|
||||
QWidget#controllerPlayer4,
|
||||
QWidget#controllerPlayer5,
|
||||
QWidget#controllerPlayer6,
|
||||
QWidget#controllerPlayer7,
|
||||
QWidget#controllerPlayer8 {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
add_library(usb STATIC EXCLUDE_FROM_ALL
|
||||
libusb/libusb/core.c
|
||||
libusb/libusb/core.c
|
||||
libusb/libusb/descriptor.c
|
||||
libusb/libusb/hotplug.c
|
||||
libusb/libusb/io.c
|
||||
libusb/libusb/strerror.c
|
||||
libusb/libusb/sync.c
|
||||
)
|
||||
set_target_properties(usb PROPERTIES VERSION 1.0.23)
|
||||
if(WIN32)
|
||||
target_include_directories(usb
|
||||
BEFORE
|
||||
PUBLIC
|
||||
libusb/libusb
|
||||
|
||||
PRIVATE
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
|
||||
if (NOT MINGW)
|
||||
target_include_directories(usb BEFORE PRIVATE libusb/msvc)
|
||||
endif()
|
||||
|
||||
# Works around other libraries providing their own definition of USB GUIDs (e.g. SDL2)
|
||||
target_compile_definitions(usb PRIVATE "-DGUID_DEVINTERFACE_USB_DEVICE=(GUID){ 0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED}}")
|
||||
else()
|
||||
target_include_directories(usb
|
||||
# turns out other projects also have "config.h", so make sure the
|
||||
# LibUSB one comes first
|
||||
BEFORE
|
||||
|
||||
PUBLIC
|
||||
libusb/libusb
|
||||
|
||||
PRIVATE
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR CYGWIN)
|
||||
target_sources(usb PRIVATE
|
||||
libusb/libusb/os/threads_windows.c
|
||||
libusb/libusb/os/windows_winusb.c
|
||||
libusb/libusb/os/windows_usbdk.c
|
||||
libusb/libusb/os/windows_nt_common.c
|
||||
)
|
||||
set(OS_WINDOWS TRUE)
|
||||
elseif(APPLE)
|
||||
target_sources(usb PRIVATE
|
||||
libusb/libusb/os/darwin_usb.c
|
||||
)
|
||||
find_library(COREFOUNDATION_LIBRARY CoreFoundation)
|
||||
find_library(IOKIT_LIBRARY IOKit)
|
||||
find_library(OBJC_LIBRARY objc)
|
||||
target_link_libraries(usb PRIVATE
|
||||
${COREFOUNDATION_LIBRARY}
|
||||
${IOKIT_LIBRARY}
|
||||
${OBJC_LIBRARY}
|
||||
)
|
||||
set(OS_DARWIN TRUE)
|
||||
elseif(ANDROID)
|
||||
target_sources(usb PRIVATE
|
||||
libusb/libusb/os/linux_usbfs.c
|
||||
libusb/libusb/os/linux_netlink.c
|
||||
)
|
||||
find_library(LOG_LIBRARY log)
|
||||
target_link_libraries(usb PRIVATE ${LOG_LIBRARY})
|
||||
set(OS_LINUX TRUE)
|
||||
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
target_sources(usb PRIVATE
|
||||
libusb/libusb/os/linux_usbfs.c
|
||||
)
|
||||
find_package(Libudev)
|
||||
if(LIBUDEV_FOUND)
|
||||
target_sources(usb PRIVATE
|
||||
libusb/libusb/os/linux_udev.c
|
||||
)
|
||||
target_link_libraries(usb PRIVATE "${LIBUDEV_LIBRARIES}")
|
||||
target_include_directories(usb PRIVATE "${LIBUDEV_INCLUDE_DIR}")
|
||||
set(HAVE_LIBUDEV TRUE)
|
||||
set(USE_UDEV TRUE)
|
||||
else()
|
||||
target_sources(usb PRIVATE
|
||||
libusb/libusb/os/linux_netlink.c
|
||||
)
|
||||
endif()
|
||||
set(OS_LINUX TRUE)
|
||||
elseif(${CMAKE_SYSTEM_NAME} MATCHES "NetBSD")
|
||||
target_sources(usb PRIVATE
|
||||
libusb/libusb/os/netbsd_usb.c
|
||||
)
|
||||
set(OS_NETBSD TRUE)
|
||||
elseif(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
|
||||
target_sources(usb PRIVATE
|
||||
libusb/libusb/os/openbsd_usb.c
|
||||
)
|
||||
set(OS_OPENBSD TRUE)
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
target_sources(usb PRIVATE
|
||||
libusb/libusb/os/poll_posix.c
|
||||
libusb/libusb/os/threads_posix.c
|
||||
)
|
||||
find_package(Threads REQUIRED)
|
||||
if(THREADS_HAVE_PTHREAD_ARG)
|
||||
target_compile_options(usb PUBLIC "-pthread")
|
||||
endif()
|
||||
if(CMAKE_THREAD_LIBS_INIT)
|
||||
target_link_libraries(usb PRIVATE "${CMAKE_THREAD_LIBS_INIT}")
|
||||
endif()
|
||||
set(THREADS_POSIX TRUE)
|
||||
elseif(WIN32)
|
||||
target_sources(usb PRIVATE
|
||||
libusb/libusb/os/poll_windows.c
|
||||
libusb/libusb/os/threads_windows.c
|
||||
)
|
||||
endif()
|
||||
|
||||
include(CheckFunctionExists)
|
||||
include(CheckIncludeFiles)
|
||||
include(CheckTypeSize)
|
||||
check_include_files(asm/types.h HAVE_ASM_TYPES_H)
|
||||
check_function_exists(gettimeofday HAVE_GETTIMEOFDAY)
|
||||
check_include_files(linux/filter.h HAVE_LINUX_FILTER_H)
|
||||
check_include_files(linux/netlink.h HAVE_LINUX_NETLINK_H)
|
||||
check_include_files(poll.h HAVE_POLL_H)
|
||||
check_include_files(signal.h HAVE_SIGNAL_H)
|
||||
check_include_files(strings.h HAVE_STRINGS_H)
|
||||
check_type_size("struct timespec" STRUCT_TIMESPEC)
|
||||
check_function_exists(syslog HAVE_SYSLOG_FUNC)
|
||||
check_include_files(syslog.h HAVE_SYSLOG_H)
|
||||
check_include_files(sys/socket.h HAVE_SYS_SOCKET_H)
|
||||
check_include_files(sys/time.h HAVE_SYS_TIME_H)
|
||||
check_include_files(sys/types.h HAVE_SYS_TYPES_H)
|
||||
|
||||
set(CMAKE_EXTRA_INCLUDE_FILES poll.h)
|
||||
check_type_size("nfds_t" nfds_t)
|
||||
unset(CMAKE_EXTRA_INCLUDE_FILES)
|
||||
if(HAVE_NFDS_T)
|
||||
set(POLL_NFDS_TYPE "nfds_t")
|
||||
else()
|
||||
set(POLL_NFDS_TYPE "unsigned int")
|
||||
endif()
|
||||
|
||||
check_include_files(sys/timerfd.h USBI_TIMERFD_AVAILABLE)
|
||||
|
||||
|
||||
configure_file(config.h.in config.h)
|
||||
@@ -0,0 +1,90 @@
|
||||
/* Default visibility */
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define DEFAULT_VISIBILITY __attribute__((visibility("default")))
|
||||
#elif defined(_MSC_VER)
|
||||
#define DEFAULT_VISIBILITY __declspec(dllexport)
|
||||
#endif
|
||||
|
||||
/* Start with debug message logging enabled */
|
||||
#undef ENABLE_DEBUG_LOGGING
|
||||
|
||||
/* Message logging */
|
||||
#undef ENABLE_LOGGING
|
||||
|
||||
/* Define to 1 if you have the <asm/types.h> header file. */
|
||||
#cmakedefine HAVE_ASM_TYPES_H 1
|
||||
|
||||
/* Define to 1 if you have the `gettimeofday' function. */
|
||||
#cmakedefine HAVE_GETTIMEOFDAY 1
|
||||
|
||||
/* Define to 1 if you have the `udev' library (-ludev). */
|
||||
#cmakedefine HAVE_LIBUDEV 1
|
||||
|
||||
/* Define to 1 if you have the <linux/filter.h> header file. */
|
||||
#cmakedefine HAVE_LINUX_FILTER_H 1
|
||||
|
||||
/* Define to 1 if you have the <linux/netlink.h> header file. */
|
||||
#cmakedefine HAVE_LINUX_NETLINK_H 1
|
||||
|
||||
/* Define to 1 if you have the <poll.h> header file. */
|
||||
#cmakedefine HAVE_POLL_H 1
|
||||
|
||||
/* Define to 1 if you have the <signal.h> header file. */
|
||||
#cmakedefine HAVE_SIGNAL_H 1
|
||||
|
||||
/* Define to 1 if you have the <strings.h> header file. */
|
||||
#cmakedefine HAVE_STRINGS_H 1
|
||||
|
||||
/* Define to 1 if the system has the type `struct timespec'. */
|
||||
#cmakedefine HAVE_STRUCT_TIMESPEC 1
|
||||
|
||||
/* syslog() function available */
|
||||
#cmakedefine HAVE_SYSLOG_FUNC 1
|
||||
|
||||
/* Define to 1 if you have the <syslog.h> header file. */
|
||||
#cmakedefine HAVE_SYSLOG_H 1
|
||||
|
||||
/* Define to 1 if you have the <sys/socket.h> header file. */
|
||||
#cmakedefine HAVE_SYS_SOCKET_H 1
|
||||
|
||||
/* Define to 1 if you have the <sys/time.h> header file. */
|
||||
#cmakedefine HAVE_SYS_TIME_H 1
|
||||
|
||||
/* Define to 1 if you have the <sys/types.h> header file. */
|
||||
#cmakedefine HAVE_SYS_TYPES_H 1
|
||||
|
||||
/* Darwin backend */
|
||||
#cmakedefine OS_DARWIN 1
|
||||
|
||||
/* Linux backend */
|
||||
#cmakedefine OS_LINUX 1
|
||||
|
||||
/* NetBSD backend */
|
||||
#cmakedefine OS_NETBSD 1
|
||||
|
||||
/* OpenBSD backend */
|
||||
#cmakedefine OS_OPENBSD 1
|
||||
|
||||
/* Windows backend */
|
||||
#cmakedefine OS_WINDOWS 1
|
||||
|
||||
/* type of second poll() argument */
|
||||
#define POLL_NFDS_TYPE @POLL_NFDS_TYPE@
|
||||
|
||||
/* Use POSIX Threads */
|
||||
#cmakedefine THREADS_POSIX
|
||||
|
||||
/* timerfd headers available */
|
||||
#cmakedefine USBI_TIMERFD_AVAILABLE 1
|
||||
|
||||
/* Enable output to system log */
|
||||
#define USE_SYSTEM_LOGGING_FACILITY 1
|
||||
|
||||
/* Use udev for device enumeration/hotplug */
|
||||
#cmakedefine USE_UDEV 1
|
||||
|
||||
/* Use GNU extensions */
|
||||
#define _GNU_SOURCE
|
||||
|
||||
/* Oldest Windows version supported */
|
||||
#define WINVER 0x0501
|
||||
@@ -1037,7 +1037,7 @@ static void MicroProfileCreateThreadLogKey()
|
||||
#else
|
||||
MP_THREAD_LOCAL MicroProfileThreadLog* g_MicroProfileThreadLog = 0;
|
||||
#endif
|
||||
static bool g_bUseLock = false; /// This is used because windows does not support using mutexes under dll init(which is where global initialization is handled)
|
||||
static std::atomic<bool> g_bUseLock{false}; /// This is used because windows does not support using mutexes under dll init(which is where global initialization is handled)
|
||||
|
||||
|
||||
MICROPROFILE_DEFINE(g_MicroProfileFlip, "MicroProfile", "MicroProfileFlip", 0x3355ee);
|
||||
|
||||
@@ -12,16 +12,32 @@ add_library(audio_core STATIC
|
||||
buffer.h
|
||||
codec.cpp
|
||||
codec.h
|
||||
command_generator.cpp
|
||||
command_generator.h
|
||||
common.h
|
||||
effect_context.cpp
|
||||
effect_context.h
|
||||
info_updater.cpp
|
||||
info_updater.h
|
||||
memory_pool.cpp
|
||||
memory_pool.h
|
||||
mix_context.cpp
|
||||
mix_context.h
|
||||
null_sink.h
|
||||
sink.h
|
||||
sink_context.cpp
|
||||
sink_context.h
|
||||
sink_details.cpp
|
||||
sink_details.h
|
||||
sink_stream.h
|
||||
splitter_context.cpp
|
||||
splitter_context.h
|
||||
stream.cpp
|
||||
stream.h
|
||||
time_stretch.cpp
|
||||
time_stretch.h
|
||||
voice_context.cpp
|
||||
voice_context.h
|
||||
|
||||
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
|
||||
)
|
||||
|
||||
@@ -197,4 +197,36 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
|
||||
return output;
|
||||
}
|
||||
|
||||
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
|
||||
const std::array<s16, 512>& lut = [pitch] {
|
||||
if (pitch > 0xaaaa) {
|
||||
return curve_lut0;
|
||||
}
|
||||
if (pitch <= 0x8000) {
|
||||
return curve_lut1;
|
||||
}
|
||||
return curve_lut2;
|
||||
}();
|
||||
|
||||
std::size_t index{};
|
||||
|
||||
for (std::size_t i = 0; i < sample_count; i++) {
|
||||
const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
|
||||
const auto l0 = lut[lut_index + 0];
|
||||
const auto l1 = lut[lut_index + 1];
|
||||
const auto l2 = lut[lut_index + 2];
|
||||
const auto l3 = lut[lut_index + 3];
|
||||
|
||||
const auto s0 = static_cast<s32>(input[index]);
|
||||
const auto s1 = static_cast<s32>(input[index + 1]);
|
||||
const auto s2 = static_cast<s32>(input[index + 2]);
|
||||
const auto s3 = static_cast<s32>(input[index + 3]);
|
||||
|
||||
output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
|
||||
fraction += pitch;
|
||||
index += (fraction >> 15);
|
||||
fraction &= 0x7fff;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -38,4 +38,7 @@ inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16>
|
||||
return Interpolate(state, std::move(input), ratio);
|
||||
}
|
||||
|
||||
/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
|
||||
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -2,95 +2,49 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <vector>
|
||||
#include "audio_core/algorithm/interpolate.h"
|
||||
#include "audio_core/audio_out.h"
|
||||
#include "audio_core/audio_renderer.h"
|
||||
#include "audio_core/codec.h"
|
||||
#include "audio_core/common.h"
|
||||
#include "audio_core/info_updater.h"
|
||||
#include "audio_core/voice_context.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
constexpr u32 STREAM_SAMPLE_RATE{48000};
|
||||
constexpr u32 STREAM_NUM_CHANNELS{2};
|
||||
using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>;
|
||||
class AudioRenderer::VoiceState {
|
||||
public:
|
||||
bool IsPlaying() const {
|
||||
return is_in_use && info.play_state == PlayState::Started;
|
||||
}
|
||||
|
||||
const VoiceOutStatus& GetOutStatus() const {
|
||||
return out_status;
|
||||
}
|
||||
|
||||
const VoiceInfo& GetInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
VoiceInfo& GetInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
void SetWaveIndex(std::size_t index);
|
||||
std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory,
|
||||
const VoiceChannelHolder& voice_resources);
|
||||
void UpdateState();
|
||||
void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources);
|
||||
|
||||
private:
|
||||
bool is_in_use{};
|
||||
bool is_refresh_pending{};
|
||||
std::size_t wave_index{};
|
||||
std::size_t offset{};
|
||||
Codec::ADPCMState adpcm_state{};
|
||||
InterpolationState interp_state{};
|
||||
std::vector<s16> samples;
|
||||
VoiceOutStatus out_status{};
|
||||
VoiceInfo info{};
|
||||
};
|
||||
|
||||
class AudioRenderer::EffectState {
|
||||
public:
|
||||
const EffectOutStatus& GetOutStatus() const {
|
||||
return out_status;
|
||||
}
|
||||
|
||||
const EffectInStatus& GetInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
EffectInStatus& GetInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
void UpdateState(Core::Memory::Memory& memory);
|
||||
|
||||
private:
|
||||
EffectOutStatus out_status{};
|
||||
EffectInStatus info{};
|
||||
};
|
||||
|
||||
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
|
||||
AudioRendererParameter params,
|
||||
AudioCommon::AudioRendererParameter params,
|
||||
std::shared_ptr<Kernel::WritableEvent> buffer_event,
|
||||
std::size_t instance_number)
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
|
||||
voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} {
|
||||
: worker_params{params}, buffer_event{buffer_event},
|
||||
memory_pool_info(params.effect_count + params.voice_count * 4),
|
||||
voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
|
||||
sink_context(params.sink_count), splitter_context(),
|
||||
voices(params.voice_count), memory{memory_},
|
||||
command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
|
||||
memory),
|
||||
temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) {
|
||||
behavior_info.SetUserRevision(params.revision);
|
||||
splitter_context.Initialize(behavior_info, params.splitter_count,
|
||||
params.num_splitter_send_channels);
|
||||
mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
|
||||
audio_out = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS,
|
||||
fmt::format("AudioRenderer-Instance{}", instance_number),
|
||||
[=]() { buffer_event->Signal(); });
|
||||
stream =
|
||||
audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
|
||||
fmt::format("AudioRenderer-Instance{}", instance_number),
|
||||
[=]() { buffer_event->Signal(); });
|
||||
audio_out->StartStream(stream);
|
||||
|
||||
QueueMixedBuffer(0);
|
||||
QueueMixedBuffer(1);
|
||||
QueueMixedBuffer(2);
|
||||
QueueMixedBuffer(3);
|
||||
}
|
||||
|
||||
AudioRenderer::~AudioRenderer() = default;
|
||||
@@ -111,355 +65,200 @@ Stream::State AudioRenderer::GetStreamState() const {
|
||||
return stream->GetState();
|
||||
}
|
||||
|
||||
ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
|
||||
// Copy UpdateDataHeader struct
|
||||
UpdateDataHeader config{};
|
||||
std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
|
||||
u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
|
||||
|
||||
if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) {
|
||||
LOG_ERROR(Audio, "Failed to update behavior info input parameters");
|
||||
return Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
// Copy MemoryPoolInfo structs
|
||||
std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
|
||||
std::memcpy(mem_pool_info.data(),
|
||||
input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
|
||||
memory_pool_count * sizeof(MemoryPoolInfo));
|
||||
|
||||
// Copy voice resources
|
||||
const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size};
|
||||
std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset,
|
||||
sizeof(VoiceResourceInformation) * voice_resources.size());
|
||||
|
||||
// Copy VoiceInfo structs
|
||||
std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size + config.voice_resource_size};
|
||||
for (auto& voice : voices) {
|
||||
std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
|
||||
voice_offset += sizeof(VoiceInfo);
|
||||
}
|
||||
|
||||
std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size + config.voice_resource_size +
|
||||
config.voices_size};
|
||||
for (auto& effect : effects) {
|
||||
std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
|
||||
effect_offset += sizeof(EffectInStatus);
|
||||
}
|
||||
|
||||
// Update memory pool state
|
||||
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
|
||||
for (std::size_t index = 0; index < memory_pool.size(); ++index) {
|
||||
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Attached;
|
||||
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Detached;
|
||||
}
|
||||
}
|
||||
|
||||
// Update voices
|
||||
for (auto& voice : voices) {
|
||||
voice.UpdateState();
|
||||
if (!voice.GetInfo().is_in_use) {
|
||||
continue;
|
||||
}
|
||||
if (voice.GetInfo().is_new) {
|
||||
voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& effect : effects) {
|
||||
effect.UpdateState(memory);
|
||||
}
|
||||
|
||||
// Release previous buffers and queue next ones for playback
|
||||
ReleaseAndQueueBuffers();
|
||||
|
||||
// Copy output header
|
||||
UpdateDataHeader response_data{worker_params};
|
||||
if (behavior_info.IsElapsedFrameCountSupported()) {
|
||||
response_data.render_info = sizeof(RendererInfo);
|
||||
response_data.total_size += sizeof(RendererInfo);
|
||||
}
|
||||
|
||||
std::vector<u8> output_params(response_data.total_size);
|
||||
std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
|
||||
|
||||
// Copy output memory pool entries
|
||||
std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
|
||||
response_data.memory_pools_size);
|
||||
|
||||
// Copy output voice status
|
||||
std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
|
||||
for (const auto& voice : voices) {
|
||||
std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
|
||||
sizeof(VoiceOutStatus));
|
||||
voice_out_status_offset += sizeof(VoiceOutStatus);
|
||||
}
|
||||
|
||||
std::size_t effect_out_status_offset{
|
||||
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
|
||||
response_data.voice_resource_size};
|
||||
for (const auto& effect : effects) {
|
||||
std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
|
||||
sizeof(EffectOutStatus));
|
||||
effect_out_status_offset += sizeof(EffectOutStatus);
|
||||
}
|
||||
|
||||
// Update behavior info output
|
||||
const std::size_t behavior_out_status_offset{
|
||||
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
|
||||
response_data.effects_size + response_data.sinks_size +
|
||||
response_data.performance_manager_size};
|
||||
|
||||
if (!behavior_info.UpdateOutput(output_params, behavior_out_status_offset)) {
|
||||
LOG_ERROR(Audio, "Failed to update behavior info output parameters");
|
||||
return Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (behavior_info.IsElapsedFrameCountSupported()) {
|
||||
const std::size_t renderer_info_offset{
|
||||
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
|
||||
response_data.effects_size + response_data.sinks_size +
|
||||
response_data.performance_manager_size + response_data.behavior_size};
|
||||
RendererInfo renderer_info{};
|
||||
renderer_info.elasped_frame_count = elapsed_frame_count;
|
||||
std::memcpy(output_params.data() + renderer_info_offset, &renderer_info,
|
||||
sizeof(RendererInfo));
|
||||
}
|
||||
|
||||
return MakeResult(output_params);
|
||||
}
|
||||
|
||||
void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) {
|
||||
wave_index = index & 3;
|
||||
is_refresh_pending = true;
|
||||
}
|
||||
|
||||
std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(
|
||||
std::size_t sample_count, Core::Memory::Memory& memory,
|
||||
const VoiceChannelHolder& voice_resources) {
|
||||
if (!IsPlaying()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (is_refresh_pending) {
|
||||
RefreshBuffer(memory, voice_resources);
|
||||
}
|
||||
|
||||
const std::size_t max_size{samples.size() - offset};
|
||||
const std::size_t dequeue_offset{offset};
|
||||
std::size_t size{sample_count * STREAM_NUM_CHANNELS};
|
||||
if (size > max_size) {
|
||||
size = max_size;
|
||||
}
|
||||
|
||||
out_status.played_sample_count += size / STREAM_NUM_CHANNELS;
|
||||
offset += size;
|
||||
|
||||
const auto& wave_buffer{info.wave_buffer[wave_index]};
|
||||
if (offset == samples.size()) {
|
||||
offset = 0;
|
||||
|
||||
if (!wave_buffer.is_looping && wave_buffer.buffer_sz) {
|
||||
SetWaveIndex(wave_index + 1);
|
||||
}
|
||||
|
||||
if (wave_buffer.buffer_sz) {
|
||||
out_status.wave_buffer_consumed++;
|
||||
}
|
||||
|
||||
if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) {
|
||||
info.play_state = PlayState::Paused;
|
||||
}
|
||||
}
|
||||
|
||||
return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size};
|
||||
}
|
||||
|
||||
void AudioRenderer::VoiceState::UpdateState() {
|
||||
if (is_in_use && !info.is_in_use) {
|
||||
// No longer in use, reset state
|
||||
is_refresh_pending = true;
|
||||
wave_index = 0;
|
||||
offset = 0;
|
||||
out_status = {};
|
||||
}
|
||||
is_in_use = info.is_in_use;
|
||||
}
|
||||
|
||||
void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory,
|
||||
const VoiceChannelHolder& voice_resources) {
|
||||
const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr;
|
||||
const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz;
|
||||
std::vector<s16> new_samples(wave_buffer_size / sizeof(s16));
|
||||
memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size);
|
||||
|
||||
switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
|
||||
case Codec::PcmFormat::Int16: {
|
||||
// PCM16 is played as-is
|
||||
break;
|
||||
}
|
||||
case Codec::PcmFormat::Adpcm: {
|
||||
// Decode ADPCM to PCM16
|
||||
Codec::ADPCM_Coeff coeffs;
|
||||
memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
|
||||
new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
|
||||
new_samples.size() * sizeof(s16), coeffs, adpcm_state);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (info.channel_count) {
|
||||
case 1: {
|
||||
// 1 channel is upsampled to 2 channel
|
||||
samples.resize(new_samples.size() * 2);
|
||||
|
||||
for (std::size_t index = 0; index < new_samples.size(); ++index) {
|
||||
auto sample = static_cast<float>(new_samples[index]);
|
||||
if (voice_resources[0]->in_use) {
|
||||
sample *= voice_resources[0]->mix_volumes[0];
|
||||
}
|
||||
|
||||
samples[index * 2] = static_cast<s16>(sample * info.volume);
|
||||
samples[index * 2 + 1] = static_cast<s16>(sample * info.volume);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// 2 channel is played as is
|
||||
samples = std::move(new_samples);
|
||||
const std::size_t sample_count = (samples.size() / 2);
|
||||
for (std::size_t index = 0; index < sample_count; ++index) {
|
||||
const std::size_t index_l = index * 2;
|
||||
const std::size_t index_r = index * 2 + 1;
|
||||
|
||||
auto sample_l = static_cast<float>(samples[index_l]);
|
||||
auto sample_r = static_cast<float>(samples[index_r]);
|
||||
|
||||
if (voice_resources[0]->in_use) {
|
||||
sample_l *= voice_resources[0]->mix_volumes[0];
|
||||
}
|
||||
|
||||
if (voice_resources[1]->in_use) {
|
||||
sample_r *= voice_resources[1]->mix_volumes[1];
|
||||
}
|
||||
|
||||
samples[index_l] = static_cast<s16>(sample_l * info.volume);
|
||||
samples[index_r] = static_cast<s16>(sample_r * info.volume);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
samples.resize((new_samples.size() / 6) * 2);
|
||||
const std::size_t sample_count = samples.size() / 2;
|
||||
|
||||
for (std::size_t index = 0; index < sample_count; ++index) {
|
||||
auto FL = static_cast<float>(new_samples[index * 6]);
|
||||
auto FR = static_cast<float>(new_samples[index * 6 + 1]);
|
||||
auto FC = static_cast<float>(new_samples[index * 6 + 2]);
|
||||
auto BL = static_cast<float>(new_samples[index * 6 + 4]);
|
||||
auto BR = static_cast<float>(new_samples[index * 6 + 5]);
|
||||
|
||||
if (voice_resources[0]->in_use) {
|
||||
FL *= voice_resources[0]->mix_volumes[0];
|
||||
}
|
||||
if (voice_resources[1]->in_use) {
|
||||
FR *= voice_resources[1]->mix_volumes[1];
|
||||
}
|
||||
if (voice_resources[2]->in_use) {
|
||||
FC *= voice_resources[2]->mix_volumes[2];
|
||||
}
|
||||
if (voice_resources[4]->in_use) {
|
||||
BL *= voice_resources[4]->mix_volumes[4];
|
||||
}
|
||||
if (voice_resources[5]->in_use) {
|
||||
BR *= voice_resources[5]->mix_volumes[5];
|
||||
}
|
||||
|
||||
samples[index * 2] =
|
||||
static_cast<s16>((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume);
|
||||
samples[index * 2 + 1] =
|
||||
static_cast<s16>((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count);
|
||||
break;
|
||||
}
|
||||
|
||||
// Only interpolate when necessary, expensive.
|
||||
if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) {
|
||||
samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate,
|
||||
STREAM_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
is_refresh_pending = false;
|
||||
}
|
||||
|
||||
void AudioRenderer::EffectState::UpdateState(Core::Memory::Memory& memory) {
|
||||
if (info.is_new) {
|
||||
out_status.state = EffectStatus::New;
|
||||
} else {
|
||||
if (info.type == Effect::Aux) {
|
||||
ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0,
|
||||
"Aux buffers tried to update");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr s16 ClampToS16(s32 value) {
|
||||
return static_cast<s16>(std::clamp(value, -32768, 32767));
|
||||
}
|
||||
|
||||
ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
|
||||
std::vector<u8>& output_params) {
|
||||
|
||||
InfoUpdater info_updater{input_params, output_params, behavior_info};
|
||||
|
||||
if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
|
||||
LOG_ERROR(Audio, "Failed to update behavior info input parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
|
||||
LOG_ERROR(Audio, "Failed to update memory pool parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
|
||||
LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
|
||||
LOG_ERROR(Audio, "Failed to update voice parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
// TODO(ogniK): Deal with stopped audio renderer but updates still taking place
|
||||
if (!info_updater.UpdateEffects(effect_context, true)) {
|
||||
LOG_ERROR(Audio, "Failed to update effect parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (behavior_info.IsSplitterSupported()) {
|
||||
if (!info_updater.UpdateSplitterInfo(splitter_context)) {
|
||||
LOG_ERROR(Audio, "Failed to update splitter parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
}
|
||||
|
||||
auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
|
||||
splitter_context, effect_context);
|
||||
|
||||
if (mix_result.IsError()) {
|
||||
LOG_ERROR(Audio, "Failed to update mix parameters");
|
||||
return mix_result;
|
||||
}
|
||||
|
||||
// TODO(ogniK): Sinks
|
||||
if (!info_updater.UpdateSinks(sink_context)) {
|
||||
LOG_ERROR(Audio, "Failed to update sink parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
// TODO(ogniK): Performance buffer
|
||||
if (!info_updater.UpdatePerformanceBuffer()) {
|
||||
LOG_ERROR(Audio, "Failed to update performance buffer parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (!info_updater.UpdateErrorInfo(behavior_info)) {
|
||||
LOG_ERROR(Audio, "Failed to update error info");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (behavior_info.IsElapsedFrameCountSupported()) {
|
||||
if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
|
||||
LOG_ERROR(Audio, "Failed to update renderer info");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
}
|
||||
// TODO(ogniK): Statistics
|
||||
|
||||
if (!info_updater.WriteOutputHeader()) {
|
||||
LOG_ERROR(Audio, "Failed to write output header");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
// TODO(ogniK): Check when all sections are implemented
|
||||
|
||||
if (!info_updater.CheckConsumedSize()) {
|
||||
LOG_ERROR(Audio, "Audio buffers were not consumed!");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
ReleaseAndQueueBuffers();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
|
||||
constexpr std::size_t BUFFER_SIZE{512};
|
||||
command_generator.PreCommand();
|
||||
// Clear mix buffers before our next operation
|
||||
command_generator.ClearMixBuffers();
|
||||
|
||||
// If the splitter is not in use, sort our mixes
|
||||
if (!splitter_context.UsingSplitter()) {
|
||||
mix_context.SortInfo();
|
||||
}
|
||||
// Sort our voices
|
||||
voice_context.SortInfo();
|
||||
|
||||
// Handle samples
|
||||
command_generator.GenerateVoiceCommands();
|
||||
command_generator.GenerateSubMixCommands();
|
||||
command_generator.GenerateFinalMixCommands();
|
||||
|
||||
command_generator.PostCommand();
|
||||
// Base sample size
|
||||
std::size_t BUFFER_SIZE{worker_params.sample_count};
|
||||
// Samples
|
||||
std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
|
||||
// Make sure to clear our samples
|
||||
std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
|
||||
|
||||
for (auto& voice : voices) {
|
||||
if (!voice.IsPlaying()) {
|
||||
continue;
|
||||
}
|
||||
VoiceChannelHolder resources{};
|
||||
for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) {
|
||||
const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel];
|
||||
resources[channel] = &voice_resources[channel_resource_id];
|
||||
if (sink_context.InUse()) {
|
||||
const auto stream_channel_count = stream->GetNumChannels();
|
||||
const auto buffer_offsets = sink_context.OutputBuffers();
|
||||
const auto channel_count = buffer_offsets.size();
|
||||
const auto& final_mix = mix_context.GetFinalMixInfo();
|
||||
const auto& in_params = final_mix.GetInParams();
|
||||
std::vector<s32*> mix_buffers(channel_count);
|
||||
for (std::size_t i = 0; i < channel_count; i++) {
|
||||
mix_buffers[i] =
|
||||
command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
|
||||
}
|
||||
|
||||
std::size_t offset{};
|
||||
s64 samples_remaining{BUFFER_SIZE};
|
||||
while (samples_remaining > 0) {
|
||||
const std::vector<s16> samples{
|
||||
voice.DequeueSamples(samples_remaining, memory, resources)};
|
||||
for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
|
||||
if (channel_count == 1) {
|
||||
const auto sample = ClampToS16(mix_buffers[0][i]);
|
||||
buffer[i * stream_channel_count + 0] = sample;
|
||||
if (stream_channel_count > 1) {
|
||||
buffer[i * stream_channel_count + 1] = sample;
|
||||
}
|
||||
if (stream_channel_count == 6) {
|
||||
buffer[i * stream_channel_count + 2] = sample;
|
||||
buffer[i * stream_channel_count + 4] = sample;
|
||||
buffer[i * stream_channel_count + 5] = sample;
|
||||
}
|
||||
} else if (channel_count == 2) {
|
||||
const auto l_sample = ClampToS16(mix_buffers[0][i]);
|
||||
const auto r_sample = ClampToS16(mix_buffers[1][i]);
|
||||
if (stream_channel_count == 1) {
|
||||
buffer[i * stream_channel_count + 0] = l_sample;
|
||||
} else if (stream_channel_count == 2) {
|
||||
buffer[i * stream_channel_count + 0] = l_sample;
|
||||
buffer[i * stream_channel_count + 1] = r_sample;
|
||||
} else if (stream_channel_count == 6) {
|
||||
buffer[i * stream_channel_count + 0] = l_sample;
|
||||
buffer[i * stream_channel_count + 1] = r_sample;
|
||||
|
||||
if (samples.empty()) {
|
||||
break;
|
||||
}
|
||||
buffer[i * stream_channel_count + 2] =
|
||||
ClampToS16((static_cast<s32>(l_sample) + static_cast<s32>(r_sample)) / 2);
|
||||
|
||||
samples_remaining -= samples.size() / stream->GetNumChannels();
|
||||
buffer[i * stream_channel_count + 4] = l_sample;
|
||||
buffer[i * stream_channel_count + 5] = r_sample;
|
||||
}
|
||||
|
||||
for (const auto& sample : samples) {
|
||||
const s32 buffer_sample{buffer[offset]};
|
||||
buffer[offset++] =
|
||||
ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume));
|
||||
} else if (channel_count == 6) {
|
||||
const auto fl_sample = ClampToS16(mix_buffers[0][i]);
|
||||
const auto fr_sample = ClampToS16(mix_buffers[1][i]);
|
||||
const auto fc_sample = ClampToS16(mix_buffers[2][i]);
|
||||
const auto lf_sample = ClampToS16(mix_buffers[3][i]);
|
||||
const auto bl_sample = ClampToS16(mix_buffers[4][i]);
|
||||
const auto br_sample = ClampToS16(mix_buffers[5][i]);
|
||||
|
||||
if (stream_channel_count == 1) {
|
||||
buffer[i * stream_channel_count + 0] = fc_sample;
|
||||
} else if (stream_channel_count == 2) {
|
||||
buffer[i * stream_channel_count + 0] =
|
||||
static_cast<s16>(0.3694f * static_cast<float>(fl_sample) +
|
||||
0.2612f * static_cast<float>(fc_sample) +
|
||||
0.3694f * static_cast<float>(bl_sample));
|
||||
buffer[i * stream_channel_count + 1] =
|
||||
static_cast<s16>(0.3694f * static_cast<float>(fr_sample) +
|
||||
0.2612f * static_cast<float>(fc_sample) +
|
||||
0.3694f * static_cast<float>(br_sample));
|
||||
} else if (stream_channel_count == 6) {
|
||||
buffer[i * stream_channel_count + 0] = fl_sample;
|
||||
buffer[i * stream_channel_count + 1] = fr_sample;
|
||||
buffer[i * stream_channel_count + 2] = fc_sample;
|
||||
buffer[i * stream_channel_count + 3] = lf_sample;
|
||||
buffer[i * stream_channel_count + 4] = bl_sample;
|
||||
buffer[i * stream_channel_count + 5] = br_sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
audio_out->QueueBuffer(stream, tag, std::move(buffer));
|
||||
elapsed_frame_count++;
|
||||
voice_context.UpdateStateByDspShared();
|
||||
}
|
||||
|
||||
void AudioRenderer::ReleaseAndQueueBuffers() {
|
||||
|
||||
@@ -9,8 +9,15 @@
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/behavior_info.h"
|
||||
#include "audio_core/command_generator.h"
|
||||
#include "audio_core/common.h"
|
||||
#include "audio_core/effect_context.h"
|
||||
#include "audio_core/memory_pool.h"
|
||||
#include "audio_core/mix_context.h"
|
||||
#include "audio_core/sink_context.h"
|
||||
#include "audio_core/splitter_context.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "audio_core/voice_context.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
@@ -30,220 +37,25 @@ class Memory;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
using DSPStateHolder = std::array<VoiceState*, 6>;
|
||||
|
||||
class AudioOut;
|
||||
|
||||
enum class PlayState : u8 {
|
||||
Started = 0,
|
||||
Stopped = 1,
|
||||
Paused = 2,
|
||||
};
|
||||
|
||||
enum class Effect : u8 {
|
||||
None = 0,
|
||||
Aux = 2,
|
||||
};
|
||||
|
||||
enum class EffectStatus : u8 {
|
||||
None = 0,
|
||||
New = 1,
|
||||
};
|
||||
|
||||
struct AudioRendererParameter {
|
||||
u32_le sample_rate;
|
||||
u32_le sample_count;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le submix_count;
|
||||
u32_le voice_count;
|
||||
u32_le sink_count;
|
||||
u32_le effect_count;
|
||||
u32_le performance_frame_count;
|
||||
u8 is_voice_drop_enabled;
|
||||
u8 unknown_21;
|
||||
u8 unknown_22;
|
||||
u8 execution_mode;
|
||||
u32_le splitter_count;
|
||||
u32_le num_splitter_send_channels;
|
||||
u32_le unknown_30;
|
||||
u32_le revision;
|
||||
};
|
||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
|
||||
|
||||
enum class MemoryPoolStates : u32 { // Should be LE
|
||||
Invalid = 0x0,
|
||||
Unknown = 0x1,
|
||||
RequestDetach = 0x2,
|
||||
Detached = 0x3,
|
||||
RequestAttach = 0x4,
|
||||
Attached = 0x5,
|
||||
Released = 0x6,
|
||||
};
|
||||
|
||||
struct MemoryPoolEntry {
|
||||
MemoryPoolStates state;
|
||||
u32_le unknown_4;
|
||||
u32_le unknown_8;
|
||||
u32_le unknown_c;
|
||||
};
|
||||
static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
|
||||
|
||||
struct MemoryPoolInfo {
|
||||
u64_le pool_address;
|
||||
u64_le pool_size;
|
||||
MemoryPoolStates pool_state;
|
||||
INSERT_PADDING_WORDS(3); // Unknown
|
||||
};
|
||||
static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
|
||||
struct BiquadFilter {
|
||||
u8 enable;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
std::array<s16_le, 3> numerator;
|
||||
std::array<s16_le, 2> denominator;
|
||||
};
|
||||
static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
|
||||
|
||||
struct WaveBuffer {
|
||||
u64_le buffer_addr;
|
||||
u64_le buffer_sz;
|
||||
s32_le start_sample_offset;
|
||||
s32_le end_sample_offset;
|
||||
u8 is_looping;
|
||||
u8 end_of_stream;
|
||||
u8 sent_to_server;
|
||||
INSERT_PADDING_BYTES(5);
|
||||
u64 context_addr;
|
||||
u64 context_sz;
|
||||
INSERT_PADDING_BYTES(8);
|
||||
};
|
||||
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
|
||||
|
||||
struct VoiceResourceInformation {
|
||||
s32_le id{};
|
||||
std::array<float_le, MAX_MIX_BUFFERS> mix_volumes{};
|
||||
bool in_use{};
|
||||
INSERT_PADDING_BYTES(11);
|
||||
};
|
||||
static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size");
|
||||
|
||||
struct VoiceInfo {
|
||||
u32_le id;
|
||||
u32_le node_id;
|
||||
u8 is_new;
|
||||
u8 is_in_use;
|
||||
PlayState play_state;
|
||||
u8 sample_format;
|
||||
u32_le sample_rate;
|
||||
u32_le priority;
|
||||
u32_le sorting_order;
|
||||
u32_le channel_count;
|
||||
float_le pitch;
|
||||
float_le volume;
|
||||
std::array<BiquadFilter, 2> biquad_filter;
|
||||
u32_le wave_buffer_count;
|
||||
u32_le wave_buffer_head;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u64_le additional_params_addr;
|
||||
u64_le additional_params_sz;
|
||||
u32_le mix_id;
|
||||
u32_le splitter_info_id;
|
||||
std::array<WaveBuffer, 4> wave_buffer;
|
||||
std::array<u32_le, 6> voice_channel_resource_ids;
|
||||
INSERT_PADDING_BYTES(24);
|
||||
};
|
||||
static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
|
||||
|
||||
struct VoiceOutStatus {
|
||||
u64_le played_sample_count;
|
||||
u32_le wave_buffer_consumed;
|
||||
u32_le voice_drops_count;
|
||||
};
|
||||
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
|
||||
|
||||
struct AuxInfo {
|
||||
std::array<u8, 24> input_mix_buffers;
|
||||
std::array<u8, 24> output_mix_buffers;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le sample_rate; // Stored in the aux buffer currently
|
||||
u32_le sample_count;
|
||||
u64_le send_buffer_info;
|
||||
u64_le send_buffer_base;
|
||||
|
||||
u64_le return_buffer_info;
|
||||
u64_le return_buffer_base;
|
||||
};
|
||||
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
|
||||
|
||||
struct EffectInStatus {
|
||||
Effect type;
|
||||
u8 is_new;
|
||||
u8 is_enabled;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u32_le mix_id;
|
||||
u64_le buffer_base;
|
||||
u64_le buffer_sz;
|
||||
s32_le priority;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
union {
|
||||
std::array<u8, 0xa0> raw;
|
||||
AuxInfo aux_info;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
|
||||
|
||||
struct EffectOutStatus {
|
||||
EffectStatus state;
|
||||
INSERT_PADDING_BYTES(0xf);
|
||||
};
|
||||
static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
|
||||
|
||||
struct RendererInfo {
|
||||
u64_le elasped_frame_count{};
|
||||
INSERT_PADDING_WORDS(2);
|
||||
};
|
||||
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
|
||||
|
||||
struct UpdateDataHeader {
|
||||
UpdateDataHeader() {}
|
||||
|
||||
explicit UpdateDataHeader(const AudioRendererParameter& config) {
|
||||
revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision
|
||||
behavior_size = 0xb0;
|
||||
memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
|
||||
voices_size = config.voice_count * 0x10;
|
||||
voice_resource_size = 0x0;
|
||||
effects_size = config.effect_count * 0x10;
|
||||
mixes_size = 0x0;
|
||||
sinks_size = config.sink_count * 0x20;
|
||||
performance_manager_size = 0x10;
|
||||
render_info = 0;
|
||||
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
|
||||
effects_size + sinks_size + performance_manager_size;
|
||||
}
|
||||
|
||||
u32_le revision{};
|
||||
u32_le behavior_size{};
|
||||
u32_le memory_pools_size{};
|
||||
u32_le voices_size{};
|
||||
u32_le voice_resource_size{};
|
||||
u32_le effects_size{};
|
||||
u32_le mixes_size{};
|
||||
u32_le sinks_size{};
|
||||
u32_le performance_manager_size{};
|
||||
u32_le splitter_size{};
|
||||
u32_le render_info{};
|
||||
INSERT_PADDING_WORDS(4);
|
||||
u32_le total_size{};
|
||||
};
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
|
||||
|
||||
class AudioRenderer {
|
||||
public:
|
||||
AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
|
||||
AudioRendererParameter params,
|
||||
AudioCommon::AudioRendererParameter params,
|
||||
std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number);
|
||||
~AudioRenderer();
|
||||
|
||||
ResultVal<std::vector<u8>> UpdateAudioRenderer(const std::vector<u8>& input_params);
|
||||
ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
|
||||
std::vector<u8>& output_params);
|
||||
void QueueMixedBuffer(Buffer::Tag tag);
|
||||
void ReleaseAndQueueBuffers();
|
||||
u32 GetSampleRate() const;
|
||||
@@ -252,19 +64,23 @@ public:
|
||||
Stream::State GetStreamState() const;
|
||||
|
||||
private:
|
||||
class EffectState;
|
||||
class VoiceState;
|
||||
BehaviorInfo behavior_info{};
|
||||
|
||||
AudioRendererParameter worker_params;
|
||||
AudioCommon::AudioRendererParameter worker_params;
|
||||
std::shared_ptr<Kernel::WritableEvent> buffer_event;
|
||||
std::vector<ServerMemoryPoolInfo> memory_pool_info;
|
||||
VoiceContext voice_context;
|
||||
EffectContext effect_context;
|
||||
MixContext mix_context;
|
||||
SinkContext sink_context;
|
||||
SplitterContext splitter_context;
|
||||
std::vector<VoiceState> voices;
|
||||
std::vector<VoiceResourceInformation> voice_resources;
|
||||
std::vector<EffectState> effects;
|
||||
std::unique_ptr<AudioOut> audio_out;
|
||||
StreamPtr stream;
|
||||
Core::Memory::Memory& memory;
|
||||
CommandGenerator command_generator;
|
||||
std::size_t elapsed_frame_count{};
|
||||
std::vector<s32> temp_mix_buffer{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -9,39 +9,11 @@
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {}
|
||||
BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
|
||||
BehaviorInfo::~BehaviorInfo() = default;
|
||||
|
||||
bool BehaviorInfo::UpdateInput(const std::vector<u8>& buffer, std::size_t offset) {
|
||||
if (!CanConsumeBuffer(buffer.size(), offset, sizeof(InParams))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
InParams params{};
|
||||
std::memcpy(¶ms, buffer.data() + offset, sizeof(InParams));
|
||||
|
||||
if (!IsValidRevision(params.revision)) {
|
||||
LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", params.revision);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user_revision != params.revision) {
|
||||
LOG_ERROR(Audio,
|
||||
"User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
|
||||
user_revision, params.revision);
|
||||
return false;
|
||||
}
|
||||
|
||||
ClearError();
|
||||
UpdateFlags(params.flags);
|
||||
|
||||
// TODO(ogniK): Check input params size when InfoUpdater is used
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
|
||||
if (!CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
|
||||
if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
@@ -65,36 +37,69 @@ void BehaviorInfo::SetUserRevision(u32_le revision) {
|
||||
user_revision = revision;
|
||||
}
|
||||
|
||||
u32_le BehaviorInfo::GetUserRevision() const {
|
||||
return user_revision;
|
||||
}
|
||||
|
||||
u32_le BehaviorInfo::GetProcessRevision() const {
|
||||
return process_revision;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
|
||||
return IsRevisionSupported(2, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsSplitterSupported() const {
|
||||
return IsRevisionSupported(2, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsLongSizePreDelaySupported() const {
|
||||
return IsRevisionSupported(3, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(3, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const {
|
||||
return IsRevisionSupported(5, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const {
|
||||
return IsRevisionSupported(4, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(4, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const {
|
||||
return IsRevisionSupported(1, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(1, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsElapsedFrameCountSupported() const {
|
||||
return IsRevisionSupported(5, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
|
||||
return (flags & 1) != 0;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
|
||||
return AudioCommon::IsRevisionSupported(7, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsSplitterBugFixed() const {
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
|
||||
dst.error_count = static_cast<u32>(error_count);
|
||||
std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin());
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -14,30 +14,6 @@
|
||||
namespace AudioCore {
|
||||
class BehaviorInfo {
|
||||
public:
|
||||
explicit BehaviorInfo();
|
||||
~BehaviorInfo();
|
||||
|
||||
bool UpdateInput(const std::vector<u8>& buffer, std::size_t offset);
|
||||
bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
|
||||
|
||||
void ClearError();
|
||||
void UpdateFlags(u64_le dest_flags);
|
||||
void SetUserRevision(u32_le revision);
|
||||
|
||||
bool IsAdpcmLoopContextBugFixed() const;
|
||||
bool IsSplitterSupported() const;
|
||||
bool IsLongSizePreDelaySupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit80PercentSupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit75PercentSupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
|
||||
bool IsElapsedFrameCountSupported() const;
|
||||
bool IsMemoryPoolForceMappingEnabled() const;
|
||||
|
||||
private:
|
||||
u32_le process_revision{};
|
||||
u32_le user_revision{};
|
||||
u64_le flags{};
|
||||
|
||||
struct ErrorInfo {
|
||||
u32_le result{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
@@ -45,9 +21,6 @@ private:
|
||||
};
|
||||
static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
|
||||
|
||||
std::array<ErrorInfo, 10> errors{};
|
||||
std::size_t error_count{};
|
||||
|
||||
struct InParams {
|
||||
u32_le revision{};
|
||||
u32_le padding{};
|
||||
@@ -61,6 +34,39 @@ private:
|
||||
INSERT_PADDING_BYTES(12);
|
||||
};
|
||||
static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
|
||||
|
||||
explicit BehaviorInfo();
|
||||
~BehaviorInfo();
|
||||
|
||||
bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
|
||||
|
||||
void ClearError();
|
||||
void UpdateFlags(u64_le dest_flags);
|
||||
void SetUserRevision(u32_le revision);
|
||||
u32_le GetUserRevision() const;
|
||||
u32_le GetProcessRevision() const;
|
||||
|
||||
bool IsAdpcmLoopContextBugFixed() const;
|
||||
bool IsSplitterSupported() const;
|
||||
bool IsLongSizePreDelaySupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit80PercentSupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit75PercentSupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
|
||||
bool IsElapsedFrameCountSupported() const;
|
||||
bool IsMemoryPoolForceMappingEnabled() const;
|
||||
bool IsFlushVoiceWaveBuffersSupported() const;
|
||||
bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
|
||||
bool IsVoicePitchAndSrcSkippedSupported() const;
|
||||
bool IsMixInParameterDirtyOnlyUpdateSupported() const;
|
||||
bool IsSplitterBugFixed() const;
|
||||
void CopyErrorInfo(OutParams& dst);
|
||||
|
||||
private:
|
||||
u32_le process_revision{};
|
||||
u32_le user_revision{};
|
||||
u64_le flags{};
|
||||
std::array<ErrorInfo, 10> errors{};
|
||||
std::size_t error_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -0,0 +1,976 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/algorithm/interpolate.h"
|
||||
#include "audio_core/command_generator.h"
|
||||
#include "audio_core/effect_context.h"
|
||||
#include "audio_core/mix_context.h"
|
||||
#include "audio_core/voice_context.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore {
|
||||
namespace {
|
||||
constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
|
||||
constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
|
||||
|
||||
template <std::size_t N>
|
||||
void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
|
||||
for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) {
|
||||
for (std::size_t j = 0; j < N; j++) {
|
||||
output[i + j] +=
|
||||
static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) {
|
||||
s32 x = 0;
|
||||
for (s32 i = 0; i < sample_count; i++) {
|
||||
x = static_cast<s32>(static_cast<float>(input[i]) * gain);
|
||||
output[i] += x;
|
||||
gain += delta;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) {
|
||||
for (s32 i = 0; i < sample_count; i++) {
|
||||
output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
|
||||
gain += delta;
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) {
|
||||
for (s32 i = 0; i < sample_count; i++) {
|
||||
output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
|
||||
}
|
||||
}
|
||||
|
||||
s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {
|
||||
const bool positive = first_sample > 0;
|
||||
auto final_sample = std::abs(first_sample);
|
||||
for (s32 i = 0; i < sample_count; i++) {
|
||||
final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15);
|
||||
if (positive) {
|
||||
output[i] += final_sample;
|
||||
} else {
|
||||
output[i] -= final_sample;
|
||||
}
|
||||
}
|
||||
if (positive) {
|
||||
return final_sample;
|
||||
} else {
|
||||
return -final_sample;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
|
||||
VoiceContext& voice_context, MixContext& mix_context,
|
||||
SplitterContext& splitter_context, EffectContext& effect_context,
|
||||
Core::Memory::Memory& memory)
|
||||
: worker_params(worker_params), voice_context(voice_context), mix_context(mix_context),
|
||||
splitter_context(splitter_context), effect_context(effect_context), memory(memory),
|
||||
mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
|
||||
worker_params.sample_count),
|
||||
sample_buffer(MIX_BUFFER_SIZE),
|
||||
depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
|
||||
worker_params.sample_count) {}
|
||||
CommandGenerator::~CommandGenerator() = default;
|
||||
|
||||
void CommandGenerator::ClearMixBuffers() {
|
||||
std::fill(mix_buffer.begin(), mix_buffer.end(), 0);
|
||||
std::fill(sample_buffer.begin(), sample_buffer.end(), 0);
|
||||
// std::fill(depop_buffer.begin(), depop_buffer.end(), 0);
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateVoiceCommands() {
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands");
|
||||
}
|
||||
// Grab all our voices
|
||||
const auto voice_count = voice_context.GetVoiceCount();
|
||||
for (std::size_t i = 0; i < voice_count; i++) {
|
||||
auto& voice_info = voice_context.GetSortedInfo(i);
|
||||
// Update voices and check if we should queue them
|
||||
if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Queue our voice
|
||||
GenerateVoiceCommand(voice_info);
|
||||
}
|
||||
// Update our splitters
|
||||
splitter_context.UpdateInternalState();
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) {
|
||||
auto& in_params = voice_info.GetInParams();
|
||||
const auto channel_count = in_params.channel_count;
|
||||
|
||||
for (s32 channel = 0; channel < channel_count; channel++) {
|
||||
const auto resource_id = in_params.voice_channel_resource_id[channel];
|
||||
auto& dsp_state = voice_context.GetDspSharedState(resource_id);
|
||||
auto& channel_resource = voice_context.GetChannelResource(resource_id);
|
||||
|
||||
// Decode our samples for our channel
|
||||
GenerateDataSourceCommand(voice_info, dsp_state, channel);
|
||||
|
||||
if (in_params.should_depop) {
|
||||
in_params.last_volume = 0.0f;
|
||||
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER ||
|
||||
in_params.mix_id != AudioCommon::NO_MIX) {
|
||||
// Apply a biquad filter if needed
|
||||
GenerateBiquadFilterCommandForVoice(voice_info, dsp_state,
|
||||
worker_params.mix_buffer_count, channel);
|
||||
// Base voice volume ramping
|
||||
GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel,
|
||||
in_params.node_id);
|
||||
in_params.last_volume = in_params.volume;
|
||||
|
||||
if (in_params.mix_id != AudioCommon::NO_MIX) {
|
||||
// If we're using a mix id
|
||||
auto& mix_info = mix_context.GetInfo(in_params.mix_id);
|
||||
const auto& dest_mix_params = mix_info.GetInParams();
|
||||
|
||||
// Voice Mixing
|
||||
GenerateVoiceMixCommand(
|
||||
channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(),
|
||||
dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
|
||||
worker_params.mix_buffer_count + channel, in_params.node_id);
|
||||
|
||||
// Update last mix volumes
|
||||
channel_resource.UpdateLastMixVolumes();
|
||||
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
|
||||
s32 base = channel;
|
||||
while (auto* destination_data =
|
||||
GetDestinationData(in_params.splitter_info_id, base)) {
|
||||
base += channel_count;
|
||||
|
||||
if (!destination_data->IsConfigured()) {
|
||||
continue;
|
||||
}
|
||||
if (destination_data->GetMixId() >= mix_context.GetCount()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId());
|
||||
const auto& dest_mix_params = mix_info.GetInParams();
|
||||
GenerateVoiceMixCommand(
|
||||
destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(),
|
||||
dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
|
||||
worker_params.mix_buffer_count + channel, in_params.node_id);
|
||||
destination_data->MarkDirty();
|
||||
}
|
||||
}
|
||||
// Update biquad filter enabled states
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
|
||||
in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateSubMixCommands() {
|
||||
const auto mix_count = mix_context.GetCount();
|
||||
for (std::size_t i = 0; i < mix_count; i++) {
|
||||
auto& mix_info = mix_context.GetSortedInfo(i);
|
||||
const auto& in_params = mix_info.GetInParams();
|
||||
if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) {
|
||||
continue;
|
||||
}
|
||||
GenerateSubMixCommand(mix_info);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateFinalMixCommands() {
|
||||
GenerateFinalMixCommand();
|
||||
}
|
||||
|
||||
void CommandGenerator::PreCommand() {
|
||||
if (!dumping_frame) {
|
||||
return;
|
||||
}
|
||||
for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
|
||||
const auto& base = splitter_context.GetInfo(i);
|
||||
std::string graph = fmt::format("b[{}]", i);
|
||||
auto* head = base.GetHead();
|
||||
while (head != nullptr) {
|
||||
graph += fmt::format("->{}", head->GetMixId());
|
||||
head = head->GetNextDestination();
|
||||
}
|
||||
LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::PostCommand() {
|
||||
if (!dumping_frame) {
|
||||
return;
|
||||
}
|
||||
dumping_frame = false;
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
|
||||
s32 channel) {
|
||||
auto& in_params = voice_info.GetInParams();
|
||||
const auto depop = in_params.should_depop;
|
||||
|
||||
if (depop) {
|
||||
if (in_params.mix_id != AudioCommon::NO_MIX) {
|
||||
auto& mix_info = mix_context.GetInfo(in_params.mix_id);
|
||||
const auto& mix_in = mix_info.GetInParams();
|
||||
GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
|
||||
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
|
||||
s32 index{};
|
||||
while (const auto* destination =
|
||||
GetDestinationData(in_params.splitter_info_id, index++)) {
|
||||
if (!destination->IsConfigured()) {
|
||||
continue;
|
||||
}
|
||||
auto& mix_info = mix_context.GetInfo(destination->GetMixId());
|
||||
const auto& mix_in = mix_info.GetInParams();
|
||||
GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (in_params.sample_format) {
|
||||
case SampleFormat::Pcm16:
|
||||
DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
|
||||
worker_params.sample_rate, worker_params.sample_count,
|
||||
in_params.node_id);
|
||||
break;
|
||||
case SampleFormat::Adpcm:
|
||||
ASSERT(channel == 0 && in_params.channel_count == 1);
|
||||
DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0,
|
||||
worker_params.sample_rate, worker_params.sample_count,
|
||||
in_params.node_id);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info,
|
||||
VoiceState& dsp_state,
|
||||
s32 mix_buffer_count, s32 channel) {
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
|
||||
const auto& in_params = voice_info.GetInParams();
|
||||
auto& biquad_filter = in_params.biquad_filter[i];
|
||||
// Check if biquad filter is actually used
|
||||
if (!biquad_filter.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reinitialize our biquad filter state if it was enabled previously
|
||||
if (!in_params.was_biquad_filter_enabled[i]) {
|
||||
dsp_state.biquad_filter_state.fill(0);
|
||||
}
|
||||
|
||||
// Generate biquad filter
|
||||
// GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
|
||||
// dsp_state.biquad_filter_state,
|
||||
// mix_buffer_count + channel, mix_buffer_count +
|
||||
// channel, worker_params.sample_count,
|
||||
// voice_info.GetInParams().node_id);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCore::CommandGenerator::GenerateBiquadFilterCommand(
|
||||
s32 mix_buffer, const BiquadFilterParameter& params, std::array<s64, 2>& state,
|
||||
std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) {
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio,
|
||||
"(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, "
|
||||
"input_mix_buffer={}, output_mix_buffer={}",
|
||||
node_id, input_offset, output_offset);
|
||||
}
|
||||
const auto* input = GetMixBuffer(input_offset);
|
||||
auto* output = GetMixBuffer(output_offset);
|
||||
|
||||
// Biquad filter parameters
|
||||
const auto [n0, n1, n2] = params.numerator;
|
||||
const auto [d0, d1] = params.denominator;
|
||||
|
||||
// Biquad filter states
|
||||
auto [s0, s1] = state;
|
||||
|
||||
constexpr s64 int32_min = std::numeric_limits<s32>::min();
|
||||
constexpr s64 int32_max = std::numeric_limits<s32>::max();
|
||||
|
||||
for (int i = 0; i < sample_count; ++i) {
|
||||
const auto sample = static_cast<s64>(input[i]);
|
||||
const auto f = (sample * n0 + s0 + 0x4000) >> 15;
|
||||
const auto y = std::clamp(f, int32_min, int32_max);
|
||||
s0 = sample * n1 + y * d0 + s1;
|
||||
s1 = sample * n2 + y * d1;
|
||||
output[i] = static_cast<s32>(y);
|
||||
}
|
||||
|
||||
state = {s0, s1};
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state,
|
||||
std::size_t mix_buffer_count,
|
||||
std::size_t mix_buffer_offset) {
|
||||
for (std::size_t i = 0; i < mix_buffer_count; i++) {
|
||||
auto& sample = dsp_state.previous_samples[i];
|
||||
if (sample != 0) {
|
||||
depop_buffer[mix_buffer_offset + i] += sample;
|
||||
sample = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
|
||||
std::size_t mix_buffer_offset,
|
||||
s32 sample_rate) {
|
||||
const std::size_t end_offset =
|
||||
std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount());
|
||||
const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB;
|
||||
for (std::size_t i = mix_buffer_offset; i < end_offset; i++) {
|
||||
if (depop_buffer[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
depop_buffer[i] =
|
||||
ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
|
||||
const std::size_t effect_count = effect_context.GetCount();
|
||||
const auto buffer_offset = mix_info.GetInParams().buffer_offset;
|
||||
for (std::size_t i = 0; i < effect_count; i++) {
|
||||
const auto index = mix_info.GetEffectOrder(i);
|
||||
if (index == AudioCommon::NO_EFFECT_ORDER) {
|
||||
break;
|
||||
}
|
||||
auto* info = effect_context.GetInfo(index);
|
||||
const auto type = info->GetType();
|
||||
|
||||
// TODO(ogniK): Finish remaining effects
|
||||
switch (type) {
|
||||
case EffectType::Aux:
|
||||
GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
|
||||
break;
|
||||
case EffectType::I3dl2Reverb:
|
||||
GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
|
||||
break;
|
||||
case EffectType::BiquadFilter:
|
||||
GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
info->UpdateForCommandGeneration();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
|
||||
bool enabled) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams();
|
||||
const auto channel_count = params.channel_count;
|
||||
for (s32 i = 0; i < channel_count; i++) {
|
||||
// TODO(ogniK): Actually implement reverb
|
||||
if (params.input[i] != params.output[i]) {
|
||||
const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
|
||||
auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
|
||||
ApplyMix<1>(output, input, 32768, worker_params.sample_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
|
||||
bool enabled) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
|
||||
const auto channel_count = params.channel_count;
|
||||
for (s32 i = 0; i < channel_count; i++) {
|
||||
// TODO(ogniK): Actually implement biquad filter
|
||||
if (params.input[i] != params.output[i]) {
|
||||
const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
|
||||
auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
|
||||
ApplyMix<1>(output, input, 32768, worker_params.sample_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
|
||||
auto aux = dynamic_cast<EffectAuxInfo*>(info);
|
||||
const auto& params = aux->GetParams();
|
||||
if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
|
||||
const auto max_channels = params.count;
|
||||
u32 offset{};
|
||||
for (u32 channel = 0; channel < max_channels; channel++) {
|
||||
u32 write_count = 0;
|
||||
if (channel == (max_channels - 1)) {
|
||||
write_count = offset + worker_params.sample_count;
|
||||
}
|
||||
|
||||
const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
|
||||
const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
|
||||
|
||||
if (enabled) {
|
||||
AuxInfoDSP send_info{};
|
||||
AuxInfoDSP recv_info{};
|
||||
memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
|
||||
memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
|
||||
|
||||
WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
|
||||
GetMixBuffer(input_index), worker_params.sample_count, offset,
|
||||
write_count);
|
||||
memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
|
||||
|
||||
const auto samples_read = ReadAuxBuffer(
|
||||
recv_info, aux->GetRecvBuffer(), params.sample_count,
|
||||
GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
|
||||
memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
|
||||
|
||||
if (samples_read != worker_params.sample_count &&
|
||||
samples_read <= params.sample_count) {
|
||||
std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read);
|
||||
}
|
||||
} else {
|
||||
AuxInfoDSP empty{};
|
||||
memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
|
||||
memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
|
||||
if (output_index != input_index) {
|
||||
std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index),
|
||||
worker_params.sample_count * sizeof(s32));
|
||||
}
|
||||
}
|
||||
|
||||
offset += worker_params.sample_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
|
||||
if (splitter_id == AudioCommon::NO_SPLITTER) {
|
||||
return nullptr;
|
||||
}
|
||||
return splitter_context.GetDestinationData(splitter_id, index);
|
||||
}
|
||||
|
||||
s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
|
||||
const s32* data, u32 sample_count, u32 write_offset,
|
||||
u32 write_count) {
|
||||
if (max_samples == 0) {
|
||||
return 0;
|
||||
}
|
||||
u32 offset = dsp_info.write_offset + write_offset;
|
||||
if (send_buffer == 0 || offset > max_samples) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t data_offset{};
|
||||
u32 remaining = sample_count;
|
||||
while (remaining > 0) {
|
||||
// Get position in buffer
|
||||
const auto base = send_buffer + (offset * sizeof(u32));
|
||||
const auto samples_to_grab = std::min(max_samples - offset, remaining);
|
||||
// Write to output
|
||||
memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32));
|
||||
offset = (offset + samples_to_grab) % max_samples;
|
||||
remaining -= samples_to_grab;
|
||||
data_offset += samples_to_grab;
|
||||
}
|
||||
|
||||
if (write_count != 0) {
|
||||
dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
|
||||
}
|
||||
return sample_count;
|
||||
}
|
||||
|
||||
s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
|
||||
s32* out_data, u32 sample_count, u32 read_offset,
|
||||
u32 read_count) {
|
||||
if (max_samples == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 offset = recv_info.read_offset + read_offset;
|
||||
if (recv_buffer == 0 || offset > max_samples) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 remaining = sample_count;
|
||||
while (remaining > 0) {
|
||||
const auto base = recv_buffer + (offset * sizeof(u32));
|
||||
const auto samples_to_grab = std::min(max_samples - offset, remaining);
|
||||
std::vector<s32> buffer(samples_to_grab);
|
||||
memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
|
||||
std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32));
|
||||
out_data += samples_to_grab;
|
||||
offset = (offset + samples_to_grab) % max_samples;
|
||||
remaining -= samples_to_grab;
|
||||
}
|
||||
|
||||
if (read_count != 0) {
|
||||
recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
|
||||
}
|
||||
return sample_count;
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
|
||||
s32 channel, s32 node_id) {
|
||||
const auto last = static_cast<s32>(last_volume * 32768.0f);
|
||||
const auto current = static_cast<s32>(current_volume * 32768.0f);
|
||||
const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
|
||||
static_cast<float>(worker_params.sample_count));
|
||||
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio,
|
||||
"(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, "
|
||||
"last_volume={}, current_volume={}",
|
||||
node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel),
|
||||
last_volume, current_volume);
|
||||
}
|
||||
// Apply generic gain on samples
|
||||
ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta,
|
||||
worker_params.sample_count);
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
|
||||
const MixVolumeBuffer& last_mix_volumes,
|
||||
VoiceState& dsp_state, s32 mix_buffer_offset,
|
||||
s32 mix_buffer_count, s32 voice_index, s32 node_id) {
|
||||
// Loop all our mix buffers
|
||||
for (s32 i = 0; i < mix_buffer_count; i++) {
|
||||
if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) {
|
||||
const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) /
|
||||
static_cast<float>(worker_params.sample_count);
|
||||
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio,
|
||||
"(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, "
|
||||
"output={}, last_volume={}, current_volume={}",
|
||||
node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i],
|
||||
mix_volumes[i]);
|
||||
}
|
||||
|
||||
dsp_state.previous_samples[i] =
|
||||
ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index),
|
||||
last_mix_volumes[i], delta, worker_params.sample_count);
|
||||
} else {
|
||||
dsp_state.previous_samples[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand");
|
||||
}
|
||||
auto& in_params = mix_info.GetInParams();
|
||||
GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
|
||||
in_params.sample_rate);
|
||||
|
||||
GenerateEffectCommand(mix_info);
|
||||
|
||||
GenerateMixCommands(mix_info);
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) {
|
||||
if (!mix_info.HasAnyConnection()) {
|
||||
return;
|
||||
}
|
||||
const auto& in_params = mix_info.GetInParams();
|
||||
if (in_params.dest_mix_id != AudioCommon::NO_MIX) {
|
||||
const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id);
|
||||
const auto& dest_in_params = dest_mix.GetInParams();
|
||||
|
||||
const auto buffer_count = in_params.buffer_count;
|
||||
|
||||
for (s32 i = 0; i < buffer_count; i++) {
|
||||
for (s32 j = 0; j < dest_in_params.buffer_count; j++) {
|
||||
const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j];
|
||||
if (mixed_volume != 0.0f) {
|
||||
GenerateMixCommand(dest_in_params.buffer_offset + j,
|
||||
in_params.buffer_offset + i, mixed_volume,
|
||||
in_params.node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) {
|
||||
s32 base{};
|
||||
while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) {
|
||||
if (!destination_data->IsConfigured()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId());
|
||||
const auto& dest_in_params = dest_mix.GetInParams();
|
||||
const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset;
|
||||
for (std::size_t i = 0; i < dest_in_params.buffer_count; i++) {
|
||||
const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i);
|
||||
if (mixed_volume != 0.0f) {
|
||||
GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume,
|
||||
in_params.node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset,
|
||||
float volume, s32 node_id) {
|
||||
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio,
|
||||
"(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}",
|
||||
node_id, input_offset, output_offset, volume);
|
||||
}
|
||||
|
||||
auto* output = GetMixBuffer(output_offset);
|
||||
const auto* input = GetMixBuffer(input_offset);
|
||||
|
||||
const s32 gain = static_cast<s32>(volume * 32768.0f);
|
||||
// Mix with loop unrolling
|
||||
if (worker_params.sample_count % 4 == 0) {
|
||||
ApplyMix<4>(output, input, gain, worker_params.sample_count);
|
||||
} else if (worker_params.sample_count % 2 == 0) {
|
||||
ApplyMix<2>(output, input, gain, worker_params.sample_count);
|
||||
} else {
|
||||
ApplyMix<1>(output, input, gain, worker_params.sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateFinalMixCommand() {
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
|
||||
}
|
||||
auto& mix_info = mix_context.GetFinalMixInfo();
|
||||
const auto in_params = mix_info.GetInParams();
|
||||
|
||||
GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
|
||||
in_params.sample_rate);
|
||||
|
||||
GenerateEffectCommand(mix_info);
|
||||
|
||||
for (s32 i = 0; i < in_params.buffer_count; i++) {
|
||||
const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(
|
||||
Audio,
|
||||
"(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}",
|
||||
in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i,
|
||||
in_params.volume);
|
||||
}
|
||||
ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i),
|
||||
GetMixBuffer(in_params.buffer_offset + i), gain,
|
||||
worker_params.sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
|
||||
s32 sample_count, s32 channel, std::size_t mix_offset) {
|
||||
auto& in_params = voice_info.GetInParams();
|
||||
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
|
||||
if (wave_buffer.buffer_address == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (wave_buffer.buffer_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
|
||||
return 0;
|
||||
}
|
||||
const auto samples_remaining =
|
||||
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
|
||||
const auto start_offset =
|
||||
((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) *
|
||||
sizeof(s16);
|
||||
const auto buffer_pos = wave_buffer.buffer_address + start_offset;
|
||||
const auto samples_processed = std::min(sample_count, samples_remaining);
|
||||
|
||||
if (in_params.channel_count == 1) {
|
||||
std::vector<s16> buffer(samples_processed);
|
||||
memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
|
||||
for (std::size_t i = 0; i < buffer.size(); i++) {
|
||||
sample_buffer[mix_offset + i] = buffer[i];
|
||||
}
|
||||
} else {
|
||||
const auto channel_count = in_params.channel_count;
|
||||
std::vector<s16> buffer(samples_processed * channel_count);
|
||||
memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
|
||||
|
||||
for (std::size_t i = 0; i < samples_processed; i++) {
|
||||
sample_buffer[mix_offset + i] = buffer[i * channel_count + channel];
|
||||
}
|
||||
}
|
||||
|
||||
return samples_processed;
|
||||
}
|
||||
|
||||
s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
|
||||
s32 sample_count, s32 channel, std::size_t mix_offset) {
|
||||
auto& in_params = voice_info.GetInParams();
|
||||
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
|
||||
if (wave_buffer.buffer_address == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (wave_buffer.buffer_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr std::array<int, 16> SIGNED_NIBBLES = {
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}};
|
||||
|
||||
constexpr std::size_t FRAME_LEN = 8;
|
||||
constexpr std::size_t NIBBLES_PER_SAMPLE = 16;
|
||||
constexpr std::size_t SAMPLES_PER_FRAME = 14;
|
||||
|
||||
auto frame_header = dsp_state.context.header;
|
||||
s32 idx = (frame_header >> 4) & 0xf;
|
||||
s32 scale = frame_header & 0xf;
|
||||
s16 yn1 = dsp_state.context.yn1;
|
||||
s16 yn2 = dsp_state.context.yn2;
|
||||
|
||||
Codec::ADPCM_Coeff coeffs;
|
||||
memory.ReadBlock(in_params.additional_params_address, coeffs.data(),
|
||||
sizeof(Codec::ADPCM_Coeff));
|
||||
|
||||
s32 coef1 = coeffs[idx * 2];
|
||||
s32 coef2 = coeffs[idx * 2 + 1];
|
||||
|
||||
const auto samples_remaining =
|
||||
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
|
||||
const auto samples_processed = std::min(sample_count, samples_remaining);
|
||||
const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset;
|
||||
|
||||
const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
|
||||
auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
|
||||
samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0);
|
||||
|
||||
const auto decode_sample = [&](const int nibble) -> s16 {
|
||||
const int xn = nibble * (1 << scale);
|
||||
// We first transform everything into 11 bit fixed point, perform the second order
|
||||
// digital filter, then transform back.
|
||||
// 0x400 == 0.5 in 11 bit fixed point.
|
||||
// Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
|
||||
int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
|
||||
// Clamp to output range.
|
||||
val = std::clamp<s32>(val, -32768, 32767);
|
||||
// Advance output feedback.
|
||||
yn2 = yn1;
|
||||
yn1 = val;
|
||||
return static_cast<s16>(val);
|
||||
};
|
||||
|
||||
std::size_t buffer_offset{};
|
||||
std::vector<u8> buffer(
|
||||
std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN));
|
||||
memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(),
|
||||
buffer.size());
|
||||
std::size_t cur_mix_offset = mix_offset;
|
||||
|
||||
auto remaining_samples = samples_processed;
|
||||
while (remaining_samples > 0) {
|
||||
if (position_in_frame % NIBBLES_PER_SAMPLE == 0) {
|
||||
// Read header
|
||||
frame_header = buffer[buffer_offset++];
|
||||
idx = (frame_header >> 4) & 0xf;
|
||||
scale = frame_header & 0xf;
|
||||
coef1 = coeffs[idx * 2];
|
||||
coef2 = coeffs[idx * 2 + 1];
|
||||
position_in_frame += 2;
|
||||
|
||||
// Decode entire frame
|
||||
if (remaining_samples >= SAMPLES_PER_FRAME) {
|
||||
for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) {
|
||||
|
||||
// Sample 1
|
||||
const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4];
|
||||
const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf];
|
||||
const s16 sample_1 = decode_sample(s0);
|
||||
const s16 sample_2 = decode_sample(s1);
|
||||
sample_buffer[cur_mix_offset++] = sample_1;
|
||||
sample_buffer[cur_mix_offset++] = sample_2;
|
||||
}
|
||||
remaining_samples -= SAMPLES_PER_FRAME;
|
||||
position_in_frame += SAMPLES_PER_FRAME;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Decode mid frame
|
||||
s32 current_nibble = buffer[buffer_offset];
|
||||
if (position_in_frame++ & 0x1) {
|
||||
current_nibble &= 0xf;
|
||||
buffer_offset++;
|
||||
} else {
|
||||
current_nibble >>= 4;
|
||||
}
|
||||
const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]);
|
||||
sample_buffer[cur_mix_offset++] = sample;
|
||||
remaining_samples--;
|
||||
}
|
||||
|
||||
dsp_state.context.header = frame_header;
|
||||
dsp_state.context.yn1 = yn1;
|
||||
dsp_state.context.yn2 = yn2;
|
||||
|
||||
return samples_processed;
|
||||
}
|
||||
|
||||
s32* CommandGenerator::GetMixBuffer(std::size_t index) {
|
||||
return mix_buffer.data() + (index * worker_params.sample_count);
|
||||
}
|
||||
|
||||
const s32* CommandGenerator::GetMixBuffer(std::size_t index) const {
|
||||
return mix_buffer.data() + (index * worker_params.sample_count);
|
||||
}
|
||||
|
||||
std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
|
||||
return worker_params.mix_buffer_count + channel;
|
||||
}
|
||||
|
||||
std::size_t CommandGenerator::GetTotalMixBufferCount() const {
|
||||
return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT;
|
||||
}
|
||||
|
||||
s32* CommandGenerator::GetChannelMixBuffer(s32 channel) {
|
||||
return GetMixBuffer(worker_params.mix_buffer_count + channel);
|
||||
}
|
||||
|
||||
const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const {
|
||||
return GetMixBuffer(worker_params.mix_buffer_count + channel);
|
||||
}
|
||||
|
||||
void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output,
|
||||
VoiceState& dsp_state, s32 channel,
|
||||
s32 target_sample_rate, s32 sample_count,
|
||||
s32 node_id) {
|
||||
auto& in_params = voice_info.GetInParams();
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio,
|
||||
"(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
|
||||
"format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}",
|
||||
node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate,
|
||||
in_params.mix_id, in_params.splitter_info_id);
|
||||
}
|
||||
ASSERT_OR_EXECUTE(output != nullptr, { return; });
|
||||
|
||||
const auto resample_rate = static_cast<s32>(
|
||||
static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
|
||||
static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
|
||||
auto* output_base = output;
|
||||
if ((dsp_state.fraction + sample_count * resample_rate) > (SCALED_MIX_BUFFER_SIZE - 4ULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto min_required_samples =
|
||||
std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate);
|
||||
if (min_required_samples >= sample_count) {
|
||||
min_required_samples = sample_count;
|
||||
}
|
||||
|
||||
std::size_t temp_mix_offset{};
|
||||
bool is_buffer_completed{false};
|
||||
auto samples_remaining = sample_count;
|
||||
while (samples_remaining > 0 && !is_buffer_completed) {
|
||||
const auto samples_to_output = std::min(samples_remaining, min_required_samples);
|
||||
const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
|
||||
|
||||
if (!in_params.behavior_flags.is_pitch_and_src_skipped) {
|
||||
// Append sample histtory for resampler
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
|
||||
sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i];
|
||||
}
|
||||
temp_mix_offset += 4;
|
||||
}
|
||||
|
||||
s32 samples_read{};
|
||||
while (samples_read < samples_to_read) {
|
||||
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
|
||||
// No more data can be read
|
||||
if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
|
||||
is_buffer_completed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
|
||||
wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
|
||||
// TODO(ogniK): ADPCM loop context
|
||||
}
|
||||
|
||||
s32 samples_decoded{0};
|
||||
switch (in_params.sample_format) {
|
||||
case SampleFormat::Pcm16:
|
||||
samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read,
|
||||
channel, temp_mix_offset);
|
||||
break;
|
||||
case SampleFormat::Adpcm:
|
||||
samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read,
|
||||
channel, temp_mix_offset);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
|
||||
}
|
||||
|
||||
temp_mix_offset += samples_decoded;
|
||||
samples_read += samples_decoded;
|
||||
dsp_state.offset += samples_decoded;
|
||||
dsp_state.played_sample_count += samples_decoded;
|
||||
|
||||
if (dsp_state.offset >=
|
||||
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) ||
|
||||
samples_decoded == 0) {
|
||||
// Reset our sample offset
|
||||
dsp_state.offset = 0;
|
||||
if (wave_buffer.is_looping) {
|
||||
if (samples_decoded == 0) {
|
||||
// End of our buffer
|
||||
is_buffer_completed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) {
|
||||
dsp_state.played_sample_count = 0;
|
||||
}
|
||||
} else {
|
||||
|
||||
// Update our wave buffer states
|
||||
dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
|
||||
dsp_state.wave_buffer_consumed++;
|
||||
dsp_state.wave_buffer_index =
|
||||
(dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
|
||||
if (wave_buffer.end_of_stream) {
|
||||
dsp_state.played_sample_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) {
|
||||
// No need to resample
|
||||
std::memcpy(output, sample_buffer.data(), samples_read * sizeof(s32));
|
||||
} else {
|
||||
std::fill(sample_buffer.begin() + temp_mix_offset,
|
||||
sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read),
|
||||
0);
|
||||
AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction,
|
||||
samples_to_output);
|
||||
// Resample
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
|
||||
dsp_state.sample_history[i] = sample_buffer[samples_to_read + i];
|
||||
}
|
||||
}
|
||||
output += samples_to_output;
|
||||
samples_remaining -= samples_to_output;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "audio_core/common.h"
|
||||
#include "audio_core/voice_context.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
class MixContext;
|
||||
class SplitterContext;
|
||||
class ServerSplitterDestinationData;
|
||||
class ServerMixInfo;
|
||||
class EffectContext;
|
||||
class EffectBase;
|
||||
struct AuxInfoDSP;
|
||||
using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
|
||||
|
||||
class CommandGenerator {
|
||||
public:
|
||||
explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
|
||||
VoiceContext& voice_context, MixContext& mix_context,
|
||||
SplitterContext& splitter_context, EffectContext& effect_context,
|
||||
Core::Memory::Memory& memory);
|
||||
~CommandGenerator();
|
||||
|
||||
void ClearMixBuffers();
|
||||
void GenerateVoiceCommands();
|
||||
void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
|
||||
void GenerateSubMixCommands();
|
||||
void GenerateFinalMixCommands();
|
||||
void PreCommand();
|
||||
void PostCommand();
|
||||
|
||||
s32* GetChannelMixBuffer(s32 channel);
|
||||
const s32* GetChannelMixBuffer(s32 channel) const;
|
||||
s32* GetMixBuffer(std::size_t index);
|
||||
const s32* GetMixBuffer(std::size_t index) const;
|
||||
std::size_t GetMixChannelBufferOffset(s32 channel) const;
|
||||
|
||||
std::size_t GetTotalMixBufferCount() const;
|
||||
|
||||
private:
|
||||
void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
|
||||
void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
|
||||
s32 mix_buffer_count, s32 channel);
|
||||
void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
|
||||
s32 node_id);
|
||||
void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
|
||||
const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state,
|
||||
s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index,
|
||||
s32 node_id);
|
||||
void GenerateSubMixCommand(ServerMixInfo& mix_info);
|
||||
void GenerateMixCommands(ServerMixInfo& mix_info);
|
||||
void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
|
||||
s32 node_id);
|
||||
void GenerateFinalMixCommand();
|
||||
void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
|
||||
std::array<s64, 2>& state, std::size_t input_offset,
|
||||
std::size_t output_offset, s32 sample_count, s32 node_id);
|
||||
void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count,
|
||||
std::size_t mix_buffer_offset);
|
||||
void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
|
||||
std::size_t mix_buffer_offset, s32 sample_rate);
|
||||
void GenerateEffectCommand(ServerMixInfo& mix_info);
|
||||
void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
|
||||
void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
|
||||
void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
|
||||
ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
|
||||
|
||||
s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data,
|
||||
u32 sample_count, u32 write_offset, u32 write_count);
|
||||
s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
|
||||
u32 sample_count, u32 read_offset, u32 read_count);
|
||||
|
||||
// DSP Code
|
||||
s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
|
||||
s32 channel, std::size_t mix_offset);
|
||||
s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
|
||||
s32 channel, std::size_t mix_offset);
|
||||
void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
|
||||
s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
|
||||
|
||||
AudioCommon::AudioRendererParameter& worker_params;
|
||||
VoiceContext& voice_context;
|
||||
MixContext& mix_context;
|
||||
SplitterContext& splitter_context;
|
||||
EffectContext& effect_context;
|
||||
Core::Memory::Memory& memory;
|
||||
std::vector<s32> mix_buffer{};
|
||||
std::vector<s32> sample_buffer{};
|
||||
std::vector<s32> depop_buffer{};
|
||||
bool dumping_frame{false};
|
||||
};
|
||||
} // namespace AudioCore
|
||||
@@ -8,13 +8,30 @@
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace AudioCore {
|
||||
namespace AudioCommon {
|
||||
namespace Audren {
|
||||
constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
|
||||
}
|
||||
constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
|
||||
} // namespace Audren
|
||||
|
||||
constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
|
||||
constexpr std::size_t MAX_MIX_BUFFERS = 24;
|
||||
constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
|
||||
constexpr std::size_t MAX_CHANNEL_COUNT = 6;
|
||||
constexpr std::size_t MAX_WAVE_BUFFERS = 4;
|
||||
constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
|
||||
constexpr u32 STREAM_SAMPLE_RATE = 48000;
|
||||
constexpr u32 STREAM_NUM_CHANNELS = 6;
|
||||
constexpr s32 NO_SPLITTER = -1;
|
||||
constexpr s32 NO_MIX = 0x7fffffff;
|
||||
constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
|
||||
constexpr s32 FINAL_MIX = 0;
|
||||
constexpr s32 NO_EFFECT_ORDER = -1;
|
||||
constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
|
||||
// Any size checks seem to take the sample history into account
|
||||
// and our const ends up being 0x3f04, the 4 bytes are most
|
||||
// likely the sample history
|
||||
constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
|
||||
|
||||
static constexpr u32 VersionFromRevision(u32_le rev) {
|
||||
// "REV7" -> 7
|
||||
@@ -45,4 +62,46 @@ static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
struct UpdateDataSizes {
|
||||
u32_le behavior{};
|
||||
u32_le memory_pool{};
|
||||
u32_le voice{};
|
||||
u32_le voice_channel_resource{};
|
||||
u32_le effect{};
|
||||
u32_le mixer{};
|
||||
u32_le sink{};
|
||||
u32_le performance{};
|
||||
u32_le splitter{};
|
||||
u32_le render_info{};
|
||||
INSERT_PADDING_WORDS(4);
|
||||
};
|
||||
static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
|
||||
|
||||
struct UpdateDataHeader {
|
||||
u32_le revision{};
|
||||
UpdateDataSizes size{};
|
||||
u32_le total_size{};
|
||||
};
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
|
||||
|
||||
struct AudioRendererParameter {
|
||||
u32_le sample_rate;
|
||||
u32_le sample_count;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le submix_count;
|
||||
u32_le voice_count;
|
||||
u32_le sink_count;
|
||||
u32_le effect_count;
|
||||
u32_le performance_frame_count;
|
||||
u8 is_voice_drop_enabled;
|
||||
u8 unknown_21;
|
||||
u8 unknown_22;
|
||||
u8 execution_mode;
|
||||
u32_le splitter_count;
|
||||
u32_le num_splitter_send_channels;
|
||||
u32_le unknown_30;
|
||||
u32_le revision;
|
||||
};
|
||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
|
||||
|
||||
} // namespace AudioCommon
|
||||
|
||||