Compare commits

..

71 Commits

Author SHA1 Message Date
Kyle Kienapfel
644dc6c93c build: override git description on mainline builds
Note: might not work for Early Access builds

right now it appears that the output of like:
git describe  --long --always --dirty
is something like: mainline-636-9139-g0ec190c16-dirty

and the title for yuzu-cmd: HEAD-mainline-636-9139-g0ec190c16-dirty

the goal here is to change this title to:
mainline-<build id>-<shorthash>

so as an example trying for: mainline-1107-0ec190c16

dirty flag on github actions builds are due to discord-rpc
and its desire to run clang-format on itself as part of
compiling. that library is retired and deprecated, so no updates.
2022-07-26 23:05:37 -07:00
liamwhite
97729fd8e9 Merge pull request #8545 from Kelebek1/Audio
Project Andio
2022-07-23 15:20:39 -04:00
liamwhite
6c4e48dac4 Merge pull request #8629 from Docteh/test_transifex
ci,transifex: enable vcpkg on transifex step
2022-07-23 15:11:37 -04:00
liamwhite
7284adf4d9 Merge pull request #8625 from Docteh/ado_titlebar
ci: pass environment variables to linux docker (AppImage)
2022-07-23 15:11:29 -04:00
liamwhite
072516dcae Merge pull request #8596 from Docteh/fix_gha
package MSVC CI Builds differently, and include yuzu.exe
2022-07-23 15:11:19 -04:00
Kyle Kienapfel
5878eb34f4 ci,transifex: enable vcpkg on transifex step
The slim docker container that runs transifex needs a few packages added
in, curl zip unzip

I've tested everything except actually pushing to transifex, but it's
not November 2022 yet so we're fine for now. Or we're actually using the
newer client and all is well.
2022-07-23 10:09:59 -07:00
liamwhite
a47fc62cea Merge pull request #8627 from lat9nq/submodule-clean
ci/windows: Cleanup unused data in submodules before packaging
2022-07-23 11:47:03 -04:00
Kyle Kienapfel
5cda630417 package MSVC CI Builds differently, and include yuzu.exe
This is related to 8486

Ninja places the exe files into .\build\bin while MSBuild may place them
into .\build\bin\Release

upload.ps1 was originally written for use with Azure Dev Ops to cough up
about 5 files and the script appears to be used for both CI and
mainline builds

GHA (GitHub Actions) makes available a single zip of the items uploaded by
each Upload action (artifacts directory), so we want to work with that.

I'm doing changes to upload.ps1 to accomplish this.

The changes to the verify.yml are as follows

-DGIT_BRANCH=pr-verify changes the header in yuzu, instead of saying
HEAD-<hash>-dirty it'll say pr-verify-<hash>

-DCLANG_FORMAT_SUFFIX=discordplzdontclang tricks the CMake stuff for
discord-rpc to NOT run clang-format, as this was marking CI builds as
dirty

I'm also making it upload just the exe by itself, as the msvc builds are
quite chunky. but maybe this is unnecessary.

Currently the MSVC artifact option is a 274MB zip that contains 3 copies
of the DLLs, and 4 copies of the source tarball, and zero copies of yuzu.exe

This PR should have msvc artifacts of about 190MB that downloads as 81 MB zip
2022-07-23 08:35:26 -07:00
lat9nq
1d700f1dfa ci/windows: Cleanup unused data before packaging
vcpkg data takes up a lot of space, and currently the scripts will
package all that data with the source archive which is unnecessary.
2022-07-23 10:16:44 -04:00
bunnei
da066e8ed8 Merge pull request #8584 from Morph1984/qt5-cleanup
CMakeLists: Require QtConcurrent, and remove unused dlls
2022-07-23 01:10:30 -07:00
Kyle K
dc45147736 ci: pass environment variables to linux docker (AppImage)
Variables in question:
AZURECIREPO TITLEBARFORMATIDLE TITLEBARFORMATRUNNING DISPLAYVERSION

CMakeModules/GenerateSCMRev.cmake has some logic that looks at BUILD_REPOSITORY variable inside CMake

src/common/CMakeLists.txt has some logic that takes some items from environment variables and
 sets variables inside CMake

This is the whole section at the moment.

    if (DEFINED ENV{AZURECIREPO})
      set(BUILD_REPOSITORY $ENV{AZURECIREPO})
    endif()
    if (DEFINED ENV{TITLEBARFORMATIDLE})
      set(TITLE_BAR_FORMAT_IDLE $ENV{TITLEBARFORMATIDLE})
    endif ()
    if (DEFINED ENV{TITLEBARFORMATRUNNING})
      set(TITLE_BAR_FORMAT_RUNNING $ENV{TITLEBARFORMATRUNNING})
    endif ()
    if (DEFINED ENV{DISPLAYVERSION})
      set(DISPLAY_VERSION $ENV{DISPLAYVERSION})
    endif ()
2022-07-22 18:13:06 -07:00
bunnei
d60b0e8655 Merge pull request #8611 from liamwhite/fix-flatpak-crash
video_core: use correct byte size for framebuffer
2022-07-22 18:04:17 -07:00
bunnei
58081a3664 Merge pull request #8624 from lat9nq/vcpkg
ci,CMake: Drop Conan support for vcpkg
2022-07-22 17:54:47 -07:00
lat9nq
5b4bef13e7 gitmodules: Remove 'externals' from names of submodules 2022-07-22 20:54:00 -04:00
lat9nq
265d1d6979 ci,CMake: Integrate vcpkg into CMakeLists
Uses manifest mode if the bundled vcpkg is used.
2022-07-22 20:54:00 -04:00
lat9nq
4b93ea59db ci,CMake: Drop Conan support for vcpkg
Between packages breaking, Conan always being a moving target for
minimum required CMake support, and now their moves to Conan 2.0 causing
existing packages to break, I suppose this was a long time coming. vcpkg
isn't without its drawbacks, but at the moment it seems easier on the
project to use for external packages.

Mostly removes the logic for Conan from the root CMakeLists file,
leaving basic find_package()'s in its place. Sets only the
find_package()'s that require CONFIG mode as necessary. clang and linux
CI now use the vcpkg toolchain file configured in the Docker container
when possible.

mingw CI turns off YUZU_TESTS because there's no way on the container to
run Windows executables on a Linux host anyway, and it's not easy to get
Catch2 there.
2022-07-22 20:54:00 -04:00
Kelebek1
458da8a948 Project Andio 2022-07-22 01:11:32 +01:00
bunnei
6e36f4d230 Merge pull request #8598 from Link4565/recv-dontwait
Enable the use of MSG_DONTWAIT flag on RecvImpl
2022-07-21 15:43:55 -07:00
Narr the Reg
bcd0ca6076 Merge pull request #8607 from lat9nq/sdl-2.0.20
externals: Revert SDL2 to release-2.0.20
2022-07-21 09:47:52 -05:00
Narr the Reg
a67776013d Merge pull request #8610 from yuzu-emu/Handheld
Rename Undocked to Handheld in input settings
2022-07-19 20:26:14 -05:00
Liam
382b41b18f video_core: use correct byte size for framebuffer 2022-07-19 17:46:26 -04:00
Matías Locatti
6f9bfb10d3 Update configure_input.ui 2022-07-19 16:20:16 -03:00
bunnei
aab25d9b22 Merge pull request #8604 from merryhime/A64CallbackConfigPass
externals: Update dynarmic to 6.2.1
2022-07-19 11:18:05 -07:00
lat9nq
f5964fe2a9 externals: Revert SDL2 to release-2.0.20
prerelease-2.23.1 appears to have issues on the SteamDeck with external
controllers. Revert to 2.0.20 for now (and as opposed to using
prerelease-2.0.19 like before.)
2022-07-18 21:43:29 -04:00
bunnei
caaf2368b6 Merge pull request #8581 from devsnek/send-resume
implement resume message
2022-07-18 12:21:14 -07:00
Gus Caplan
742f67908c implement resume message 2022-07-17 22:35:07 -07:00
Merry
b4ee3fa39a externals: Update dynarmic to 6.2.1
Fix issue with A64CallbackConfigPass
2022-07-17 22:44:34 +01:00
merry
09300abe92 Merge pull request #8569 from merryhime/watchpoints
dynarmic: Abort watchpoints ASAP
2022-07-17 22:41:28 +01:00
bunnei
ba8ea95624 Merge pull request #8508 from yuzu-emu/mc-speed-limit
hle: service: nvflinger: Factor speed limit into frame time calculation.
2022-07-17 13:59:52 -07:00
bunnei
a5bdf824e6 Merge pull request #8544 from german77/14dot0
service: Update some services to 14.0.0+
2022-07-17 12:30:52 -07:00
bunnei
e4eb7acd49 Merge pull request #8525 from lat9nq/update-sdl
externals/SDL: Update to prerelease-2.23.1
2022-07-17 00:07:20 -07:00
bunnei
8ca8281f4f Merge pull request #8543 from BreadFish64/use_tsc_from_caps
common/x64: Use TSC clock rate from CPUID when available
2022-07-16 23:14:38 -07:00
bunnei
6d160873c4 hle: service: nvflinger: Fix implicit conversion. 2022-07-16 23:11:42 -07:00
bunnei
02282477e7 yuzu: settings: Remove framerate cap and merge unlocked framerate setting.
- These were all somewhat redundant.
2022-07-16 23:11:39 -07:00
bunnei
f8aaa59990 hle: service: nvflinger: Factor speed limit into frame time calculation.
- This allows the %-based "Limit Speed Percent" setting to work with MC emulation.
- This is already supported for SC emulation.
2022-07-16 23:10:45 -07:00
bunnei
7d66f8339e Merge pull request #8593 from merryhime/ranged-setting-T
common/setting: Make ranged a property of the type
2022-07-16 15:32:52 -07:00
bunnei
b8813edc51 Merge pull request #8594 from liamwhite/skip-wp
core/arm: skip watchpoint checks when reading instructions
2022-07-16 13:28:20 -07:00
bunnei
87bb44830b Merge pull request #8511 from german77/hbmenu
service: ptm: Add TS, nifm: Stub GetInternetConnectionStatus
2022-07-16 11:30:56 -07:00
Link4565
912cae21b0 Enable the use of MSG_DONTWAIT flag on RecvImpl 2022-07-16 18:30:28 +01:00
bunnei
93a4ca11fa Merge pull request #8560 from liamwhite/bitfield-may-alias
common: fix bitfield aliasing on GCC/Clang
2022-07-15 22:16:52 -07:00
Liam
a9a9999efd core/arm: skip watchpoint checks when reading instructions 2022-07-15 19:47:28 -04:00
merry
99fbdaf75b common/setting: Make ranged a property of the type
- Avoids new GCC 12 warnings when Type is of form std::optional<T>
- Makes more sense this way, because ranged is not a property which would change over time
2022-07-15 18:45:55 +01:00
Morph
d2c0b45bca Merge pull request #8587 from merryhime/padding-unused
common_funcs: Mark padding as [[maybe_unused]]
2022-07-15 06:02:48 -04:00
Morph
8266f63130 Merge pull request #8588 from merryhime/IBinder-vdestruct
nvflinger: Polymorphic destructor requried for abstract class IBinder
2022-07-15 06:02:31 -04:00
Morph
037ce7cb5f Merge pull request #8586 from merryhime/KCodeMemory-override
KCodeMemory: Mark virtual methods as override
2022-07-15 06:02:23 -04:00
Merry
a1d2fb314e KCodeMemory: Mark virtual methods as override 2022-07-15 10:39:58 +01:00
Merry
914ead075e common_funcs: Mark padding as [[maybe_unused]] 2022-07-15 10:34:38 +01:00
Merry
30b23fb7b8 nvflinger: Polymorphic destructor requried for abstract class IBinder 2022-07-15 10:28:54 +01:00
Merry
40e39ddd46 dynarmic: Abort watchpoints ASAP 2022-07-15 10:03:30 +01:00
Morph
1002563776 CopyYuzuQt5Deps: Remove unused dlls 2022-07-15 00:57:42 -04:00
Morph
fc503c3445 CMakeLists: Mark WebEngine(Core/Widgets) as required
Mark these components as required when we are building with QtWebEngine enabled.
2022-07-15 00:50:51 -04:00
Morph
e991525d63 CMakeLists: Add QtConcurrent to required components
We use QtConcurrent in various places in our Qt frontend, add it to the required components.
2022-07-15 00:49:21 -04:00
liamwhite
53fb4a78a3 Merge pull request #8540 from lat9nq/copy-nv-ffmpeg
ci/windows: Copy what of FFmpeg not already present
2022-07-14 20:44:00 -04:00
liamwhite
1a85c18600 Merge pull request #8539 from Morph1984/gha-update-actions
ci: Update various actions from v2 to v3
2022-07-14 20:43:30 -04:00
liamwhite
381381c7e0 Merge pull request #8571 from merryhime/update-dynarmic
externals: Update dynarmic to 6.1.1
2022-07-14 20:41:51 -04:00
liamwhite
2fed6dd7e1 Merge pull request #8536 from Morph1984/fix-webapplet-input
qt_web_browser: Fix button inputs with QtWebEngine
2022-07-14 20:41:41 -04:00
liamwhite
9627c550a0 Merge pull request #8510 from german77/vibration
input_common: sdl: lower vibration frequency and use it's own unique thread
2022-07-14 20:41:29 -04:00
german77
1584de951a service: fatal: Add function table 2022-07-14 16:33:09 -05:00
german77
2535e9d1ec service: btdrv,bcat,btm: Update service tables to 14.0.0 2022-07-14 16:33:09 -05:00
german77
8e0e2e95e6 service am: Update service tables to 14.0.0 2022-07-14 16:33:08 -05:00
german77
32b522b1fd service: ac: Replace intances of ProfileData with UserData 2022-07-14 16:33:07 -05:00
Liam
a1c1ad096d common: fix bitfield aliasing on GCC/Clang 2022-07-09 22:43:45 -04:00
Marshall Mohror
e71d457af9 guard against div-by-zero 2022-07-06 13:00:00 -05:00
Marshall Mohror
b2ad4dd189 common/x64: Use TSC clock rate from CPUID when available
The current method used to estimate the TSC is fairly accurate - within a few kHz - but the exact value can be extracted from CPUID if available.
2022-07-06 12:42:01 -05:00
lat9nq
caef92a584 ci/windows: Copy what of FFmpeg not already present
Prevents overwriting libwinpthreads.dll when one should already be
present from the first DLL search.
2022-07-05 22:32:12 -04:00
Morph
4fa625c4fa ci: Update various actions from v2 to v3 2022-07-05 20:41:49 -04:00
Morph
cbef6b1fca qt_web_browser: Fix button inputs with QtWebEngine
Button inputs were broken as button was assumed to be the bit position of NpadButton prior to the input rewrite. Since this was changed to use NpadButton directly, we should count the number of trailing zeros to determine the bit position.
2022-07-05 20:34:10 -04:00
lat9nq
4f847621af externals/SDL: Update to prerelease-2.23.1
This includes a fix needed for building with MSYS2/MinGW.
2022-06-30 18:56:38 -04:00
german77
b38509b030 service: nifm: Stub GetInternetConnectionStatus 2022-06-28 19:22:46 -05:00
german77
c0264d2121 service: ptm: Rewrite PSM and add TS 2022-06-28 19:22:46 -05:00
german77
5e7e55b98a input_common: sdl: lower vibration frequency and use it's own unique thread 2022-06-28 19:22:16 -05:00
330 changed files with 34361 additions and 8963 deletions

View File

@@ -6,7 +6,17 @@ set -e
ccache -s
mkdir build || true && cd build
cmake .. -GNinja -DDISPLAY_VERSION=$1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/clang -DCMAKE_CXX_COMPILER=/usr/lib/ccache/clang++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_INSTALL_PREFIX="/usr"
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_COMPILER=/usr/lib/ccache/clang++ \
-DCMAKE_C_COMPILER=/usr/lib/ccache/clang \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DDISPLAY_VERSION=$1 \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DENABLE_QT_TRANSLATION=ON \
-DUSE_DISCORD_PRESENCE=ON \
-DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
-GNinja
ninja

View File

@@ -4,5 +4,10 @@ mkdir -p "ccache" || true
chmod a+x ./.ci/scripts/linux/docker.sh
# the UID for the container yuzu user is 1027
sudo chown -R 1027 ./
docker run -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v "$(pwd):/yuzu" -w /yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh "$1"
# The environment variables listed below:
# AZURECIREPO TITLEBARFORMATIDLE TITLEBARFORMATRUNNING DISPLAYVERSION
# are requested in src/common/CMakeLists.txt and appear to be provided somewhere in Azure DevOps
docker run -e AZURECIREPO -e TITLEBARFORMATIDLE -e TITLEBARFORMATRUNNING -e DISPLAYVERSION -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v "$(pwd):/yuzu" -w /yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh "$1"
sudo chown -R $UID ./

View File

@@ -16,8 +16,11 @@ cmake --version
gcc -v
tx --version
# vcpkg needs these: curl zip unzip tar, have tar
apt-get install -y curl zip unzip
mkdir build && cd build
cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF -DYUZU_TESTS=OFF -DYUZU_USE_BUNDLED_VCPKG=ON
make translation
cd ..

View File

@@ -6,10 +6,6 @@ set -e
ccache -sv
mkdir -p "$HOME/.conan/profiles"
wget -c "https://github.com/yuzu-emu/build-environments/raw/master/linux-mingw/default" -O "$HOME/.conan/profiles/default"
wget -c "https://github.com/yuzu-emu/build-environments/raw/master/linux-mingw/settings.yml" -O "$HOME/.conan/settings.yml"
mkdir -p build && cd build
export LDFLAGS="-fuse-ld=lld"
# -femulated-tls required due to an incompatibility between GCC and Clang
@@ -24,6 +20,7 @@ cmake .. \
-DUSE_CCACHE=ON \
-DYUZU_USE_BUNDLED_SDL2=OFF \
-DYUZU_USE_EXTERNAL_SDL2=OFF \
-DYUZU_TESTS=OFF \
-GNinja
ninja yuzu yuzu-cmd
@@ -65,7 +62,7 @@ python3 .ci/scripts/windows/scan_dll.py package/*.exe package/imageformats/*.dll
# copy FFmpeg libraries
EXTERNALS_PATH="$(pwd)/build/externals"
FFMPEG_DLL_PATH="$(find "${EXTERNALS_PATH}" -maxdepth 1 -type d | grep 'ffmpeg-')/bin"
find ${FFMPEG_DLL_PATH} -type f -regex ".*\.dll" -exec cp -v {} package/ ';'
find ${FFMPEG_DLL_PATH} -type f -regex ".*\.dll" -exec cp -nv {} package/ ';'
# copy libraries from yuzu.exe path
find "$(pwd)/build/bin/" -type f -regex ".*\.dll" -exec cp -v {} package/ ';'

View File

@@ -25,6 +25,9 @@ $env:BUILD_UPDATE = $MSVC_SEVENZIP
$BUILD_DIR = ".\build\bin\Release"
# Cleanup unneeded data in submodules
git submodule foreach git clean -fxd
# Upload debugging symbols
mkdir pdb
Get-ChildItem "$BUILD_DIR\" -Recurse -Filter "*.pdb" | Copy-Item -destination .\pdb
@@ -47,6 +50,49 @@ Copy-Item .\CMakeModules -Recurse -Destination $MSVC_SOURCE
7z a -r -ttar $MSVC_SOURCE_TAR $MSVC_SOURCE
7z a -r -txz $MSVC_SOURCE_TARXZ $MSVC_SOURCE_TAR
# Following section is quick hack to package artifacts differently for GitHub Actions
if ("$env:GITHUB_ACTIONS" -eq "true") {
echo "Hello GitHub Actions"
# Hopefully there is an exe in either .\build\bin or .\build\bin\Release
cp .\build\bin\yuzu*.exe .\artifacts\
Copy-Item "$BUILD_DIR\*" -Destination "artifacts" -Recurse
Remove-Item .\artifacts\tests.exe -ErrorAction ignore
# None of the other GHA builds are including source, so commenting out today
#Copy-Item $MSVC_SOURCE_TARXZ -Destination "artifacts"
# Are debug symbols important?
# cp .\build\bin\yuzu*.pdb .\pdb\
# Write out a tag BUILD_TAG to environment for the Upload step
# We're getting ${{ github.event.number }} as $env:PR_NUMBER"
echo "env:PR_NUMBER: $env:PR_NUMBER"
if (Test-Path env:PR_NUMBER) {
$PR_NUMBER = $env:PR_NUMBER.Substring(2) -as [int]
$PR_NUMBER_TAG = "pr"+([string]$PR_NUMBER).PadLeft(5,'0')
if ($PR_NUMBER -gt 1){
$BUILD_TAG="verify-$PR_NUMBER_TAG-$GITDATE-$GITREV"
} else {
$BUILD_TAG = "verify-$GITDATE-$GITREV"
}
} else {
# If env:PR_NUMBER isn't set, we should still write out a variable
$BUILD_TAG = "verify-$GITDATE-$GITREV"
}
echo "BUILD_TAG=$BUILD_TAG"
echo "BUILD_TAG=$BUILD_TAG" >> $env:GITHUB_ENV
# For extra job, just the exe
$INDIVIDUAL_EXE = "yuzu-msvc-$BUILD_TAG.exe"
echo "INDIVIDUAL_EXE=$INDIVIDUAL_EXE"
echo "INDIVIDUAL_EXE=$INDIVIDUAL_EXE" >> $env:GITHUB_ENV
echo "Just the exe: $INDIVIDUAL_EXE"
cp .\artifacts\yuzu.exe .\$INDIVIDUAL_EXE
} else {
# Build the final release artifacts
Copy-Item $MSVC_SOURCE_TARXZ -Destination $RELEASE_DIST
Copy-Item "$BUILD_DIR\*" -Destination $RELEASE_DIST -Recurse
@@ -62,3 +108,7 @@ Get-ChildItem "$BUILD_DIR" -Recurse -Filter "QtWebEngineProcess*.exe" | Copy-Ite
Get-ChildItem . -Filter "*.zip" | Copy-Item -destination "artifacts"
Get-ChildItem . -Filter "*.7z" | Copy-Item -destination "artifacts"
Get-ChildItem . -Filter "*.tar.xz" | Copy-Item -destination "artifacts"
}
# Extra items
git status
cp .\build\src\common\scm_rev.cpp .\artifacts

View File

@@ -6,9 +6,7 @@ parameters:
steps:
- script: choco install vulkan-sdk
displayName: 'Install vulkan-sdk'
- script: python -m pip install --upgrade pip conan
displayName: 'Install conan'
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release .. && cd ..
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_TESTS=OFF -DYUZU_USE_BUNDLED_VCPKG=ON .. && cd ..
displayName: 'Configure CMake'
- task: MSBuild@1
displayName: 'Build'

View File

@@ -13,7 +13,7 @@ jobs:
container: yuzuemu/build-environments:linux-transifex
if: ${{ github.repository == 'yuzu-emu/yuzu' && !github.head_ref }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0

View File

@@ -3,6 +3,8 @@ name: 'yuzu verify'
on:
pull_request:
branches: [ master ]
env:
PR_NUMBER: pr${{ github.event.number }}
jobs:
format:
@@ -12,7 +14,7 @@ jobs:
image: yuzuemu/build-environments:linux-clang-format
options: -u 1001
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: false
- name: 'Verify Formatting'
@@ -35,12 +37,12 @@ jobs:
image: yuzuemu/build-environments:${{ matrix.image }}
options: -u 1001
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- name: Set up cache
uses: actions/cache@v2
uses: actions/cache@v3
id: ccache-restore
with:
path: ~/.ccache
@@ -69,7 +71,7 @@ jobs:
runs-on: windows-2019
steps:
- name: Set up cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.buildcache
key: ${{ runner.os }}-msvc-${{ github.sha }}
@@ -80,7 +82,6 @@ jobs:
shell: cmd
run: |
choco install vulkan-sdk wget
python -m pip install --upgrade pip conan
call refreshenv
wget https://github.com/mbitsnbites/buildcache/releases/download/v0.27.6/buildcache-windows.zip
7z x buildcache-windows.zip
@@ -89,7 +90,7 @@ jobs:
echo %PATH% >> %GITHUB_PATH%
- name: Set up MSVC
uses: ilammy/msvc-dev-cmd@v1
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
@@ -100,7 +101,7 @@ jobs:
run: |
glslangValidator --version
mkdir build
cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release
cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify -DCLANG_FORMAT_SUFFIX=discordplzdontclang -DYUZU_TESTS=OFF -DYUZU_USE_BUNDLED_VCPKG=ON
- name: Build
run: cmake --build build
- name: Cache Summary
@@ -113,3 +114,8 @@ jobs:
with:
name: msvc
path: artifacts/
- name: Upload EXE
uses: actions/upload-artifact@v3
with:
name: ${{ env.INDIVIDUAL_EXE }}
path: ${{ env.INDIVIDUAL_EXE }}

9
.gitmodules vendored
View File

@@ -3,7 +3,7 @@
url = https://github.com/benhoyt/inih.git
[submodule "cubeb"]
path = externals/cubeb
url = https://github.com/kinetiknz/cubeb.git
url = https://github.com/mozilla/cubeb.git
[submodule "dynarmic"]
path = externals/dynarmic
url = https://github.com/MerryMage/dynarmic.git
@@ -34,9 +34,12 @@
[submodule "SDL"]
path = externals/SDL
url = https://github.com/libsdl-org/SDL.git
[submodule "externals/cpp-httplib"]
[submodule "cpp-httplib"]
path = externals/cpp-httplib
url = https://github.com/yhirose/cpp-httplib.git
[submodule "externals/ffmpeg/ffmpeg"]
[submodule "ffmpeg"]
path = externals/ffmpeg/ffmpeg
url = https://git.ffmpeg.org/ffmpeg.git
[submodule "vcpkg"]
path = externals/vcpkg
url = https://github.com/Microsoft/vcpkg.git

View File

@@ -35,6 +35,16 @@ option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON)
option(YUZU_TESTS "Compile tests" ON)
option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" OFF)
if (YUZU_USE_BUNDLED_VCPKG)
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
# Disable manifest mode (use vcpkg classic mode) when using a custom vcpkg installation
option(VCPKG_MANIFEST_MODE "")
include("$ENV{VCPKG_TOOLCHAIN_FILE}")
endif()
# Default to a Release build
get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
@@ -144,82 +154,34 @@ endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
# System imported libraries
# If not found, download any missing through Conan
# =======================================================================
set(CONAN_CMAKE_SILENT_OUTPUT TRUE)
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
if (YUZU_CONAN_INSTALLED)
if (IS_MULTI_CONFIG)
include(${CMAKE_BINARY_DIR}/conanbuildinfo_multi.cmake)
else()
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR}")
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}")
conan_basic_setup()
message(STATUS "Adding conan installed libraries to the search path")
find_package(fmt 8.0.1 REQUIRED CONFIG)
find_package(lz4 1.8 REQUIRED)
find_package(nlohmann_json 3.8 REQUIRED CONFIG)
find_package(ZLIB 1.2 REQUIRED)
# Search for config-only package first (for vcpkg), then try non-config
find_package(zstd 1.5 CONFIG)
if (NOT zstd_FOUND)
find_package(zstd 1.5 REQUIRED)
endif()
macro(yuzu_find_packages)
set(options FORCE_REQUIRED)
cmake_parse_arguments(FN "${options}" "" "" ${ARGN})
if (YUZU_TESTS)
find_package(Catch2 2.13.7 REQUIRED CONFIG)
endif()
# Cmake has a *serious* lack of 2D array or associative array...
# Capitalization matters here. We need the naming to match the generated paths from Conan
set(REQUIRED_LIBS
# Cmake Pkg Prefix Version Conan Pkg
"fmt 8.0.1 fmt/8.1.1"
"lz4 1.8 lz4/1.9.2"
"nlohmann_json 3.8 nlohmann_json/3.8.0"
"ZLIB 1.2 zlib/1.2.11"
"zstd 1.5 zstd/1.5.0"
# can't use opus until AVX check is fixed: https://github.com/yuzu-emu/yuzu/pull/4068
#"opus 1.3 opus/1.3.1"
)
if (YUZU_TESTS)
list(APPEND REQUIRED_LIBS
"Catch2 2.13.7 catch2/2.13.7"
)
endif()
foreach(PACKAGE ${REQUIRED_LIBS})
string(REGEX REPLACE "[ \t\r\n]+" ";" PACKAGE_SPLIT ${PACKAGE})
list(GET PACKAGE_SPLIT 0 PACKAGE_PREFIX)
list(GET PACKAGE_SPLIT 1 PACKAGE_VERSION)
list(GET PACKAGE_SPLIT 2 PACKAGE_CONAN)
# This function is called twice, once to check if the packages exist on the system already
# and a second time to check if conan installed them properly. The second check passes in FORCE_REQUIRED
if (NOT ${PACKAGE_PREFIX}_FOUND)
if (FN_FORCE_REQUIRED)
find_package(${PACKAGE_PREFIX} ${PACKAGE_VERSION} REQUIRED)
else()
find_package(${PACKAGE_PREFIX} ${PACKAGE_VERSION})
endif()
endif()
if (NOT ${PACKAGE_PREFIX}_FOUND)
list(APPEND CONAN_REQUIRED_LIBS ${PACKAGE_CONAN})
else()
# Set a legacy findPackage.cmake style PACKAGE_LIBRARIES variable for subprojects that rely on this
set(${PACKAGE_PREFIX}_LIBRARIES "${PACKAGE_PREFIX}::${PACKAGE_PREFIX}")
endif()
endforeach()
unset(FN_FORCE_REQUIRED)
endmacro()
find_package(Boost 1.73.0 COMPONENTS context headers)
find_package(Boost 1.73.0 COMPONENTS context)
if (Boost_FOUND)
set(Boost_LIBRARIES Boost::boost)
# Conditionally add Boost::context only if the active version of the Conan or system Boost package provides it
# Conditionally add Boost::context only if the found Boost package provides it
# The old version is missing Boost::context, so we want to avoid adding in that case
# The new version requires adding Boost::context to prevent linking issues
#
# This one is used by Conan on subsequent CMake configures, not the first configure.
if (TARGET Boost::context)
list(APPEND Boost_LIBRARIES Boost::context)
endif()
else()
message(STATUS "Boost 1.79.0 or newer not found, falling back to Conan")
list(APPEND CONAN_REQUIRED_LIBS "boost/1.79.0")
message(FATAL_ERROR "Boost 1.73.0 or newer not found")
endif()
# boost:asio has functions that require AcceptEx et al
@@ -227,19 +189,9 @@ if (MINGW)
find_library(MSWSOCK_LIBRARY mswsock REQUIRED)
endif()
# Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS
yuzu_find_packages()
# Qt5 requires that we find components, so it doesn't fit our pretty little find package function
if(ENABLE_QT)
set(QT_VERSION 5.15)
# We want to load the generated conan qt config so that we get the QT_ROOT var so that we can use the official
# Qt5Config inside the root folder instead of the conan generated one.
if(EXISTS ${CMAKE_BINARY_DIR}/qtConfig.cmake)
include(${CMAKE_BINARY_DIR}/qtConfig.cmake)
list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}")
list(APPEND CMAKE_PREFIX_PATH "${CONAN_QT_ROOT_RELEASE}")
endif()
# Check for system Qt on Linux, fallback to bundled Qt
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
@@ -330,9 +282,6 @@ if(ENABLE_QT)
set(YUZU_QT_NO_CMAKE_SYSTEM_PATH)
# Workaround for an issue where conan tries to build Qt from scratch instead of download prebuilt binaries
set(QT_PREFIX_HINT)
if(YUZU_USE_BUNDLED_QT)
if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
set(QT_BUILD qt-5.15.2-msvc2019_64)
@@ -351,12 +300,12 @@ if(ENABLE_QT)
set(YUZU_QT_NO_CMAKE_SYSTEM_PATH "NO_CMAKE_SYSTEM_PATH")
endif()
if ((${CMAKE_SYSTEM_NAME} STREQUAL "Linux") AND YUZU_USE_BUNDLED_QT)
find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets DBus ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Concurrent DBus ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
else()
find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Concurrent ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
endif()
if (YUZU_USE_QT_WEB_ENGINE)
find_package(Qt5 COMPONENTS WebEngineCore WebEngineWidgets)
find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets)
endif()
if (ENABLE_QT_TRANSLATION)
@@ -403,71 +352,8 @@ if (ENABLE_SDL2)
endif()
endif()
# Install any missing dependencies with conan install
if (CONAN_REQUIRED_LIBS)
message(STATUS "Packages ${CONAN_REQUIRED_LIBS} not found!")
# Use Conan to fetch the libraries that aren't found
# Download conan.cmake automatically, you can also just copy the conan.cmake file
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/release/0.18/conan.cmake" "${CMAKE_BINARY_DIR}/conan.cmake")
endif()
include(${CMAKE_BINARY_DIR}/conan.cmake)
conan_check(VERSION 1.45.0 REQUIRED)
# Manually add iconv to fix a dep conflict between qt and sdl2
# We don't need to add it through find_package or anything since the other two can find it just fine
if ("${CONAN_REQUIRED_LIBS}" MATCHES "qt" AND "${CONAN_REQUIRED_LIBS}" MATCHES "sdl")
list(APPEND CONAN_REQUIRED_LIBS "libiconv/1.16")
endif()
if (IS_MULTI_CONFIG)
conan_cmake_run(REQUIRES ${CONAN_REQUIRED_LIBS}
OPTIONS ${CONAN_LIB_OPTIONS}
BUILD missing
CONFIGURATION_TYPES "Release;Debug"
GENERATORS cmake_multi cmake_find_package_multi)
include(${CMAKE_BINARY_DIR}/conanbuildinfo_multi.cmake)
else()
conan_cmake_run(REQUIRES ${CONAN_REQUIRED_LIBS}
OPTIONS ${CONAN_LIB_OPTIONS}
BUILD missing
GENERATORS cmake cmake_find_package_multi)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR}")
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}")
conan_basic_setup()
set(YUZU_CONAN_INSTALLED TRUE CACHE BOOL "If true, the following builds will add conan to the lib search path" FORCE)
# Now that we've installed what we are missing, try to locate them again,
# this time with required, so we bail if its not found.
yuzu_find_packages(FORCE_REQUIRED)
if (NOT Boost_FOUND)
find_package(Boost 1.73.0 REQUIRED COMPONENTS context headers)
set(Boost_LIBRARIES Boost::boost)
# Conditionally add Boost::context only if the active version of the Conan Boost package provides it
# The old version is missing Boost::context, so we want to avoid adding in that case
# The new version requires adding Boost::context to prevent linking issues
if (TARGET Boost::context)
list(APPEND Boost_LIBRARIES Boost::context)
endif()
endif()
# Due to issues with variable scopes in functions, we need to also find_package(qt5) outside of the function
if(ENABLE_QT)
list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}")
list(APPEND CMAKE_PREFIX_PATH "${CONAN_QT_ROOT_RELEASE}")
find_package(Qt5 5.15 REQUIRED COMPONENTS Widgets)
if (YUZU_USE_QT_WEB_ENGINE)
find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets)
endif()
endif()
endif()
# TODO(lat9nq): Determine what if any of this we still need
#
# Reexport some targets that are named differently when using the upstream CmakeConfig vs the generated Conan config
# In order to ALIAS targets to a new name, they first need to be IMPORTED_GLOBAL
# Dynarmic checks for target `boost` and so we want to make sure it can find it through our system instead of using their external

View File

@@ -19,9 +19,6 @@ function(copy_yuzu_Qt5_deps target_dir)
set(IMAGEFORMATS ${DLL_DEST}plugins/imageformats/)
if (MSVC)
windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST}
icudt*.dll
icuin*.dll
icuuc*.dll
Qt5Core$<$<CONFIG:Debug>:d>.*
Qt5Gui$<$<CONFIG:Debug>:d>.*
Qt5Widgets$<$<CONFIG:Debug>:d>.*
@@ -37,18 +34,17 @@ function(copy_yuzu_Qt5_deps target_dir)
Qt5Quick$<$<CONFIG:Debug>:d>.*
Qt5QuickWidgets$<$<CONFIG:Debug>:d>.*
Qt5WebChannel$<$<CONFIG:Debug>:d>.*
Qt5WebEngine$<$<CONFIG:Debug>:d>.*
Qt5WebEngineCore$<$<CONFIG:Debug>:d>.*
Qt5WebEngineWidgets$<$<CONFIG:Debug>:d>.*
QtWebEngineProcess$<$<CONFIG:Debug>:d>.*
)
windows_copy_files(${target_dir} ${Qt5_RESOURCES_DIR} ${DLL_DEST}
qtwebengine_resources.pak
icudtl.dat
qtwebengine_devtools_resources.pak
qtwebengine_resources.pak
qtwebengine_resources_100p.pak
qtwebengine_resources_200p.pak
icudtl.dat
)
endif ()
windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
@@ -56,7 +52,7 @@ function(copy_yuzu_Qt5_deps target_dir)
windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS}
qjpeg$<$<CONFIG:Debug>:d>.*
qgif$<$<CONFIG:Debug>:d>.*
)
)
else()
set(Qt5_DLLS
"${Qt5_DLL_DIR}libQt5Core.so.5"

View File

@@ -38,6 +38,21 @@ if (BUILD_REPOSITORY)
string(TOUPPER ${FIRST_LETTER} FIRST_LETTER)
set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER}")
endforeach()
# git describe output looks odd for mainline builds, override this
string(SUBSTRING "${GIT_REV}" 0 9 GIT_REV_SHORT)
if (GIT_BRANCH STREQUAL "HEAD" AND BUILD_ID AND REPO_NAME STREQUAL "Mainline")
set(GIT_BRANCH "mainline")
set(GIT_DESC "${BUILD_ID}-${GIT_REV_SHORT}")
message(STATUS "mainline, overrode GIT_BRANCH: ${GIT_BRANCH} GIT_DESC: ${GIT_DESC}")
elseif (BUILD_ID AND REPO_NAME STREQUAL "Mainline")
set(GIT_DESC "mainline-${BUILD_ID}-${GIT_REV_SHORT}")
message(STATUS "mainline, overrode GIT_DESC: ${GIT_DESC}")
elseif (BUILD_ID AND REPO_NAME STREQUAL "Early Access")
set(GIT_DESC "ea-${BUILD_ID}-${GIT_REV_SHORT}")
message(STATUS "ea, overrode GIT_DESC: ${GIT_DESC}")
endif()
if (BUILD_TAG)
string(REGEX MATCH "${CMAKE_MATCH_1}-([0-9]+)" OUTVAR ${BUILD_TAG})
if (${CMAKE_MATCH_COUNT} GREATER 0)

2
externals/SDL vendored

1
externals/vcpkg vendored Submodule

Submodule externals/vcpkg added at cef0b3ec76

View File

@@ -1,54 +1,218 @@
add_library(audio_core STATIC
algorithm/filter.cpp
algorithm/filter.h
algorithm/interpolate.cpp
algorithm/interpolate.h
audio_out.cpp
audio_out.h
audio_renderer.cpp
audio_renderer.h
behavior_info.cpp
behavior_info.h
buffer.h
codec.cpp
codec.h
command_generator.cpp
command_generator.h
common.h
delay_line.cpp
delay_line.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
voice_context.cpp
voice_context.h
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
$<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h>
audio_core.cpp
audio_core.h
audio_event.h
audio_event.cpp
audio_render_manager.cpp
audio_render_manager.h
audio_in_manager.cpp
audio_in_manager.h
audio_out_manager.cpp
audio_out_manager.h
audio_manager.cpp
audio_manager.h
common/audio_renderer_parameter.h
common/common.h
common/feature_support.h
common/wave_buffer.h
common/workbuffer_allocator.h
device/audio_buffer.h
device/audio_buffers.h
device/device_session.cpp
device/device_session.h
in/audio_in.cpp
in/audio_in.h
in/audio_in_system.cpp
in/audio_in_system.h
out/audio_out.cpp
out/audio_out.h
out/audio_out_system.cpp
out/audio_out_system.h
renderer/adsp/adsp.cpp
renderer/adsp/adsp.h
renderer/adsp/audio_renderer.cpp
renderer/adsp/audio_renderer.h
renderer/adsp/command_buffer.h
renderer/adsp/command_list_processor.cpp
renderer/adsp/command_list_processor.h
renderer/audio_device.cpp
renderer/audio_device.h
renderer/audio_renderer.h
renderer/audio_renderer.cpp
renderer/behavior/behavior_info.cpp
renderer/behavior/behavior_info.h
renderer/behavior/info_updater.cpp
renderer/behavior/info_updater.h
renderer/command/data_source/adpcm.cpp
renderer/command/data_source/adpcm.h
renderer/command/data_source/decode.cpp
renderer/command/data_source/decode.h
renderer/command/data_source/pcm_float.cpp
renderer/command/data_source/pcm_float.h
renderer/command/data_source/pcm_int16.cpp
renderer/command/data_source/pcm_int16.h
renderer/command/effect/aux_.cpp
renderer/command/effect/aux_.h
renderer/command/effect/biquad_filter.cpp
renderer/command/effect/biquad_filter.h
renderer/command/effect/capture.cpp
renderer/command/effect/capture.h
renderer/command/effect/compressor.cpp
renderer/command/effect/compressor.h
renderer/command/effect/delay.cpp
renderer/command/effect/delay.h
renderer/command/effect/i3dl2_reverb.cpp
renderer/command/effect/i3dl2_reverb.h
renderer/command/effect/light_limiter.cpp
renderer/command/effect/light_limiter.h
renderer/command/effect/multi_tap_biquad_filter.cpp
renderer/command/effect/multi_tap_biquad_filter.h
renderer/command/effect/reverb.cpp
renderer/command/effect/reverb.h
renderer/command/mix/clear_mix.cpp
renderer/command/mix/clear_mix.h
renderer/command/mix/copy_mix.cpp
renderer/command/mix/copy_mix.h
renderer/command/mix/depop_for_mix_buffers.cpp
renderer/command/mix/depop_for_mix_buffers.h
renderer/command/mix/depop_prepare.cpp
renderer/command/mix/depop_prepare.h
renderer/command/mix/mix.cpp
renderer/command/mix/mix.h
renderer/command/mix/mix_ramp.cpp
renderer/command/mix/mix_ramp.h
renderer/command/mix/mix_ramp_grouped.cpp
renderer/command/mix/mix_ramp_grouped.h
renderer/command/mix/volume.cpp
renderer/command/mix/volume.h
renderer/command/mix/volume_ramp.cpp
renderer/command/mix/volume_ramp.h
renderer/command/performance/performance.cpp
renderer/command/performance/performance.h
renderer/command/resample/downmix_6ch_to_2ch.cpp
renderer/command/resample/downmix_6ch_to_2ch.h
renderer/command/resample/resample.h
renderer/command/resample/resample.cpp
renderer/command/resample/upsample.cpp
renderer/command/resample/upsample.h
renderer/command/sink/device.cpp
renderer/command/sink/device.h
renderer/command/sink/circular_buffer.cpp
renderer/command/sink/circular_buffer.h
renderer/command/command_buffer.cpp
renderer/command/command_buffer.h
renderer/command/command_generator.cpp
renderer/command/command_generator.h
renderer/command/command_list_header.h
renderer/command/command_processing_time_estimator.cpp
renderer/command/command_processing_time_estimator.h
renderer/command/commands.h
renderer/command/icommand.h
renderer/effect/aux_.cpp
renderer/effect/aux_.h
renderer/effect/biquad_filter.cpp
renderer/effect/biquad_filter.h
renderer/effect/buffer_mixer.cpp
renderer/effect/buffer_mixer.h
renderer/effect/capture.cpp
renderer/effect/capture.h
renderer/effect/compressor.cpp
renderer/effect/compressor.h
renderer/effect/delay.cpp
renderer/effect/delay.h
renderer/effect/effect_context.cpp
renderer/effect/effect_context.h
renderer/effect/effect_info_base.h
renderer/effect/effect_reset.h
renderer/effect/effect_result_state.h
renderer/effect/i3dl2.cpp
renderer/effect/i3dl2.h
renderer/effect/light_limiter.cpp
renderer/effect/light_limiter.h
renderer/effect/reverb.h
renderer/effect/reverb.cpp
renderer/mix/mix_context.cpp
renderer/mix/mix_context.h
renderer/mix/mix_info.cpp
renderer/mix/mix_info.h
renderer/memory/address_info.h
renderer/memory/memory_pool_info.cpp
renderer/memory/memory_pool_info.h
renderer/memory/pool_mapper.cpp
renderer/memory/pool_mapper.h
renderer/nodes/bit_array.h
renderer/nodes/edge_matrix.cpp
renderer/nodes/edge_matrix.h
renderer/nodes/node_states.cpp
renderer/nodes/node_states.h
renderer/performance/detail_aspect.cpp
renderer/performance/detail_aspect.h
renderer/performance/entry_aspect.cpp
renderer/performance/entry_aspect.h
renderer/performance/performance_detail.h
renderer/performance/performance_entry.h
renderer/performance/performance_entry_addresses.h
renderer/performance/performance_frame_header.h
renderer/performance/performance_manager.cpp
renderer/performance/performance_manager.h
renderer/sink/circular_buffer_sink_info.cpp
renderer/sink/circular_buffer_sink_info.h
renderer/sink/device_sink_info.cpp
renderer/sink/device_sink_info.h
renderer/sink/sink_context.cpp
renderer/sink/sink_context.h
renderer/sink/sink_info_base.cpp
renderer/sink/sink_info_base.h
renderer/splitter/splitter_context.cpp
renderer/splitter/splitter_context.h
renderer/splitter/splitter_destinations_data.cpp
renderer/splitter/splitter_destinations_data.h
renderer/splitter/splitter_info.cpp
renderer/splitter/splitter_info.h
renderer/system.cpp
renderer/system.h
renderer/system_manager.cpp
renderer/system_manager.h
renderer/upsampler/upsampler_info.h
renderer/upsampler/upsampler_manager.cpp
renderer/upsampler/upsampler_manager.h
renderer/upsampler/upsampler_state.h
renderer/voice/voice_channel_resource.h
renderer/voice/voice_context.cpp
renderer/voice/voice_context.h
renderer/voice/voice_info.cpp
renderer/voice/voice_info.h
renderer/voice/voice_state.h
sink/cubeb_sink.cpp
sink/cubeb_sink.h
sink/null_sink.h
sink/sdl2_sink.cpp
sink/sdl2_sink.h
sink/sink.h
sink/sink_details.cpp
sink/sink_details.h
sink/sink_stream.h
)
create_target_directory_groups(audio_core)
if (NOT MSVC)
if (MSVC)
target_compile_options(audio_core PRIVATE
/we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
/we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data
/we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch
/we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
/we4456 # Declaration of 'identifier' hides previous local declaration
/we4457 # Declaration of 'identifier' hides function parameter
/we4458 # Declaration of 'identifier' hides class member
/we4459 # Declaration of 'identifier' hides global declaration
)
else()
target_compile_options(audio_core PRIVATE
-Werror=conversion
-Werror=ignored-qualifiers
-Werror=shadow
-Werror=unused-variable
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
@@ -58,6 +222,9 @@ if (NOT MSVC)
endif()
target_link_libraries(audio_core PUBLIC common core)
if (ARCHITECTURE_x86_64)
target_link_libraries(audio_core PRIVATE dynarmic)
endif()
if(ENABLE_CUBEB)
target_link_libraries(audio_core PRIVATE cubeb)

View File

@@ -1,79 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#define _USE_MATH_DEFINES
#include <algorithm>
#include <array>
#include <cmath>
#include <vector>
#include "audio_core/algorithm/filter.h"
#include "common/common_types.h"
namespace AudioCore {
Filter Filter::LowPass(double cutoff, double Q) {
const double w0 = 2.0 * M_PI * cutoff;
const double sin_w0 = std::sin(w0);
const double cos_w0 = std::cos(w0);
const double alpha = sin_w0 / (2 * Q);
const double a0 = 1 + alpha;
const double a1 = -2.0 * cos_w0;
const double a2 = 1 - alpha;
const double b0 = 0.5 * (1 - cos_w0);
const double b1 = 1.0 * (1 - cos_w0);
const double b2 = 0.5 * (1 - cos_w0);
return {a0, a1, a2, b0, b1, b2};
}
Filter::Filter() : Filter(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {}
Filter::Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_)
: a1(a1_ / a0_), a2(a2_ / a0_), b0(b0_ / a0_), b1(b1_ / a0_), b2(b2_ / a0_) {}
void Filter::Process(std::vector<s16>& signal) {
const std::size_t num_frames = signal.size() / 2;
for (std::size_t i = 0; i < num_frames; i++) {
std::rotate(in.begin(), in.end() - 1, in.end());
std::rotate(out.begin(), out.end() - 1, out.end());
for (std::size_t ch = 0; ch < channel_count; ch++) {
in[0][ch] = signal[i * channel_count + ch];
out[0][ch] = b0 * in[0][ch] + b1 * in[1][ch] + b2 * in[2][ch] - a1 * out[1][ch] -
a2 * out[2][ch];
signal[i * 2 + ch] = static_cast<s16>(std::clamp(out[0][ch], -32768.0, 32767.0));
}
}
}
/// Calculates the appropriate Q for each biquad in a cascading filter.
/// @param total_count The total number of biquads to be cascaded.
/// @param index 0-index of the biquad to calculate the Q value for.
static double CascadingBiquadQ(std::size_t total_count, std::size_t index) {
const auto pole =
M_PI * static_cast<double>(2 * index + 1) / (4.0 * static_cast<double>(total_count));
return 1.0 / (2.0 * std::cos(pole));
}
CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size) {
std::vector<Filter> cascade(cascade_size);
for (std::size_t i = 0; i < cascade_size; i++) {
cascade[i] = Filter::LowPass(cutoff, CascadingBiquadQ(cascade_size, i));
}
return CascadingFilter{std::move(cascade)};
}
CascadingFilter::CascadingFilter() = default;
CascadingFilter::CascadingFilter(std::vector<Filter> filters_) : filters(std::move(filters_)) {}
void CascadingFilter::Process(std::vector<s16>& signal) {
for (auto& filter : filters) {
filter.Process(signal);
}
}
} // namespace AudioCore

View File

@@ -1,61 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <vector>
#include "common/common_types.h"
namespace AudioCore {
/// Digital biquad filter:
///
/// b0 + b1 z^-1 + b2 z^-2
/// H(z) = ------------------------
/// a0 + a1 z^-1 + b2 z^-2
class Filter {
public:
/// Creates a low-pass filter.
/// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0.
/// @param Q Determines the quality factor of this filter.
static Filter LowPass(double cutoff, double Q = 0.7071);
/// Passthrough filter.
Filter();
Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_);
void Process(std::vector<s16>& signal);
private:
static constexpr std::size_t channel_count = 2;
/// Coefficients are in normalized form (a0 = 1.0).
double a1, a2, b0, b1, b2;
/// Input History
std::array<std::array<double, channel_count>, 3> in;
/// Output History
std::array<std::array<double, channel_count>, 3> out;
};
/// Cascade filters to build up higher-order filters from lower-order ones.
class CascadingFilter {
public:
/// Creates a cascading low-pass filter.
/// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0.
/// @param cascade_size Number of biquads in cascade.
static CascadingFilter LowPass(double cutoff, std::size_t cascade_size);
/// Passthrough.
CascadingFilter();
explicit CascadingFilter(std::vector<Filter> filters_);
void Process(std::vector<s16>& signal);
private:
std::vector<Filter> filters;
};
} // namespace AudioCore

View File

@@ -1,232 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#define _USE_MATH_DEFINES
#include <algorithm>
#include <climits>
#include <cmath>
#include <vector>
#include "audio_core/algorithm/interpolate.h"
#include "common/common_types.h"
#include "common/logging/log.h"
namespace AudioCore {
constexpr std::array<s16, 512> curve_lut0{
6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239,
19412, 7093, 22, 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377,
7472, 41, 5773, 19361, 7600, 48, 5659, 19342, 7728, 55, 5546, 19321, 7857,
62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77, 5213, 19245, 8249, 84,
5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109, 4785,
19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988,
9183, 147, 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590,
179, 4083, 18793, 9726, 190, 3987, 18738, 9863, 202, 3893, 18682, 10000, 215,
3800, 18624, 10137, 228, 3709, 18563, 10274, 241, 3618, 18500, 10411, 255, 3529,
18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300, 3269, 18230,
10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375,
369, 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428,
2709, 17681, 11925, 449, 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489,
17418, 12334, 517, 2418, 17327, 12470, 541, 2348, 17234, 12606, 566, 2280, 17140,
12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647, 2083, 16846, 13144,
675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766,
1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667,
16109, 14062, 901, 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772,
14445, 1013, 1457, 15657, 14571, 1052, 1407, 15540, 14695, 1093, 1359, 15423, 14819,
1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221, 1221, 15064, 15185, 1266,
1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407, 1052,
14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191,
15998, 1613, 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327,
1780, 798, 13673, 16434, 1838, 766, 13541, 16539, 1897, 735, 13409, 16643, 1958,
704, 13277, 16745, 2020, 675, 13144, 16846, 2083, 647, 13010, 16946, 2147, 619,
12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348, 541, 12470,
17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595,
2635, 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863,
388, 11513, 17927, 2942, 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334,
11100, 18157, 3186, 317, 10962, 18230, 3269, 300, 10824, 18300, 3355, 285, 10687,
18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618, 241, 10274, 18563,
3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987,
190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157,
9318, 18942, 4377, 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914,
19073, 4681, 118, 8780, 19112, 4785, 109, 8646, 19148, 4890, 101, 8513, 19183,
4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213, 77, 8118, 19273, 5323,
69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659, 48,
7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219,
19403, 6121, 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424,
6479, 3, 6722, 19426, 6600};
constexpr std::array<s16, 512> curve_lut1{
-68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450,
32586, 512, -36, -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454,
1000, -69, -891, 32393, 1174, -80, -990, 32323, 1352, -92, -1084, 32244, 1536,
-103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128, -1338, 31956, 2118, -140,
-1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180, -1617,
31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995,
3657, -240, -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393,
-289, -1951, 30272, 4648, -307, -1984, 30072, 4908, -325, -2014, 29866, 5172, -343,
-2040, 29652, 5442, -362, -2063, 29431, 5716, -382, -2083, 29203, 5994, -403, -2100,
28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468, -2133, 28226,
7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066,
-563, -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641,
-2121, 26285, 9336, -668, -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084,
25375, 10326, -753, -2067, 25063, 10662, -783, -2049, 24746, 11000, -813, -2030, 24425,
11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907, -1962, 23438, 12382,
-939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039,
-1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764,
21027, 14877, -1176, -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959,
15965, -1282, -1633, 19600, 16329, -1317, -1599, 19239, 16694, -1353, -1564, 18878, 17058,
-1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459, -1459, 17787, 18151, -1495,
-1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599, -1317,
16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239,
20673, -1732, -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729,
-1825, -1072, 13798, 22077, -1855, -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911,
-972, 12733, 23103, -1937, -939, 12382, 23438, -1962, -907, 12033, 23771, -1986, -875,
11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049, -783, 10662,
25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987,
-2111, -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136,
-588, 8378, 27151, -2141, -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514,
7453, 27966, -2139, -490, 7153, 28226, -2133, -468, 6857, 28480, -2125, -445, 6565,
28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083, -382, 5716, 29431,
-2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984,
-307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256,
3897, 30826, -1830, -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192,
31310, -1676, -194, 2967, 31456, -1617, -180, 2747, 31593, -1554, -167, 2532, 31723,
-1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338, -128, 1919, 32061, -1258,
-115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990, -80,
1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669,
32551, -568, -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630,
-200, -5, 69, 32639, -68};
constexpr std::array<s16, 512> curve_lut2{
3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811,
26253, 3751, -42, 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169,
4199, -54, 2338, 26130, 4354, -58, 2227, 26085, 4512, -63, 2120, 26035, 4673,
-67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76, 1813, 25852, 5174, -81,
1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98, 1442,
25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239,
6442, -121, 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027,
-140, 897, 24776, 7227, -146, 829, 24648, 7430, -153, 764, 24516, 7635, -159,
701, 24379, 7842, -166, 641, 24237, 8052, -174, 583, 24091, 8264, -181, 526,
23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202, 371, 23462,
9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809,
-230, 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250,
81, 22208, 10735, -258, 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16,
21618, 11444, -277, -44, 21415, 11684, -283, -71, 21208, 11924, -290, -97, 20999,
12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306, -163, 20354, 12898,
-311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325,
-234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273,
18765, 14625, -337, -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057,
15369, -341, -310, 17817, 15617, -341, -317, 17577, 15864, -340, -323, 17335, 16111,
-340, -328, 17092, 16357, -338, -332, 16848, 16603, -336, -336, 16603, 16848, -332,
-338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317, -341,
15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873,
18531, -284, -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230,
-248, -328, 13882, 19459, -234, -325, 13635, 19686, -218, -321, 13389, 19911, -201,
-316, 13143, 20134, -183, -311, 12898, 20354, -163, -306, 12653, 20571, -143, -302,
12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71, -283, 11684,
21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015,
47, -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154,
-237, 10038, 22769, 194, -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215,
9358, 23295, 324, -209, 9135, 23462, 371, -202, 8914, 23626, 420, -194, 8695,
23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583, -174, 8052, 24237,
641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829,
-146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127,
6635, 25131, 1115, -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066,
25440, 1357, -103, 5882, 25533, 1442, -98, 5701, 25621, 1531, -92, 5522, 25704,
1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813, -76, 5004, 25919, 1912,
-72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227, -58,
4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897,
26230, 2688, -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281,
3064, -32, 3329, 26287, 3195};
std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio) {
if (input.size() < 2)
return {};
if (ratio <= 0) {
LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio);
return input;
}
const s32 step{static_cast<s32>(ratio * 0x8000)};
const std::array<s16, 512>& lut = [step] {
if (step > 0xaaaa) {
return curve_lut0;
}
if (step <= 0x8000) {
return curve_lut1;
}
return curve_lut2;
}();
const std::size_t num_frames{input.size() / 2};
std::vector<s16> output;
output.reserve(static_cast<std::size_t>(static_cast<double>(input.size()) / ratio +
InterpolationState::taps));
for (std::size_t frame{}; frame < num_frames; ++frame) {
const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps};
std::rotate(state.history.begin(), state.history.end() - 1, state.history.end());
state.history[0][0] = input[frame * 2 + 0];
state.history[0][1] = input[frame * 2 + 1];
while (state.position <= 1.0) {
const s32 left{state.history[0][0] * lut[lut_index + 0] +
state.history[1][0] * lut[lut_index + 1] +
state.history[2][0] * lut[lut_index + 2] +
state.history[3][0] * lut[lut_index + 3]};
const s32 right{state.history[0][1] * lut[lut_index + 0] +
state.history[1][1] * lut[lut_index + 1] +
state.history[2][1] * lut[lut_index + 2] +
state.history[3][1] * lut[lut_index + 3]};
const s32 new_offset{state.fraction + step};
state.fraction = new_offset & 0x7fff;
output.emplace_back(static_cast<s16>(std::clamp(left >> 15, SHRT_MIN, SHRT_MAX)));
output.emplace_back(static_cast<s16>(std::clamp(right >> 15, SHRT_MIN, SHRT_MAX)));
state.position += ratio;
}
state.position -= 1.0;
}
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 + 0]);
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

View File

@@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <vector>
#include "common/common_types.h"
namespace AudioCore {
struct InterpolationState {
static constexpr std::size_t taps{4};
static constexpr std::size_t history_size{taps * 2 - 1};
std::array<std::array<s16, 2>, history_size> history{};
double position{};
s32 fraction{};
};
/// Interpolates input signal to produce output signal.
/// @param input The signal to interpolate.
/// @param ratio Interpolation ratio.
/// ratio > 1.0 results in fewer output samples.
/// ratio < 1.0 results in more output samples.
/// @returns Output signal.
std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio);
/// Interpolates input signal to produce output signal.
/// @param input The signal to interpolate.
/// @param input_rate The sample rate of input.
/// @param output_rate The desired sample rate of the output.
/// @returns Output signal.
inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
u32 input_rate, u32 output_rate) {
const double ratio = static_cast<double>(input_rate) / static_cast<double>(output_rate);
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

View File

@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_core.h"
#include "audio_core/sink/sink_details.h"
#include "common/settings.h"
#include "core/core.h"
namespace AudioCore {
AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>(system)} {
CreateSinks();
// Must be created after the sinks
adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink);
}
AudioCore ::~AudioCore() {
Shutdown();
}
void AudioCore::CreateSinks() {
const auto& sink_id{Settings::values.sink_id};
const auto& audio_output_device_id{Settings::values.audio_output_device_id};
const auto& audio_input_device_id{Settings::values.audio_input_device_id};
output_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_output_device_id.GetValue());
input_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_input_device_id.GetValue());
}
void AudioCore::Shutdown() {
audio_manager->Shutdown();
}
AudioManager& AudioCore::GetAudioManager() {
return *audio_manager;
}
Sink::Sink& AudioCore::GetOutputSink() {
return *output_sink;
}
Sink::Sink& AudioCore::GetInputSink() {
return *input_sink;
}
AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
return *adsp;
}
void AudioCore::PauseSinks(const bool pausing) const {
if (pausing) {
output_sink->PauseStreams();
input_sink->PauseStreams();
} else {
output_sink->UnpauseStreams();
input_sink->UnpauseStreams();
}
}
u32 AudioCore::GetStreamQueue() const {
return estimated_queue.load();
}
void AudioCore::SetStreamQueue(u32 size) {
estimated_queue.store(size);
}
} // namespace AudioCore

100
src/audio_core/audio_core.h Normal file
View File

@@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include "audio_core/audio_manager.h"
#include "audio_core/renderer/adsp/adsp.h"
#include "audio_core/sink/sink.h"
namespace Core {
class System;
}
namespace AudioCore {
class AudioManager;
/**
* Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP.
*/
class AudioCore {
public:
explicit AudioCore(Core::System& system);
~AudioCore();
/**
* Shutdown the audio core.
*/
void Shutdown();
/**
* Get a reference to the audio manager.
*
* @return Ref to the audio manager.
*/
AudioManager& GetAudioManager();
/**
* Get the audio output sink currently in use.
*
* @return Ref to the sink.
*/
Sink::Sink& GetOutputSink();
/**
* Get the audio input sink currently in use.
*
* @return Ref to the sink.
*/
Sink::Sink& GetInputSink();
/**
* Get the ADSP.
*
* @return Ref to the ADSP.
*/
AudioRenderer::ADSP::ADSP& GetADSP();
/**
* Pause the sink. Called from the core.
*
* @param pausing - Is this pause due to an actual pause, or shutdown?
* Unfortunately, shutdown also pauses streams, which can cause issues.
*/
void PauseSinks(bool pausing) const;
/**
* Get the size of the current stream queue.
*
* @return Current stream queue size.
*/
u32 GetStreamQueue() const;
/**
* Get the size of the current stream queue.
*
* @param size - New stream size.
*/
void SetStreamQueue(u32 size);
private:
/**
* Create the sinks on startup.
*/
void CreateSinks();
/// Main audio manager for audio in/out
std::unique_ptr<AudioManager> audio_manager;
/// Sink used for audio renderer and audio out
std::unique_ptr<Sink::Sink> output_sink;
/// Sink used for audio input
std::unique_ptr<Sink::Sink> input_sink;
/// The ADSP in the sysmodule
std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
/// Current size of the stream queue
std::atomic<u32> estimated_queue{0};
};
} // namespace AudioCore

View File

@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_event.h"
#include "common/assert.h"
namespace AudioCore {
size_t Event::GetManagerIndex(const Type type) const {
switch (type) {
case Type::AudioInManager:
return 0;
case Type::AudioOutManager:
return 1;
case Type::FinalOutputRecorderManager:
return 2;
case Type::Max:
return 3;
default:
UNREACHABLE();
}
return 3;
}
void Event::SetAudioEvent(const Type type, const bool signalled) {
events_signalled[GetManagerIndex(type)] = signalled;
if (signalled) {
manager_event.notify_one();
}
}
bool Event::CheckAudioEventSet(const Type type) const {
return events_signalled[GetManagerIndex(type)];
}
std::mutex& Event::GetAudioEventLock() {
return event_lock;
}
std::condition_variable_any& Event::GetAudioEvent() {
return manager_event;
}
bool Event::Wait(std::unique_lock<std::mutex>& l, const std::chrono::seconds timeout) {
bool timed_out{false};
if (!manager_event.wait_for(l, timeout, [&]() {
return std::ranges::any_of(events_signalled, [](bool x) { return x; });
})) {
timed_out = true;
}
return timed_out;
}
void Event::ClearEvents() {
events_signalled[0] = false;
events_signalled[1] = false;
events_signalled[2] = false;
events_signalled[3] = false;
}
} // namespace AudioCore

View File

@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
namespace AudioCore {
/**
* Responsible for the input/output events, set by the stream backend when buffers are consumed, and
* waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer
* recycling going.
* In a real Switch this is not a seprate class, and exists entirely within the audio manager.
* On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can
* wait on multiple events at once, and the events are not needed by the backend.
*/
class Event {
public:
enum class Type {
AudioInManager,
AudioOutManager,
FinalOutputRecorderManager,
Max,
};
/**
* Convert a manager type to an index.
*
* @param type - The manager type to convert
* @return The index of the type.
*/
size_t GetManagerIndex(Type type) const;
/**
* Set an audio event to true or false.
*
* @param type - The manager type to signal.
* @param signalled - Its signal state.
*/
void SetAudioEvent(Type type, bool signalled);
/**
* Check if the given manager type is signalled.
*
* @param type - The manager type to check.
* @return True if the event is signalled, otherwise false.
*/
bool CheckAudioEventSet(Type type) const;
/**
* Get the lock for audio events.
*
* @return Reference to the lock.
*/
std::mutex& GetAudioEventLock();
/**
* Get the manager event, this signals the audio manager to release buffers and signal the game
* for more.
*
* @return Reference to the condition variable.
*/
std::condition_variable_any& GetAudioEvent();
/**
* Wait on the manager_event.
*
* @param l - Lock held by the wait.
* @param timeout - Timeout for the wait. This is 2 seconds by default.
* @return True if the wait timed out, otherwise false if signalled.
*/
bool Wait(std::unique_lock<std::mutex>& l, std::chrono::seconds timeout);
/**
* Reset all manager events.
*/
void ClearEvents();
private:
/// Lock, used bythe audio manager
std::mutex event_lock;
/// Array of events, one per system type (see Type), last event is used to terminate
std::array<std::atomic<bool>, 4> events_signalled;
/// Event to signal the audio manager
std::condition_variable_any manager_event;
};
} // namespace AudioCore

View File

@@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_core.h"
#include "audio_core/audio_in_manager.h"
#include "audio_core/audio_manager.h"
#include "audio_core/in/audio_in.h"
#include "audio_core/sink/sink_details.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/service/audio/errors.h"
namespace AudioCore::AudioIn {
Manager::Manager(Core::System& system_) : system{system_} {
std::iota(session_ids.begin(), session_ids.end(), 0);
num_free_sessions = MaxInSessions;
}
Result Manager::AcquireSessionId(size_t& session_id) {
if (num_free_sessions == 0) {
LOG_ERROR(Service_Audio, "All 4 AudioIn sessions are in use, cannot create any more");
return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
}
session_id = session_ids[next_session_id];
next_session_id = (next_session_id + 1) % MaxInSessions;
num_free_sessions--;
return ResultSuccess;
}
void Manager::ReleaseSessionId(const size_t session_id) {
std::scoped_lock l{mutex};
LOG_DEBUG(Service_Audio, "Freeing AudioIn session {}", session_id);
session_ids[free_session_id] = session_id;
num_free_sessions++;
free_session_id = (free_session_id + 1) % MaxInSessions;
sessions[session_id].reset();
applet_resource_user_ids[session_id] = 0;
}
Result Manager::LinkToManager() {
std::scoped_lock l{mutex};
if (!linked_to_manager) {
AudioManager& manager{system.AudioCore().GetAudioManager()};
manager.SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this));
linked_to_manager = true;
}
return ResultSuccess;
}
void Manager::Start() {
if (sessions_started) {
return;
}
std::scoped_lock l{mutex};
for (auto& session : sessions) {
if (session) {
session->StartSession();
}
}
sessions_started = true;
}
void Manager::BufferReleaseAndRegister() {
std::scoped_lock l{mutex};
for (auto& session : sessions) {
if (session != nullptr) {
session->ReleaseAndRegisterBuffers();
}
}
}
u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
[[maybe_unused]] const u32 max_count,
[[maybe_unused]] const bool filter) {
std::scoped_lock l{mutex};
LinkToManager();
auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
if (input_devices.size() > 1) {
names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac"));
return 1;
}
return 0;
}
} // namespace AudioCore::AudioIn

View File

@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <mutex>
#include <vector>
#include "audio_core/renderer/audio_device.h"
namespace Core {
class System;
}
namespace AudioCore::AudioIn {
class In;
constexpr size_t MaxInSessions = 4;
/**
* Manages all audio in sessions.
*/
class Manager {
public:
explicit Manager(Core::System& system);
/**
* Acquire a free session id for opening a new audio in.
*
* @param session_id - Output session_id.
* @return Result code.
*/
Result AcquireSessionId(size_t& session_id);
/**
* Release a session id on close.
*
* @param session_id - Session id to free.
*/
void ReleaseSessionId(size_t session_id);
/**
* Link the audio in manager to the main audio manager.
*
* @return Result code.
*/
Result LinkToManager();
/**
* Start the audio in manager.
*/
void Start();
/**
* Callback function, called by the audio manager when the audio in event is signalled.
*/
void BufferReleaseAndRegister();
/**
* Get a list of audio in device names.
*
* @oaram names - Output container to write names to.
* @param max_count - Maximum numebr of deivce names to write. Unused
* @param filter - Should the list be filtered? Unused.
* @return Number of names written.
*/
u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
u32 max_count, bool filter);
/// Core system
Core::System& system;
/// Array of session ids
std::array<size_t, MaxInSessions> session_ids{};
/// Array of resource user ids
std::array<size_t, MaxInSessions> applet_resource_user_ids{};
/// Pointer to each open session
std::array<std::shared_ptr<In>, MaxInSessions> sessions{};
/// The number of free sessions
size_t num_free_sessions{};
/// The next session id to be taken
size_t next_session_id{};
/// The next session id to be freed
size_t free_session_id{};
/// Whether this is linked to the audio manager
bool linked_to_manager{};
/// Whether the sessions have been started
bool sessions_started{};
/// Protect state due to audio manager callback
std::recursive_mutex mutex{};
};
} // namespace AudioCore::AudioIn

View File

@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_in_manager.h"
#include "audio_core/audio_manager.h"
#include "audio_core/audio_out_manager.h"
#include "core/core.h"
namespace AudioCore {
AudioManager::AudioManager(Core::System& system_) : system{system_} {
thread = std::jthread([this]() { ThreadFunc(); });
}
void AudioManager::Shutdown() {
running = false;
events.SetAudioEvent(Event::Type::Max, true);
thread.join();
}
Result AudioManager::SetOutManager(BufferEventFunc buffer_func) {
if (!running) {
return Service::Audio::ERR_OPERATION_FAILED;
}
std::scoped_lock l{lock};
const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)};
if (buffer_events[index] == nullptr) {
buffer_events[index] = buffer_func;
needs_update = true;
events.SetAudioEvent(Event::Type::AudioOutManager, true);
}
return ResultSuccess;
}
Result AudioManager::SetInManager(BufferEventFunc buffer_func) {
if (!running) {
return Service::Audio::ERR_OPERATION_FAILED;
}
std::scoped_lock l{lock};
const auto index{events.GetManagerIndex(Event::Type::AudioInManager)};
if (buffer_events[index] == nullptr) {
buffer_events[index] = buffer_func;
needs_update = true;
events.SetAudioEvent(Event::Type::AudioInManager, true);
}
return ResultSuccess;
}
void AudioManager::SetEvent(const Event::Type type, const bool signalled) {
events.SetAudioEvent(type, signalled);
}
void AudioManager::ThreadFunc() {
std::unique_lock l{events.GetAudioEventLock()};
events.ClearEvents();
running = true;
while (running) {
auto timed_out{events.Wait(l, std::chrono::seconds(2))};
if (events.CheckAudioEventSet(Event::Type::Max)) {
break;
}
for (size_t i = 0; i < buffer_events.size(); i++) {
if (events.CheckAudioEventSet(Event::Type(i)) || timed_out) {
if (buffer_events[i]) {
buffer_events[i]();
}
}
events.SetAudioEvent(Event::Type(i), false);
}
}
}
} // namespace AudioCore

View File

@@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <atomic>
#include <functional>
#include <mutex>
#include <thread>
#include "audio_core/audio_event.h"
#include "core/hle/service/audio/errors.h"
namespace Core {
class System;
}
namespace AudioCore {
namespace AudioOut {
class Manager;
}
namespace AudioIn {
class Manager;
}
/**
* The AudioManager's main purpose is to wait for buffer events for the audio in and out managers,
* and call an associated callback to release buffers.
*
* Execution pattern is:
* Buffers appended ->
* Buffers queued and played by the backend stream ->
* When consumed, set the corresponding manager event and signal the audio manager ->
* Consumed buffers are released, game is signalled ->
* Game appends more buffers.
*
* This is only used by audio in and audio out.
*/
class AudioManager {
using BufferEventFunc = std::function<void()>;
public:
explicit AudioManager(Core::System& system);
/**
* Shutdown the audio manager.
*/
void Shutdown();
/**
* Register the out manager, keeping a function to be called when the out event is signalled.
*
* @param buffer_func - Function to be called on signal.
* @return Result code.
*/
Result SetOutManager(BufferEventFunc buffer_func);
/**
* Register the in manager, keeping a function to be called when the in event is signalled.
*
* @param buffer_func - Function to be called on signal.
* @return Result code.
*/
Result SetInManager(BufferEventFunc buffer_func);
/**
* Set an event to signalled, and signal the thread.
*
* @param type - Manager type to set.
* @param signalled - Set the event to true or false?
*/
void SetEvent(Event::Type type, bool signalled);
private:
/**
* Main thread, waiting on a manager signal and calling the registered fucntion.
*/
void ThreadFunc();
/// Core system
Core::System& system;
/// Have sessions started palying?
bool sessions_started{};
/// Is the main thread running?
std::atomic<bool> running{};
/// Unused
bool needs_update{};
/// Events to be set and signalled
Event events{};
/// Callbacks for each manager
std::array<BufferEventFunc, 3> buffer_events{};
/// General lock
std::mutex lock{};
/// Main thread for waiting and callbacks
std::jthread thread;
};
} // namespace AudioCore

View File

@@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_out.h"
#include "audio_core/sink.h"
#include "audio_core/sink_details.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/settings.h"
namespace AudioCore {
/// Returns the stream format from the specified number of channels
static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
switch (num_channels) {
case 1:
return Stream::Format::Mono16;
case 2:
return Stream::Format::Stereo16;
case 6:
return Stream::Format::Multi51Channel16;
}
UNIMPLEMENTED_MSG("Unimplemented num_channels={}", num_channels);
return {};
}
StreamPtr AudioOut::OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate,
u32 num_channels, std::string&& name,
Stream::ReleaseCallback&& release_callback) {
if (!sink) {
sink = CreateSinkFromID(Settings::values.sink_id.GetValue(),
Settings::values.audio_device_id.GetValue());
}
return std::make_shared<Stream>(
core_timing, sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback),
sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name));
}
std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream,
std::size_t max_count) {
return stream->GetTagsAndReleaseBuffers(max_count);
}
std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream) {
return stream->GetTagsAndReleaseBuffers();
}
void AudioOut::StartStream(StreamPtr stream) {
stream->Play();
}
void AudioOut::StopStream(StreamPtr stream) {
stream->Stop();
}
bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data) {
return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data)));
}
} // namespace AudioCore

View File

@@ -1,49 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string>
#include <vector>
#include "audio_core/buffer.h"
#include "audio_core/sink.h"
#include "audio_core/stream.h"
#include "common/common_types.h"
namespace Core::Timing {
class CoreTiming;
}
namespace AudioCore {
/**
* Represents an audio playback interface, used to open and play audio streams
*/
class AudioOut {
public:
/// Opens a new audio stream
StreamPtr OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, u32 num_channels,
std::string&& name, Stream::ReleaseCallback&& release_callback);
/// Returns a vector of recently released buffers specified by tag for the specified stream
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count);
/// Returns a vector of all recently released buffers specified by tag for the specified stream
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream);
/// Starts an audio stream for playback
void StartStream(StreamPtr stream);
/// Stops an audio stream that is currently playing
void StopStream(StreamPtr stream);
/// Queues a buffer into the specified audio stream, returns true on success
bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data);
private:
SinkPtr sink;
};
} // namespace AudioCore

View File

@@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_core.h"
#include "audio_core/audio_manager.h"
#include "audio_core/audio_out_manager.h"
#include "audio_core/out/audio_out.h"
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/audio/errors.h"
namespace AudioCore::AudioOut {
Manager::Manager(Core::System& system_) : system{system_} {
std::iota(session_ids.begin(), session_ids.end(), 0);
num_free_sessions = MaxOutSessions;
}
Result Manager::AcquireSessionId(size_t& session_id) {
if (num_free_sessions == 0) {
LOG_ERROR(Service_Audio, "All 12 Audio Out sessions are in use, cannot create any more");
return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
}
session_id = session_ids[next_session_id];
next_session_id = (next_session_id + 1) % MaxOutSessions;
num_free_sessions--;
return ResultSuccess;
}
void Manager::ReleaseSessionId(const size_t session_id) {
std::scoped_lock l{mutex};
LOG_DEBUG(Service_Audio, "Freeing AudioOut session {}", session_id);
session_ids[free_session_id] = session_id;
num_free_sessions++;
free_session_id = (free_session_id + 1) % MaxOutSessions;
sessions[session_id].reset();
applet_resource_user_ids[session_id] = 0;
}
Result Manager::LinkToManager() {
std::scoped_lock l{mutex};
if (!linked_to_manager) {
AudioManager& manager{system.AudioCore().GetAudioManager()};
manager.SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this));
linked_to_manager = true;
}
return ResultSuccess;
}
void Manager::Start() {
if (sessions_started) {
return;
}
std::scoped_lock l{mutex};
for (auto& session : sessions) {
if (session) {
session->StartSession();
}
}
sessions_started = true;
}
void Manager::BufferReleaseAndRegister() {
std::scoped_lock l{mutex};
for (auto& session : sessions) {
if (session != nullptr) {
session->ReleaseAndRegisterBuffers();
}
}
}
u32 Manager::GetAudioOutDeviceNames(
std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
names.push_back({"DeviceOut"});
return 1;
}
} // namespace AudioCore::AudioOut

View File

@@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <mutex>
#include "audio_core/renderer/audio_device.h"
namespace Core {
class System;
}
namespace AudioCore::AudioOut {
class Out;
constexpr size_t MaxOutSessions = 12;
/**
* Manages all audio out sessions.
*/
class Manager {
public:
explicit Manager(Core::System& system);
/**
* Acquire a free session id for opening a new audio out.
*
* @param session_id - Output session_id.
* @return Result code.
*/
Result AcquireSessionId(size_t& session_id);
/**
* Release a session id on close.
*
* @param session_id - Session id to free.
*/
void ReleaseSessionId(size_t session_id);
/**
* Link this manager to the main audio manager.
*
* @return Result code.
*/
Result LinkToManager();
/**
* Start the audio out manager.
*/
void Start();
/**
* Callback function, called by the audio manager when the audio out event is signalled.
*/
void BufferReleaseAndRegister();
/**
* Get a list of audio out device names.
*
* @oaram names - Output container to write names to.
* @return Number of names written.
*/
u32 GetAudioOutDeviceNames(
std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const;
/// Core system
Core::System& system;
/// Array of session ids
std::array<size_t, MaxOutSessions> session_ids{};
/// Array of resource user ids
std::array<size_t, MaxOutSessions> applet_resource_user_ids{};
/// Pointer to each open session
std::array<std::shared_ptr<Out>, MaxOutSessions> sessions{};
/// The number of free sessions
size_t num_free_sessions{};
/// The next session id to be taken
size_t next_session_id{};
/// The next session id to be freed
size_t free_session_id{};
/// Whether this is linked to the audio manager
bool linked_to_manager{};
/// Whether the sessions have been started
bool sessions_started{};
/// Protect state due to audio manager callback
std::recursive_mutex mutex{};
};
} // namespace AudioCore::AudioOut

View File

@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_render_manager.h"
#include "audio_core/common/audio_renderer_parameter.h"
#include "audio_core/common/feature_support.h"
#include "core/core.h"
namespace AudioCore::AudioRenderer {
Manager::Manager(Core::System& system_)
: system{system_}, system_manager{std::make_unique<SystemManager>(system)} {
std::iota(session_ids.begin(), session_ids.end(), 0);
}
Manager::~Manager() {
Stop();
}
void Manager::Stop() {
system_manager->Stop();
}
SystemManager& Manager::GetSystemManager() {
return *system_manager;
}
auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count)
-> Result {
if (!CheckValidRevision(params.revision)) {
return Service::Audio::ERR_INVALID_REVISION;
}
out_count = System::GetWorkBufferSize(params);
return ResultSuccess;
}
s32 Manager::GetSessionId() {
std::scoped_lock l{session_lock};
auto session_id{session_ids[session_count]};
if (session_id == -1) {
return -1;
}
session_ids[session_count] = -1;
session_count++;
return session_id;
}
void Manager::ReleaseSessionId(const s32 session_id) {
std::scoped_lock l{session_lock};
session_ids[--session_count] = session_id;
}
u32 Manager::GetSessionCount() {
std::scoped_lock l{session_lock};
return session_count;
}
bool Manager::AddSystem(System& system_) {
return system_manager->Add(system_);
}
bool Manager::RemoveSystem(System& system_) {
return system_manager->Remove(system_);
}
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,103 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <mutex>
#include "audio_core/common/common.h"
#include "audio_core/renderer/system_manager.h"
#include "core/hle/service/audio/errors.h"
namespace Core {
class System;
}
namespace AudioCore {
struct AudioRendererParameterInternal;
namespace AudioRenderer {
/**
* Wrapper for the audio system manager, handles service calls.
*/
class Manager {
public:
explicit Manager(Core::System& system);
~Manager();
/**
* Stop the manager.
*/
void Stop();
/**
* Get the system manager.
*
* @return The system manager.
*/
SystemManager& GetSystemManager();
/**
* Get required size for the audio renderer workbuffer.
*
* @param params - Input parameters with the numbers of voices/mixes/sinks etc.
* @param out_count - Output size of the required workbuffer.
* @return Result code.
*/
Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count);
/**
* Get a new session id.
*
* @return The new session id. -1 if invalid, otherwise 0-MaxRendererSessions.
*/
s32 GetSessionId();
/**
* Get the number of currently active sessions.
*
* @return The number of active sessions.
*/
u32 GetSessionCount();
/**
* Add a renderer system to the manager.
* The system will be reguarly called to generate commands for the AudioRenderer.
*
* @param system - The system to add.
* @return True if the system was sucessfully added, otherwise false.
*/
bool AddSystem(System& system);
/**
* Remove a renderer system from the manager.
*
* @param system - The system to remove.
* @return True if the system was sucessfully removed, otherwise false.
*/
bool RemoveSystem(System& system);
/**
* Free a session id when the system wants to shut down.
*
* @param session_id - The session id to free.
*/
void ReleaseSessionId(s32 session_id);
private:
/// Core system
Core::System& system;
/// Session ids, -1 when in use
std::array<s32, MaxRendererSessions> session_ids{};
/// Number of active renderers
u32 session_count{};
/// Lock for interacting with the sessions
std::mutex session_lock{};
/// Regularly generates commands from the registered systems for the AudioRenderer
std::unique_ptr<SystemManager> system_manager{};
};
} // namespace AudioRenderer
} // namespace AudioCore

View File

@@ -1,343 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <limits>
#include <optional>
#include <vector>
#include "audio_core/audio_out.h"
#include "audio_core/audio_renderer.h"
#include "audio_core/common.h"
#include "audio_core/info_updater.h"
#include "audio_core/voice_context.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/memory.h"
namespace {
[[nodiscard]] static constexpr s16 ClampToS16(s32 value) {
return static_cast<s16>(std::clamp(value, s32{std::numeric_limits<s16>::min()},
s32{std::numeric_limits<s16>::max()}));
}
[[nodiscard]] static constexpr s16 Mix2To1(s16 l_channel, s16 r_channel) {
// Mix 50% from left and 50% from right channel
constexpr float l_mix_amount = 50.0f / 100.0f;
constexpr float r_mix_amount = 50.0f / 100.0f;
return ClampToS16(static_cast<s32>((static_cast<float>(l_channel) * l_mix_amount) +
(static_cast<float>(r_channel) * r_mix_amount)));
}
[[maybe_unused, nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2(
s16 fl_channel, s16 fr_channel, s16 fc_channel, [[maybe_unused]] s16 lf_channel, s16 bl_channel,
s16 br_channel) {
// Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels
// are mixed to be 36.94%
constexpr float front_mix_amount = 36.94f / 100.0f;
constexpr float center_mix_amount = 26.12f / 100.0f;
constexpr float back_mix_amount = 36.94f / 100.0f;
// Mix 50% from left and 50% from right channel
const auto left = front_mix_amount * static_cast<float>(fl_channel) +
center_mix_amount * static_cast<float>(fc_channel) +
back_mix_amount * static_cast<float>(bl_channel);
const auto right = front_mix_amount * static_cast<float>(fr_channel) +
center_mix_amount * static_cast<float>(fc_channel) +
back_mix_amount * static_cast<float>(br_channel);
return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))};
}
[[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2WithCoefficients(
s16 fl_channel, s16 fr_channel, s16 fc_channel, s16 lf_channel, s16 bl_channel, s16 br_channel,
const std::array<float_le, 4>& coeff) {
const auto left =
static_cast<float>(fl_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] +
static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(bl_channel) * coeff[3];
const auto right =
static_cast<float>(fr_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] +
static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(br_channel) * coeff[3];
return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))};
}
} // namespace
namespace AudioCore {
constexpr s32 NUM_BUFFERS = 2;
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_,
AudioCommon::AudioRendererParameter params,
Stream::ReleaseCallback&& release_callback,
std::size_t instance_number)
: worker_params{params}, 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),
core_timing{core_timing_} {
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, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback));
process_event =
Core::Timing::CreateEvent(fmt::format("AudioRenderer-Instance{}-Process", instance_number),
[this](std::uintptr_t, s64, std::chrono::nanoseconds) {
ReleaseAndQueueBuffers();
return std::nullopt;
});
for (s32 i = 0; i < NUM_BUFFERS; ++i) {
QueueMixedBuffer(i);
}
}
AudioRenderer::~AudioRenderer() = default;
Result AudioRenderer::Start() {
audio_out->StartStream(stream);
ReleaseAndQueueBuffers();
return ResultSuccess;
}
Result AudioRenderer::Stop() {
audio_out->StopStream(stream);
return ResultSuccess;
}
u32 AudioRenderer::GetSampleRate() const {
return worker_params.sample_rate;
}
u32 AudioRenderer::GetSampleCount() const {
return worker_params.sample_count;
}
u32 AudioRenderer::GetMixBufferCount() const {
return worker_params.mix_buffer_count;
}
Stream::State AudioRenderer::GetStreamState() const {
return stream->GetState();
}
Result AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
std::vector<u8>& output_params) {
std::scoped_lock lock{mutex};
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;
}
}
const 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;
}
return ResultSuccess;
}
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
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, making sure to clear
std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels(), 0);
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<std::span<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]);
}
for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
if (channel_count == 1) {
const auto sample = ClampToS16(mix_buffers[0][i]);
// Place sample in all channels
for (u32 channel = 0; channel < stream_channel_count; channel++) {
buffer[i * stream_channel_count + channel] = sample;
}
if (stream_channel_count == 6) {
// Output stream has a LF channel, mute it!
buffer[i * stream_channel_count + 3] = 0;
}
} 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] = Mix2To1(l_sample, r_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;
// Combine both left and right channels to the center channel
buffer[i * stream_channel_count + 2] = Mix2To1(l_sample, r_sample);
buffer[i * stream_channel_count + 4] = l_sample;
buffer[i * stream_channel_count + 5] = r_sample;
}
} 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) {
// Games seem to ignore the center channel half the time, we use the front left
// and right channel for mixing as that's where majority of the audio goes
buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample);
} else if (stream_channel_count == 2) {
// Mix all channels into 2 channels
const auto [left, right] = Mix6To2WithCoefficients(
fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample,
sink_context.GetDownmixCoefficients());
buffer[i * stream_channel_count + 0] = left;
buffer[i * stream_channel_count + 1] = right;
} else if (stream_channel_count == 6) {
// Pass through
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() {
if (!stream->IsPlaying()) {
return;
}
{
std::scoped_lock lock{mutex};
const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)};
for (const auto& tag : released_buffers) {
QueueMixedBuffer(tag);
}
}
const f32 sample_rate = static_cast<f32>(GetSampleRate());
const f32 sample_count = static_cast<f32>(GetSampleCount());
const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240));
const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1;
const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1));
core_timing.ScheduleEvent(next_event_time, process_event, {});
}
} // namespace AudioCore

View File

@@ -1,78 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <mutex>
#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"
#include "core/hle/result.h"
namespace Core::Timing {
class CoreTiming;
}
namespace Core::Memory {
class Memory;
}
namespace AudioCore {
using DSPStateHolder = std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>;
class AudioOut;
class AudioRenderer {
public:
AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
AudioCommon::AudioRendererParameter params,
Stream::ReleaseCallback&& release_callback, std::size_t instance_number);
~AudioRenderer();
[[nodiscard]] Result UpdateAudioRenderer(const std::vector<u8>& input_params,
std::vector<u8>& output_params);
[[nodiscard]] Result Start();
[[nodiscard]] Result Stop();
void QueueMixedBuffer(Buffer::Tag tag);
void ReleaseAndQueueBuffers();
[[nodiscard]] u32 GetSampleRate() const;
[[nodiscard]] u32 GetSampleCount() const;
[[nodiscard]] u32 GetMixBufferCount() const;
[[nodiscard]] Stream::State GetStreamState() const;
private:
BehaviorInfo behavior_info{};
AudioCommon::AudioRendererParameter worker_params;
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::unique_ptr<AudioOut> audio_out;
StreamPtr stream;
Core::Memory::Memory& memory;
CommandGenerator command_generator;
std::size_t elapsed_frame_count{};
Core::Timing::CoreTiming& core_timing;
std::shared_ptr<Core::Timing::EventType> process_event;
std::mutex mutex;
};
} // namespace AudioCore

View File

@@ -1,104 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "audio_core/behavior_info.h"
#include "audio_core/common.h"
#include "common/logging/log.h"
namespace AudioCore {
BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
BehaviorInfo::~BehaviorInfo() = default;
bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
OutParams params{};
std::memcpy(params.errors.data(), errors.data(), sizeof(ErrorInfo) * errors.size());
params.error_count = static_cast<u32_le>(error_count);
std::memcpy(buffer.data() + offset, &params, sizeof(OutParams));
return true;
}
void BehaviorInfo::ClearError() {
error_count = 0;
}
void BehaviorInfo::UpdateFlags(u64_le dest_flags) {
flags = dest_flags;
}
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 AudioCommon::IsRevisionSupported(2, user_revision);
}
bool BehaviorInfo::IsSplitterSupported() const {
return AudioCommon::IsRevisionSupported(2, user_revision);
}
bool BehaviorInfo::IsLongSizePreDelaySupported() const {
return AudioCommon::IsRevisionSupported(3, user_revision);
}
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
return AudioCommon::IsRevisionSupported(4, user_revision);
}
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
return AudioCommon::IsRevisionSupported(1, user_revision);
}
bool BehaviorInfo::IsElapsedFrameCountSupported() const {
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

View File

@@ -1,71 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
class BehaviorInfo {
public:
struct ErrorInfo {
u32_le result{};
INSERT_PADDING_WORDS(1);
u64_le result_info{};
};
static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
struct InParams {
u32_le revision{};
u32_le padding{};
u64_le flags{};
};
static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size");
struct OutParams {
std::array<ErrorInfo, 10> errors{};
u32_le error_count{};
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);
[[nodiscard]] u32_le GetUserRevision() const;
[[nodiscard]] u32_le GetProcessRevision() const;
[[nodiscard]] bool IsAdpcmLoopContextBugFixed() const;
[[nodiscard]] bool IsSplitterSupported() const;
[[nodiscard]] bool IsLongSizePreDelaySupported() const;
[[nodiscard]] bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
[[nodiscard]] bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
[[nodiscard]] bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
[[nodiscard]] bool IsElapsedFrameCountSupported() const;
[[nodiscard]] bool IsMemoryPoolForceMappingEnabled() const;
[[nodiscard]] bool IsFlushVoiceWaveBuffersSupported() const;
[[nodiscard]] bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
[[nodiscard]] bool IsVoicePitchAndSrcSkippedSupported() const;
[[nodiscard]] bool IsMixInParameterDirtyOnlyUpdateSupported() const;
[[nodiscard]] 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

View File

@@ -1,44 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <vector>
#include "common/common_types.h"
namespace AudioCore {
/**
* Represents a buffer of audio samples to be played in an audio stream
*/
class Buffer {
public:
using Tag = u64;
Buffer(Tag tag_, std::vector<s16>&& samples_) : tag{tag_}, samples{std::move(samples_)} {}
/// Returns the raw audio data for the buffer
std::vector<s16>& GetSamples() {
return samples;
}
/// Returns the raw audio data for the buffer
const std::vector<s16>& GetSamples() const {
return samples;
}
/// Returns the buffer tag, this is provided by the game to the audout service
Tag GetTag() const {
return tag;
}
private:
Tag tag;
std::vector<s16> samples;
};
using BufferPtr = std::shared_ptr<Buffer>;
} // namespace AudioCore

View File

@@ -1,77 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "audio_core/codec.h"
namespace AudioCore::Codec {
std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff,
ADPCMState& state) {
// GC-ADPCM with scale factor and variable coefficients.
// Frames are 8 bytes long containing 14 samples each.
// Samples are 4 bits (one nibble) long.
constexpr std::size_t FRAME_LEN = 8;
constexpr std::size_t SAMPLES_PER_FRAME = 14;
static constexpr std::array<int, 16> SIGNED_NIBBLES{
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
};
const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME;
const std::size_t ret_size =
sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two.
std::vector<s16> ret(ret_size);
int yn1 = state.yn1, yn2 = state.yn2;
const std::size_t NUM_FRAMES =
(sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up.
for (std::size_t framei = 0; framei < NUM_FRAMES; framei++) {
const int frame_header = data[framei * FRAME_LEN];
const int scale = 1 << (frame_header & 0xF);
const int idx = (frame_header >> 4) & 0x7;
// Coefficients are fixed point with 11 bits fractional part.
const int coef1 = coeff[idx * 2 + 0];
const int coef2 = coeff[idx * 2 + 1];
// Decodes an audio sample. One nibble produces one sample.
const auto decode_sample = [&](const int nibble) -> s16 {
const int xn = nibble * 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 outputi = framei * SAMPLES_PER_FRAME;
std::size_t datai = framei * FRAME_LEN + 1;
for (std::size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) {
const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]);
ret[outputi] = sample1;
outputi++;
const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]);
ret[outputi] = sample2;
outputi++;
datai++;
}
}
state.yn1 = static_cast<s16>(yn1);
state.yn2 = static_cast<s16>(yn2);
return ret;
}
} // namespace AudioCore::Codec

View File

@@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <vector>
#include "common/common_types.h"
namespace AudioCore::Codec {
enum class PcmFormat : u32 {
Invalid = 0,
Int8 = 1,
Int16 = 2,
Int24 = 3,
Int32 = 4,
PcmFloat = 5,
Adpcm = 6,
};
/// See: Codec::DecodeADPCM
struct ADPCMState {
// Two historical samples from previous processed buffer,
// required for ADPCM decoding
s16 yn1; ///< y[n-1]
s16 yn2; ///< y[n-2]
};
using ADPCM_Coeff = std::array<s16, 16>;
/**
* @param data Pointer to buffer that contains ADPCM data to decode
* @param size Size of buffer in bytes
* @param coeff ADPCM coefficients
* @param state ADPCM state, this is updated with new state
* @return Decoded stereo signed PCM16 data, sample_count in length
*/
std::vector<s16> DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff,
ADPCMState& state);
}; // namespace AudioCore::Codec

File diff suppressed because it is too large Load Diff

View File

@@ -1,110 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include "audio_core/common.h"
#include "audio_core/voice_context.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;
struct I3dl2ReverbParams;
struct I3dl2ReverbState;
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();
[[nodiscard]] std::span<s32> GetChannelMixBuffer(s32 channel);
[[nodiscard]] std::span<const s32> GetChannelMixBuffer(s32 channel) const;
[[nodiscard]] std::span<s32> GetMixBuffer(std::size_t index);
[[nodiscard]] std::span<const s32> GetMixBuffer(std::size_t index) const;
[[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const;
[[nodiscard]] 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);
[[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
std::span<const s32> data, u32 sample_count, u32 write_offset,
u32 write_count);
s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
std::span<s32> out_data, u32 sample_count, u32 read_offset, u32 read_count);
void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
std::vector<u8>& work_buffer);
void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear);
// DSP Code
template <typename T>
s32 DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<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

View File

@@ -1,132 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/result.h"
namespace AudioCommon {
namespace Audren {
constexpr Result ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
constexpr Result ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
} // namespace Audren
constexpr u8 BASE_REVISION = '0';
constexpr u32_le CURRENT_PROCESS_REVISION =
Common::MakeMagic('R', 'E', 'V', static_cast<u8>(BASE_REVISION + 0xA));
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 = 2;
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;
constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f;
constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f;
constexpr std::size_t I3DL2REVERB_TAPS = 20;
constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4;
using Fractional = s32;
template <typename T>
constexpr Fractional ToFractional(T x) {
return static_cast<Fractional>(x * static_cast<T>(0x4000));
}
constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) {
return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14);
}
constexpr s32 FractionalToFixed(Fractional x) {
const auto s = x & (1 << 13);
return static_cast<s32>(x >> 14) + s;
}
constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) {
return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time)));
}
static constexpr u32 VersionFromRevision(u32_le rev) {
// "REV7" -> 7
return ((rev >> 24) & 0xff) - 0x30;
}
static constexpr bool IsRevisionSupported(u32 required, u32_le user_revision) {
const auto base = VersionFromRevision(user_revision);
return required <= base;
}
static constexpr bool IsValidRevision(u32_le revision) {
const auto base = VersionFromRevision(revision);
constexpr auto max_rev = VersionFromRevision(CURRENT_PROCESS_REVISION);
return base <= max_rev;
}
static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std::size_t required) {
if (offset > size) {
return false;
}
if (size < required) {
return false;
}
if ((size - offset) < required) {
return false;
}
return true;
}
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

View File

@@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/renderer/behavior/behavior_info.h"
#include "audio_core/renderer/memory/memory_pool_info.h"
#include "audio_core/renderer/upsampler/upsampler_manager.h"
#include "common/common_types.h"
namespace AudioCore {
/**
* Execution mode of the audio renderer.
* Only Auto is currently supported.
*/
enum class ExecutionMode : u8 {
Auto,
Manual,
};
/**
* Parameters from the game, passed to the audio renderer for initialisation.
*/
struct AudioRendererParameterInternal {
/* 0x00 */ u32 sample_rate;
/* 0x04 */ u32 sample_count;
/* 0x08 */ u32 mixes;
/* 0x0C */ u32 sub_mixes;
/* 0x10 */ u32 voices;
/* 0x14 */ u32 sinks;
/* 0x18 */ u32 effects;
/* 0x1C */ u32 perf_frames;
/* 0x20 */ u16 voice_drop_enabled;
/* 0x22 */ u8 rendering_device;
/* 0x23 */ ExecutionMode execution_mode;
/* 0x24 */ u32 splitter_infos;
/* 0x28 */ s32 splitter_destinations;
/* 0x2C */ u32 external_context_size;
/* 0x30 */ u32 revision;
/* 0x34 */ char unk34[0x4];
};
static_assert(sizeof(AudioRendererParameterInternal) == 0x38,
"AudioRendererParameterInternal has the wrong size!");
/**
* Context for rendering, contains a bunch of useful fields for the command generator.
*/
struct AudioRendererSystemContext {
s32 session_id;
s8 channels;
s16 mix_buffer_count;
AudioRenderer::BehaviorInfo* behavior;
std::span<s32> depop_buffer;
AudioRenderer::UpsamplerManager* upsampler_manager;
AudioRenderer::MemoryPoolInfo* memory_pool_info;
};
} // namespace AudioCore

View File

@@ -0,0 +1,138 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <numeric>
#include <span>
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace AudioCore {
using CpuAddr = std::uintptr_t;
enum class PlayState : u8 {
Started,
Stopped,
Paused,
};
enum class SrcQuality : u8 {
Medium,
High,
Low,
};
enum class SampleFormat : u8 {
Invalid,
PcmInt8,
PcmInt16,
PcmInt24,
PcmInt32,
PcmFloat,
Adpcm,
};
enum class SessionTypes {
AudioIn,
AudioOut,
FinalOutputRecorder,
};
enum class Channels : u32 {
FrontLeft,
FrontRight,
Center,
LFE,
BackLeft,
BackRight,
};
// These are used by Delay, Reverb and I3dl2Reverb prior to Revision 11.
enum class OldChannels : u32 {
FrontLeft,
FrontRight,
BackLeft,
BackRight,
Center,
LFE,
};
constexpr u32 BufferCount = 32;
constexpr u32 MaxRendererSessions = 2;
constexpr u32 TargetSampleCount = 240;
constexpr u32 TargetSampleRate = 48'000;
constexpr u32 MaxChannels = 6;
constexpr u32 MaxMixBuffers = 24;
constexpr u32 MaxWaveBuffers = 4;
constexpr s32 LowestVoicePriority = 0xFF;
constexpr s32 HighestVoicePriority = 0;
constexpr u32 BufferAlignment = 0x40;
constexpr u32 WorkbufferAlignment = 0x1000;
constexpr s32 FinalMixId = 0;
constexpr s32 InvalidDistanceFromFinalMix = std::numeric_limits<s32>::min();
constexpr s32 UnusedSplitterId = -1;
constexpr s32 UnusedMixId = std::numeric_limits<s32>::max();
constexpr u32 InvalidNodeId = 0xF0000000;
constexpr s32 InvalidProcessOrder = -1;
constexpr u32 MaxBiquadFilters = 2;
constexpr u32 MaxEffects = 256;
constexpr bool IsChannelCountValid(u16 channel_count) {
return channel_count <= 6 &&
(channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6);
}
constexpr void UseOldChannelMapping(std::span<s16> inputs, std::span<s16> outputs) {
constexpr auto old_center{static_cast<u32>(OldChannels::Center)};
constexpr auto new_center{static_cast<u32>(Channels::Center)};
constexpr auto old_lfe{static_cast<u32>(OldChannels::LFE)};
constexpr auto new_lfe{static_cast<u32>(Channels::LFE)};
auto center{inputs[old_center]};
auto lfe{inputs[old_lfe]};
inputs[old_center] = inputs[new_center];
inputs[old_lfe] = inputs[new_lfe];
inputs[new_center] = center;
inputs[new_lfe] = lfe;
center = outputs[old_center];
lfe = outputs[old_lfe];
outputs[old_center] = outputs[new_center];
outputs[old_lfe] = outputs[new_lfe];
outputs[new_center] = center;
outputs[new_lfe] = lfe;
}
constexpr u32 GetSplitterInParamHeaderMagic() {
return Common::MakeMagic('S', 'N', 'D', 'H');
}
constexpr u32 GetSplitterInfoMagic() {
return Common::MakeMagic('S', 'N', 'D', 'I');
}
constexpr u32 GetSplitterSendDataMagic() {
return Common::MakeMagic('S', 'N', 'D', 'D');
}
constexpr size_t GetSampleFormatByteSize(SampleFormat format) {
switch (format) {
case SampleFormat::PcmInt8:
return 1;
case SampleFormat::PcmInt16:
return 2;
case SampleFormat::PcmInt24:
return 3;
case SampleFormat::PcmInt32:
case SampleFormat::PcmFloat:
return 4;
default:
return 2;
}
}
} // namespace AudioCore

View File

@@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <ranges>
#include <tuple>
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace AudioCore {
constexpr u32 CurrentRevision = 11;
enum class SupportTags {
CommandProcessingTimeEstimatorVersion4,
CommandProcessingTimeEstimatorVersion3,
CommandProcessingTimeEstimatorVersion2,
MultiTapBiquadFilterProcessing,
EffectInfoVer2,
WaveBufferVer2,
BiquadFilterFloatProcessing,
VolumeMixParameterPrecisionQ23,
MixInParameterDirtyOnlyUpdate,
BiquadFilterEffectStateClearBugFix,
VoicePlayedSampleCountResetAtLoopPoint,
VoicePitchAndSrcSkipped,
SplitterBugFix,
FlushVoiceWaveBuffers,
ElapsedFrameCount,
AudioRendererVariadicCommandBufferSize,
PerformanceMetricsDataFormatVersion2,
AudioRendererProcessingTimeLimit80Percent,
AudioRendererProcessingTimeLimit75Percent,
AudioRendererProcessingTimeLimit70Percent,
AdpcmLoopContextBugFix,
Splitter,
LongSizePreDelay,
AudioUsbDeviceOutput,
DeviceApiVersion2,
DelayChannelMappingChange,
ReverbChannelMappingChange,
I3dl2ReverbChannelMappingChange,
// Not a real tag, just here to get the count.
Size
};
constexpr u32 GetRevisionNum(u32 user_revision) {
if (user_revision >= 0x100) {
user_revision -= Common::MakeMagic('R', 'E', 'V', '0');
user_revision >>= 24;
}
return user_revision;
};
constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) {
constexpr std::array<std::pair<SupportTags, u32>, static_cast<u32>(SupportTags::Size)> features{
{
{SupportTags::AudioRendererProcessingTimeLimit70Percent, 1},
{SupportTags::Splitter, 2},
{SupportTags::AdpcmLoopContextBugFix, 2},
{SupportTags::LongSizePreDelay, 3},
{SupportTags::AudioUsbDeviceOutput, 4},
{SupportTags::AudioRendererProcessingTimeLimit75Percent, 4},
{SupportTags::VoicePlayedSampleCountResetAtLoopPoint, 5},
{SupportTags::VoicePitchAndSrcSkipped, 5},
{SupportTags::SplitterBugFix, 5},
{SupportTags::FlushVoiceWaveBuffers, 5},
{SupportTags::ElapsedFrameCount, 5},
{SupportTags::AudioRendererProcessingTimeLimit80Percent, 5},
{SupportTags::AudioRendererVariadicCommandBufferSize, 5},
{SupportTags::PerformanceMetricsDataFormatVersion2, 5},
{SupportTags::CommandProcessingTimeEstimatorVersion2, 5},
{SupportTags::BiquadFilterEffectStateClearBugFix, 6},
{SupportTags::BiquadFilterFloatProcessing, 7},
{SupportTags::VolumeMixParameterPrecisionQ23, 7},
{SupportTags::MixInParameterDirtyOnlyUpdate, 7},
{SupportTags::WaveBufferVer2, 8},
{SupportTags::CommandProcessingTimeEstimatorVersion3, 8},
{SupportTags::EffectInfoVer2, 9},
{SupportTags::CommandProcessingTimeEstimatorVersion4, 10},
{SupportTags::MultiTapBiquadFilterProcessing, 10},
{SupportTags::DelayChannelMappingChange, 11},
{SupportTags::ReverbChannelMappingChange, 11},
{SupportTags::I3dl2ReverbChannelMappingChange, 11},
}};
const auto& feature =
std::ranges::find_if(features, [tag](const auto& entry) { return entry.first == tag; });
if (feature == features.cend()) {
LOG_ERROR(Service_Audio, "Invalid SupportTag {}!", static_cast<u32>(tag));
return false;
}
user_revision = GetRevisionNum(user_revision);
return (*feature).second <= user_revision;
}
constexpr bool CheckValidRevision(u32 user_revision) {
return GetRevisionNum(user_revision) <= CurrentRevision;
};
} // namespace AudioCore

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
namespace AudioCore {
struct WaveBufferVersion1 {
CpuAddr buffer;
u64 buffer_size;
u32 start_offset;
u32 end_offset;
bool loop;
bool stream_ended;
CpuAddr context;
u64 context_size;
};
struct WaveBufferVersion2 {
CpuAddr buffer;
CpuAddr context;
u64 buffer_size;
u64 context_size;
u32 start_offset;
u32 end_offset;
u32 loop_start_offset;
u32 loop_end_offset;
s32 loop_count;
bool loop;
bool stream_ended;
};
} // namespace AudioCore

View File

@@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
namespace AudioCore {
/**
* Responsible for allocating up a workbuffer into multiple pieces.
* Takes in a buffer and size (it does not own them), and allocates up the buffer via Allocate.
*/
class WorkbufferAllocator {
public:
explicit WorkbufferAllocator(std::span<u8> buffer_, u64 size_)
: buffer{reinterpret_cast<u64>(buffer_.data())}, size{size_} {}
/**
* Allocate the given count of T elements, aligned to alignment.
*
* @param count - The number of elements to allocate.
* @param alignment - The required starting alignment.
* @return Non-owning container of allocated elements.
*/
template <typename T>
std::span<T> Allocate(u64 count, u64 alignment) {
u64 out{0};
u64 byte_size{count * sizeof(T)};
if (byte_size > 0) {
auto current{buffer + offset};
auto aligned_buffer{Common::AlignUp(current, alignment)};
if (aligned_buffer + byte_size <= buffer + size) {
out = aligned_buffer;
offset = byte_size - buffer + aligned_buffer;
} else {
LOG_ERROR(
Service_Audio,
"Allocated buffer was too small to hold new alloc.\nAllocator size={:08X}, "
"offset={:08X}.\nAttempting to allocate {:08X} with alignment={:02X}",
size, offset, byte_size, alignment);
count = 0;
}
}
return std::span<T>(reinterpret_cast<T*>(out), count);
}
/**
* Align the current offset to the given alignment.
*
* @param alignment - The required starting alignment.
*/
void Align(u64 alignment) {
auto current{buffer + offset};
auto aligned_buffer{Common::AlignUp(current, alignment)};
offset = 0 - buffer + aligned_buffer;
}
/**
* Get the current buffer offset.
*
* @return The current allocating offset.
*/
u64 GetCurrentOffset() const {
return offset;
}
/**
* Get the current buffer size.
*
* @return The size of the current buffer.
*/
u64 GetSize() const {
return size;
}
/**
* Get the remaining size that can be allocated.
*
* @return The remaining size left in the buffer.
*/
u64 GetRemainingSize() const {
return size - offset;
}
private:
/// The buffer into which we are allocating.
u64 buffer;
/// Size of the buffer we're allocating to.
u64 size;
/// Current offset into the buffer, an error will be thrown if it exceeds size.
u64 offset{};
};
} // namespace AudioCore

View File

@@ -1,249 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <atomic>
#include <cstring>
#include "audio_core/cubeb_sink.h"
#include "audio_core/stream.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/ring_buffer.h"
#include "common/settings.h"
#ifdef _WIN32
#include <objbase.h>
#endif
namespace AudioCore {
class CubebSinkStream final : public SinkStream {
public:
CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
const std::string& name)
: ctx{ctx_}, num_channels{std::min(num_channels_, 6u)} {
cubeb_stream_params params{};
params.rate = sample_rate;
params.channels = num_channels;
params.format = CUBEB_SAMPLE_S16NE;
params.prefs = CUBEB_STREAM_PREF_PERSIST;
switch (num_channels) {
case 1:
params.layout = CUBEB_LAYOUT_MONO;
break;
case 2:
params.layout = CUBEB_LAYOUT_STEREO;
break;
case 6:
params.layout = CUBEB_LAYOUT_3F2_LFE;
break;
}
u32 minimum_latency{};
if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency");
}
if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device,
&params, std::max(512u, minimum_latency),
&CubebSinkStream::DataCallback, &CubebSinkStream::StateCallback,
this) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream");
return;
}
if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
return;
}
}
~CubebSinkStream() override {
if (!ctx) {
return;
}
if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
}
cubeb_stream_destroy(stream_backend);
}
void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override {
if (source_num_channels > num_channels) {
// Downsample 6 channels to 2
ASSERT_MSG(source_num_channels == 6, "Channel count must be 6");
std::vector<s16> buf;
buf.reserve(samples.size() * num_channels / source_num_channels);
for (std::size_t i = 0; i < samples.size(); i += source_num_channels) {
// Downmixing implementation taken from the ATSC standard
const s16 left{samples[i + 0]};
const s16 right{samples[i + 1]};
const s16 center{samples[i + 2]};
const s16 surround_left{samples[i + 4]};
const s16 surround_right{samples[i + 5]};
// Not used in the ATSC reference implementation
[[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]};
constexpr s32 clev{707}; // center mixing level coefficient
constexpr s32 slev{707}; // surround mixing level coefficient
buf.push_back(static_cast<s16>(left + (clev * center / 1000) +
(slev * surround_left / 1000)));
buf.push_back(static_cast<s16>(right + (clev * center / 1000) +
(slev * surround_right / 1000)));
}
queue.Push(buf);
return;
}
queue.Push(samples);
}
std::size_t SamplesInQueue(u32 channel_count) const override {
if (!ctx)
return 0;
return queue.Size() / channel_count;
}
void Flush() override {
should_flush = true;
}
u32 GetNumChannels() const {
return num_channels;
}
private:
std::vector<std::string> device_list;
cubeb* ctx{};
cubeb_stream* stream_backend{};
u32 num_channels{};
Common::RingBuffer<s16, 0x10000> queue;
std::array<s16, 2> last_frame{};
std::atomic<bool> should_flush{};
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
void* output_buffer, long num_frames);
static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
};
CubebSink::CubebSink(std::string_view target_device_name) {
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
#ifdef _WIN32
com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif
if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return;
}
if (target_device_name != auto_device_name && !target_device_name.empty()) {
cubeb_device_collection collection;
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
} else {
const auto collection_end{collection.device + collection.count};
const auto device{
std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
return info.friendly_name != nullptr &&
target_device_name == info.friendly_name;
})};
if (device != collection_end) {
output_device = device->devid;
}
cubeb_device_collection_destroy(ctx, &collection);
}
}
}
CubebSink::~CubebSink() {
if (!ctx) {
return;
}
for (auto& sink_stream : sink_streams) {
sink_stream.reset();
}
cubeb_destroy(ctx);
#ifdef _WIN32
if (SUCCEEDED(com_init_result)) {
CoUninitialize();
}
#endif
}
SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
const std::string& name) {
sink_streams.push_back(
std::make_unique<CubebSinkStream>(ctx, sample_rate, num_channels, output_device, name));
return *sink_streams.back();
}
long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
[[maybe_unused]] const void* input_buffer, void* output_buffer,
long num_frames) {
auto* impl = static_cast<CubebSinkStream*>(user_data);
auto* buffer = static_cast<u8*>(output_buffer);
if (!impl) {
return {};
}
const std::size_t num_channels = impl->GetNumChannels();
const std::size_t samples_to_write = num_channels * num_frames;
const std::size_t samples_written = impl->queue.Pop(buffer, samples_to_write);
if (samples_written >= num_channels) {
std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
num_channels * sizeof(s16));
}
// Fill the rest of the frames with last_frame
for (std::size_t i = samples_written; i < samples_to_write; i += num_channels) {
std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16));
}
return num_frames;
}
void CubebSinkStream::StateCallback([[maybe_unused]] cubeb_stream* stream,
[[maybe_unused]] void* user_data,
[[maybe_unused]] cubeb_state state) {}
std::vector<std::string> ListCubebSinkDevices() {
std::vector<std::string> device_list;
cubeb* ctx;
if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return {};
}
cubeb_device_collection collection;
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
} else {
for (std::size_t i = 0; i < collection.count; i++) {
const cubeb_device_info& device = collection.device[i];
if (device.friendly_name) {
device_list.emplace_back(device.friendly_name);
}
}
cubeb_device_collection_destroy(ctx, &collection);
}
cubeb_destroy(ctx);
return device_list;
}
} // namespace AudioCore

View File

@@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <vector>
#include <cubeb/cubeb.h>
#include "audio_core/sink.h"
namespace AudioCore {
class CubebSink final : public Sink {
public:
explicit CubebSink(std::string_view device_id);
~CubebSink() override;
SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
const std::string& name) override;
private:
cubeb* ctx{};
cubeb_devid output_device{};
std::vector<SinkStreamPtr> sink_streams;
#ifdef _WIN32
u32 com_init_result = 0;
#endif
};
std::vector<std::string> ListCubebSinkDevices();
} // namespace AudioCore

View File

@@ -1,107 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "audio_core/delay_line.h"
namespace AudioCore {
DelayLineBase::DelayLineBase() = default;
DelayLineBase::~DelayLineBase() = default;
void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) {
buffer = src_buffer;
buffer_end = buffer + max_delay_;
max_delay = max_delay_;
output = buffer;
SetDelay(max_delay_);
Clear();
}
void DelayLineBase::SetDelay(s32 new_delay) {
if (max_delay < new_delay) {
return;
}
delay = new_delay;
input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1));
}
s32 DelayLineBase::GetDelay() const {
return delay;
}
s32 DelayLineBase::GetMaxDelay() const {
return max_delay;
}
f32 DelayLineBase::TapOut(s32 last_sample) {
const float* ptr = input - (last_sample + 1);
if (ptr < buffer) {
ptr += (max_delay + 1);
}
return *ptr;
}
f32 DelayLineBase::Tick(f32 sample) {
*(input++) = sample;
const auto out_sample = *(output++);
if (buffer_end < input) {
input = buffer;
}
if (buffer_end < output) {
output = buffer;
}
return out_sample;
}
float* DelayLineBase::GetInput() {
return input;
}
const float* DelayLineBase::GetInput() const {
return input;
}
f32 DelayLineBase::GetOutputSample() const {
return *output;
}
void DelayLineBase::Clear() {
std::memset(buffer, 0, sizeof(float) * max_delay);
}
void DelayLineBase::Reset() {
buffer = nullptr;
buffer_end = nullptr;
max_delay = 0;
input = nullptr;
output = nullptr;
delay = 0;
}
DelayLineAllPass::DelayLineAllPass() = default;
DelayLineAllPass::~DelayLineAllPass() = default;
void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) {
DelayLineBase::Initialize(delay_, src_buffer);
SetCoefficient(coeffcient_);
}
void DelayLineAllPass::SetCoefficient(float coeffcient_) {
coefficient = coeffcient_;
}
f32 DelayLineAllPass::Tick(f32 sample) {
const auto temp = sample - coefficient * *output;
return coefficient * temp + DelayLineBase::Tick(temp);
}
void DelayLineAllPass::Reset() {
coefficient = 0.0f;
DelayLineBase::Reset();
}
} // namespace AudioCore

View File

@@ -1,49 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
namespace AudioCore {
class DelayLineBase {
public:
DelayLineBase();
~DelayLineBase();
void Initialize(s32 max_delay_, float* src_buffer);
void SetDelay(s32 new_delay);
s32 GetDelay() const;
s32 GetMaxDelay() const;
f32 TapOut(s32 last_sample);
f32 Tick(f32 sample);
float* GetInput();
const float* GetInput() const;
f32 GetOutputSample() const;
void Clear();
void Reset();
protected:
float* buffer{nullptr};
float* buffer_end{nullptr};
s32 max_delay{};
float* input{nullptr};
float* output{nullptr};
s32 delay{};
};
class DelayLineAllPass final : public DelayLineBase {
public:
DelayLineAllPass();
~DelayLineAllPass();
void Initialize(u32 delay, float coeffcient_, f32* src_buffer);
void SetCoefficient(float coeffcient_);
f32 Tick(f32 sample);
void Reset();
private:
float coefficient{};
};
} // namespace AudioCore

View File

@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
namespace AudioCore {
struct AudioBuffer {
/// Timestamp this buffer completed playing.
s64 played_timestamp;
/// Game memory address for these samples.
VAddr samples;
/// Unqiue identifier for this buffer.
u64 tag;
/// Size of the samples buffer.
u64 size;
};
} // namespace AudioCore

View File

@@ -0,0 +1,304 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <mutex>
#include <span>
#include <vector>
#include "audio_buffer.h"
#include "audio_core/device/device_session.h"
#include "core/core_timing.h"
namespace AudioCore {
constexpr s32 BufferAppendLimit = 4;
/**
* A ringbuffer of N audio buffers.
* The buffer contains 3 sections:
* Appended - Buffers added to the ring, but have yet to be sent to the audio backend.
* Registered - Buffers sent to the backend and queued for playback.
* Released - Buffers which have been played, and can now be recycled.
* Any others are free/untracked.
*
* @tparam N - Maximum number of buffers in the ring.
*/
template <size_t N>
class AudioBuffers {
public:
explicit AudioBuffers(size_t limit) : append_limit{static_cast<u32>(limit)} {}
/**
* Append a new audio buffer to the ring.
*
* @param buffer - The new buffer.
*/
void AppendBuffer(AudioBuffer& buffer) {
std::scoped_lock l{lock};
buffers[appended_index] = buffer;
appended_count++;
appended_index = (appended_index + 1) % append_limit;
}
/**
* Register waiting buffers, up to a maximum of BufferAppendLimit.
*
* @param out_buffers - The buffers which were registered.
*/
void RegisterBuffers(std::vector<AudioBuffer>& out_buffers) {
std::scoped_lock l{lock};
const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit),
BufferAppendLimit - registered_count)};
for (s32 i = 0; i < to_register; i++) {
s32 index{appended_index - appended_count};
if (index < 0) {
index += N;
}
out_buffers.push_back(buffers[index]);
registered_count++;
registered_index = (registered_index + 1) % append_limit;
appended_count--;
if (appended_count == 0) {
break;
}
}
}
/**
* Release a single buffer. Must be already registered.
*
* @param index - The buffer index to release.
* @param timestamp - The released timestamp for this buffer.
*/
void ReleaseBuffer(s32 index, s64 timestamp) {
std::scoped_lock l{lock};
buffers[index].played_timestamp = timestamp;
registered_count--;
released_count++;
released_index = (released_index + 1) % append_limit;
}
/**
* Release all registered buffers.
*
* @param timestamp - The released timestamp for this buffer.
* @return Is the buffer was released.
*/
bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) {
std::scoped_lock l{lock};
bool buffer_released{false};
while (registered_count > 0) {
auto index{registered_index - registered_count};
if (index < 0) {
index += N;
}
// Check with the backend if this buffer can be released yet.
if (!session.IsBufferConsumed(buffers[index].tag)) {
break;
}
ReleaseBuffer(index, core_timing.GetGlobalTimeNs().count());
buffer_released = true;
}
return buffer_released || registered_count == 0;
}
/**
* Get all released buffers.
*
* @param tags - Container to be filled with the released buffers' tags.
* @return The number of buffers released.
*/
u32 GetReleasedBuffers(std::span<u64> tags) {
std::scoped_lock l{lock};
u32 released{0};
while (released_count > 0) {
auto index{released_index - released_count};
if (index < 0) {
index += N;
}
auto& buffer{buffers[index]};
released_count--;
auto tag{buffer.tag};
buffer.played_timestamp = 0;
buffer.samples = 0;
buffer.tag = 0;
buffer.size = 0;
if (tag == 0) {
break;
}
tags[released++] = tag;
if (released >= tags.size()) {
break;
}
}
return released;
}
/**
* Get all appended and registered buffers.
*
* @param buffers_flushed - Output vector for the buffers which are released.
* @param max_buffers - Maximum number of buffers to released.
* @return The number of buffers released.
*/
u32 GetRegisteredAppendedBuffers(std::vector<AudioBuffer>& buffers_flushed, u32 max_buffers) {
std::scoped_lock l{lock};
if (registered_count + appended_count == 0) {
return 0;
}
size_t buffers_to_flush{
std::min(static_cast<u32>(registered_count + appended_count), max_buffers)};
if (buffers_to_flush == 0) {
return 0;
}
while (registered_count > 0) {
auto index{registered_index - registered_count};
if (index < 0) {
index += N;
}
buffers_flushed.push_back(buffers[index]);
registered_count--;
released_count++;
released_index = (released_index + 1) % append_limit;
if (buffers_flushed.size() >= buffers_to_flush) {
break;
}
}
while (appended_count > 0) {
auto index{appended_index - appended_count};
if (index < 0) {
index += N;
}
buffers_flushed.push_back(buffers[index]);
appended_count--;
released_count++;
released_index = (released_index + 1) % append_limit;
if (buffers_flushed.size() >= buffers_to_flush) {
break;
}
}
return static_cast<u32>(buffers_flushed.size());
}
/**
* Check if the given tag is in the buffers.
*
* @param tag - Unique tag of the buffer to search for.
* @return True if the buffer is still in the ring, otherwise false.
*/
bool ContainsBuffer(const u64 tag) const {
std::scoped_lock l{lock};
const auto registered_buffers{appended_count + registered_count + released_count};
if (registered_buffers == 0) {
return false;
}
auto index{released_index - released_count};
if (index < 0) {
index += append_limit;
}
for (s32 i = 0; i < registered_buffers; i++) {
if (buffers[index].tag == tag) {
return true;
}
index = (index + 1) % append_limit;
}
return false;
}
/**
* Get the number of active buffers in the ring.
* That is, appended, registered and released buffers.
*
* @return Number of active buffers.
*/
u32 GetAppendedRegisteredCount() const {
std::scoped_lock l{lock};
return appended_count + registered_count;
}
/**
* Get the total number of active buffers in the ring.
* That is, appended, registered and released buffers.
*
* @return Number of active buffers.
*/
u32 GetTotalBufferCount() const {
std::scoped_lock l{lock};
return static_cast<u32>(appended_count + registered_count + released_count);
}
/**
* Flush all of the currently appended and registered buffers
*
* @param buffers_released - Output count for the number of buffers released.
* @return True if buffers were successfully flushed, otherwise false.
*/
bool FlushBuffers(u32& buffers_released) {
std::scoped_lock l{lock};
std::vector<AudioBuffer> buffers_flushed{};
buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit);
if (registered_count > 0) {
return false;
}
if (static_cast<u32>(released_count + appended_count) > append_limit) {
return false;
}
return true;
}
private:
/// Buffer lock
mutable std::recursive_mutex lock{};
/// The audio buffers
std::array<AudioBuffer, N> buffers{};
/// Current released index
s32 released_index{};
/// Number of released buffers
s32 released_count{};
/// Current registered index
s32 registered_index{};
/// Number of registered buffers
s32 registered_count{};
/// Current appended index
s32 appended_index{};
/// Number of appended buffers
s32 appended_count{};
/// Maximum number of buffers (default 32)
u32 append_limit{};
};
} // namespace AudioCore

View File

@@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_core.h"
#include "audio_core/audio_manager.h"
#include "audio_core/device/audio_buffer.h"
#include "audio_core/device/device_session.h"
#include "audio_core/sink/sink_stream.h"
#include "core/core.h"
#include "core/memory.h"
namespace AudioCore {
DeviceSession::DeviceSession(Core::System& system_) : system{system_} {}
DeviceSession::~DeviceSession() {
Finalize();
}
Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_,
u16 channel_count_, size_t session_id_, u32 handle_,
u64 applet_resource_user_id_, Sink::StreamType type_) {
if (stream) {
Finalize();
}
name = fmt::format("{}-{}", name_, session_id_);
type = type_;
sample_format = sample_format_;
channel_count = channel_count_;
session_id = session_id_;
handle = handle_;
applet_resource_user_id = applet_resource_user_id_;
if (type == Sink::StreamType::In) {
sink = &system.AudioCore().GetInputSink();
} else {
sink = &system.AudioCore().GetOutputSink();
}
stream = sink->AcquireSinkStream(system, channel_count, name, type);
initialized = true;
return ResultSuccess;
}
void DeviceSession::Finalize() {
if (initialized) {
Stop();
sink->CloseStream(stream);
stream = nullptr;
}
}
void DeviceSession::Start() {
stream->SetPlayedSampleCount(played_sample_count);
stream->Start();
}
void DeviceSession::Stop() {
if (stream) {
played_sample_count = stream->GetPlayedSampleCount();
stream->Stop();
}
}
void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
auto& memory{system.Memory()};
for (size_t i = 0; i < buffers.size(); i++) {
Sink::SinkBuffer new_buffer{
.frames = buffers[i].size / (channel_count * sizeof(s16)),
.frames_played = 0,
.tag = buffers[i].tag,
.consumed = false,
};
if (type == Sink::StreamType::In) {
std::vector<s16> samples{};
stream->AppendBuffer(new_buffer, samples);
} else {
std::vector<s16> samples(buffers[i].size / sizeof(s16));
memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size);
stream->AppendBuffer(new_buffer, samples);
}
}
}
void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const {
if (type == Sink::StreamType::In) {
auto& memory{system.Memory()};
auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
}
}
bool DeviceSession::IsBufferConsumed(u64 tag) const {
if (stream) {
return stream->IsBufferConsumed(tag);
}
return true;
}
void DeviceSession::SetVolume(f32 volume) const {
if (stream) {
stream->SetSystemVolume(volume);
}
}
u64 DeviceSession::GetPlayedSampleCount() const {
if (stream) {
return stream->GetPlayedSampleCount();
}
return 0;
}
} // namespace AudioCore

View File

@@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/common/common.h"
#include "audio_core/sink/sink.h"
#include "core/hle/service/audio/errors.h"
namespace Core {
class System;
}
namespace AudioCore {
namespace Sink {
class SinkStream;
struct SinkBuffer;
} // namespace Sink
struct AudioBuffer;
/**
* Represents an input or output device stream for audio in and audio out (not used for render).
**/
class DeviceSession {
public:
explicit DeviceSession(Core::System& system);
~DeviceSession();
/**
* Initialize this device session.
*
* @param name - Name of this device.
* @param sample_format - Sample format for this device's output.
* @param channel_count - Number of channels for this device (2 or 6).
* @param session_id - This session's id.
* @param handle - Handle for this device session (unused).
* @param applet_resource_user_id - Applet resource user id for this device session (unused).
* @param type - Type of this stream (Render, In, Out).
* @return Result code for this call.
*/
Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count,
size_t session_id, u32 handle, u64 applet_resource_user_id,
Sink::StreamType type);
/**
* Finalize this device session.
*/
void Finalize();
/**
* Append audio buffers to this device session to be played back.
*
* @param buffers - The buffers to play.
*/
void AppendBuffers(std::span<AudioBuffer> buffers) const;
/**
* (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
*
* @param buffer - The buffer to write to.
*/
void ReleaseBuffer(AudioBuffer& buffer) const;
/**
* Check if the buffer for the given tag has been consumed by the backend.
*
* @param tag - Unqiue tag of the buffer to check.
* @return true if the buffer has been consumed, otherwise false.
*/
bool IsBufferConsumed(u64 tag) const;
/**
* Start this device session, starting the backend stream.
*/
void Start();
/**
* Stop this device session, stopping the backend stream.
*/
void Stop();
/**
* Set this device session's volume.
*
* @param volume - New volume for this session.
*/
void SetVolume(f32 volume) const;
/**
* Get this device session's total played sample count.
*
* @return Samples played by this session.
*/
u64 GetPlayedSampleCount() const;
private:
/// System
Core::System& system;
/// Output sink this device will use
Sink::Sink* sink{};
/// The backend stream for this device session to send samples to
Sink::SinkStream* stream{};
/// Name of this device session
std::string name{};
/// Type of this device session (render/in/out)
Sink::StreamType type{};
/// Sample format for this device.
SampleFormat sample_format{SampleFormat::PcmInt16};
/// Channel count for this device session
u16 channel_count{};
/// Session id of this device session
size_t session_id{};
/// Handle of this device session
u32 handle{};
/// Applet resource user id of this device session
u64 applet_resource_user_id{};
/// Total number of samples played by this device session
u64 played_sample_count{};
/// Is this session initialised?
bool initialized{};
};
} // namespace AudioCore

View File

@@ -1,320 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "audio_core/effect_context.h"
namespace AudioCore {
namespace {
bool ValidChannelCountForEffect(s32 channel_count) {
return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
}
} // namespace
EffectContext::EffectContext(std::size_t effect_count_) : effect_count(effect_count_) {
effects.reserve(effect_count);
std::generate_n(std::back_inserter(effects), effect_count,
[] { return std::make_unique<EffectStubbed>(); });
}
EffectContext::~EffectContext() = default;
std::size_t EffectContext::GetCount() const {
return effect_count;
}
EffectBase* EffectContext::GetInfo(std::size_t i) {
return effects.at(i).get();
}
EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
switch (effect) {
case EffectType::Invalid:
effects[i] = std::make_unique<EffectStubbed>();
break;
case EffectType::BufferMixer:
effects[i] = std::make_unique<EffectBufferMixer>();
break;
case EffectType::Aux:
effects[i] = std::make_unique<EffectAuxInfo>();
break;
case EffectType::Delay:
effects[i] = std::make_unique<EffectDelay>();
break;
case EffectType::Reverb:
effects[i] = std::make_unique<EffectReverb>();
break;
case EffectType::I3dl2Reverb:
effects[i] = std::make_unique<EffectI3dl2Reverb>();
break;
case EffectType::BiquadFilter:
effects[i] = std::make_unique<EffectBiquadFilter>();
break;
default:
ASSERT_MSG(false, "Unimplemented effect {}", effect);
effects[i] = std::make_unique<EffectStubbed>();
}
return GetInfo(i);
}
const EffectBase* EffectContext::GetInfo(std::size_t i) const {
return effects.at(i).get();
}
EffectStubbed::EffectStubbed() : EffectBase(EffectType::Invalid) {}
EffectStubbed::~EffectStubbed() = default;
void EffectStubbed::Update([[maybe_unused]] EffectInfo::InParams& in_params) {}
void EffectStubbed::UpdateForCommandGeneration() {}
EffectBase::EffectBase(EffectType effect_type_) : effect_type(effect_type_) {}
EffectBase::~EffectBase() = default;
UsageState EffectBase::GetUsage() const {
return usage;
}
EffectType EffectBase::GetType() const {
return effect_type;
}
bool EffectBase::IsEnabled() const {
return enabled;
}
s32 EffectBase::GetMixID() const {
return mix_id;
}
s32 EffectBase::GetProcessingOrder() const {
return processing_order;
}
std::vector<u8>& EffectBase::GetWorkBuffer() {
return work_buffer;
}
const std::vector<u8>& EffectBase::GetWorkBuffer() const {
return work_buffer;
}
EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {}
EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
auto& params = GetParams();
const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
ASSERT_MSG(false, "Invalid reverb max channel count {}", reverb_params->max_channels);
return;
}
const auto last_status = params.status;
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
params = *reverb_params;
if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
params.channel_count = params.max_channels;
}
enabled = in_params.is_enabled;
if (last_status != ParameterStatus::Updated) {
params.status = last_status;
}
if (in_params.is_new || skipped) {
usage = UsageState::Initialized;
params.status = ParameterStatus::Initialized;
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
if (!skipped) {
auto& cur_work_buffer = GetWorkBuffer();
// Has two buffers internally
cur_work_buffer.resize(in_params.buffer_size * 2);
std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0);
}
}
}
void EffectI3dl2Reverb::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
I3dl2ReverbState& EffectI3dl2Reverb::GetState() {
return state;
}
const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const {
return state;
}
EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {}
EffectBiquadFilter::~EffectBiquadFilter() = default;
void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
auto& params = GetParams();
const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
params = *biquad_params;
enabled = in_params.is_enabled;
}
void EffectBiquadFilter::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
EffectAuxInfo::EffectAuxInfo() : EffectGeneric(EffectType::Aux) {}
EffectAuxInfo::~EffectAuxInfo() = default;
void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
GetParams() = *aux_params;
enabled = in_params.is_enabled;
if (in_params.is_new || skipped) {
skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
if (skipped) {
return;
}
// There's two AuxInfos which are an identical size, the first one is managed by the cpu,
// the second is managed by the dsp. All we care about is managing the DSP one
send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
}
}
void EffectAuxInfo::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
}
VAddr EffectAuxInfo::GetSendInfo() const {
return send_info;
}
VAddr EffectAuxInfo::GetSendBuffer() const {
return send_buffer;
}
VAddr EffectAuxInfo::GetRecvInfo() const {
return recv_info;
}
VAddr EffectAuxInfo::GetRecvBuffer() const {
return recv_buffer;
}
EffectDelay::EffectDelay() : EffectGeneric(EffectType::Delay) {}
EffectDelay::~EffectDelay() = default;
void EffectDelay::Update(EffectInfo::InParams& in_params) {
const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
auto& params = GetParams();
if (!ValidChannelCountForEffect(delay_params->max_channels)) {
return;
}
const auto last_status = params.status;
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
params = *delay_params;
if (!ValidChannelCountForEffect(delay_params->channels)) {
params.channels = params.max_channels;
}
enabled = in_params.is_enabled;
if (last_status != ParameterStatus::Updated) {
params.status = last_status;
}
if (in_params.is_new || skipped) {
usage = UsageState::Initialized;
params.status = ParameterStatus::Initialized;
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
}
}
void EffectDelay::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
EffectBufferMixer::EffectBufferMixer() : EffectGeneric(EffectType::BufferMixer) {}
EffectBufferMixer::~EffectBufferMixer() = default;
void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
enabled = in_params.is_enabled;
}
void EffectBufferMixer::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
}
EffectReverb::EffectReverb() : EffectGeneric(EffectType::Reverb) {}
EffectReverb::~EffectReverb() = default;
void EffectReverb::Update(EffectInfo::InParams& in_params) {
const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
auto& params = GetParams();
if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
return;
}
const auto last_status = params.status;
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
params = *reverb_params;
if (!ValidChannelCountForEffect(reverb_params->channels)) {
params.channels = params.max_channels;
}
enabled = in_params.is_enabled;
if (last_status != ParameterStatus::Updated) {
params.status = last_status;
}
if (in_params.is_new || skipped) {
usage = UsageState::Initialized;
params.status = ParameterStatus::Initialized;
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
}
}
void EffectReverb::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
} // namespace AudioCore

View File

@@ -1,349 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <vector>
#include "audio_core/common.h"
#include "audio_core/delay_line.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
enum class EffectType : u8 {
Invalid = 0,
BufferMixer = 1,
Aux = 2,
Delay = 3,
Reverb = 4,
I3dl2Reverb = 5,
BiquadFilter = 6,
};
enum class UsageStatus : u8 {
Invalid = 0,
New = 1,
Initialized = 2,
Used = 3,
Removed = 4,
};
enum class UsageState {
Invalid = 0,
Initialized = 1,
Running = 2,
Stopped = 3,
};
enum class ParameterStatus : u8 {
Initialized = 0,
Updating = 1,
Updated = 2,
};
struct BufferMixerParams {
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
s32_le count{};
};
static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
struct AuxInfoDSP {
u32_le read_offset{};
u32_le write_offset{};
u32_le remaining{};
INSERT_PADDING_WORDS(13);
};
static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
struct AuxInfo {
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
u32_le count{};
s32_le sample_rate{};
s32_le sample_count{};
s32_le mix_buffer_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 I3dl2ReverbParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
u16_le max_channels{};
u16_le channel_count{};
INSERT_PADDING_BYTES(1);
u32_le sample_rate{};
f32 room_hf{};
f32 hf_reference{};
f32 decay_time{};
f32 hf_decay_ratio{};
f32 room{};
f32 reflection{};
f32 reverb{};
f32 diffusion{};
f32 reflection_delay{};
f32 reverb_delay{};
f32 density{};
f32 dry_gain{};
ParameterStatus status{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
struct BiquadFilterParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
std::array<s16_le, 3> numerator;
std::array<s16_le, 2> denominator;
s8 channel_count{};
ParameterStatus status{};
};
static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
struct DelayParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
u16_le max_channels{};
u16_le channels{};
s32_le max_delay{};
s32_le delay{};
s32_le sample_rate{};
s32_le gain{};
s32_le feedback_gain{};
s32_le out_gain{};
s32_le dry_gain{};
s32_le channel_spread{};
s32_le low_pass{};
ParameterStatus status{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
struct ReverbParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
u16_le max_channels{};
u16_le channels{};
s32_le sample_rate{};
s32_le mode0{};
s32_le mode0_gain{};
s32_le pre_delay{};
s32_le mode1{};
s32_le mode1_gain{};
s32_le decay{};
s32_le hf_decay_ratio{};
s32_le coloration{};
s32_le reverb_gain{};
s32_le out_gain{};
s32_le dry_gain{};
ParameterStatus status{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
class EffectInfo {
public:
struct InParams {
EffectType type{};
u8 is_new{};
u8 is_enabled{};
INSERT_PADDING_BYTES(1);
s32_le mix_id{};
u64_le buffer_address{};
u64_le buffer_size{};
s32_le processing_order{};
INSERT_PADDING_BYTES(4);
union {
std::array<u8, 0xa0> raw;
};
};
static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size");
struct OutParams {
UsageStatus status{};
INSERT_PADDING_BYTES(15);
};
static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size");
};
struct AuxAddress {
VAddr send_dsp_info{};
VAddr send_buffer_base{};
VAddr return_dsp_info{};
VAddr return_buffer_base{};
};
class EffectBase {
public:
explicit EffectBase(EffectType effect_type_);
virtual ~EffectBase();
virtual void Update(EffectInfo::InParams& in_params) = 0;
virtual void UpdateForCommandGeneration() = 0;
[[nodiscard]] UsageState GetUsage() const;
[[nodiscard]] EffectType GetType() const;
[[nodiscard]] bool IsEnabled() const;
[[nodiscard]] s32 GetMixID() const;
[[nodiscard]] s32 GetProcessingOrder() const;
[[nodiscard]] std::vector<u8>& GetWorkBuffer();
[[nodiscard]] const std::vector<u8>& GetWorkBuffer() const;
protected:
UsageState usage{UsageState::Invalid};
EffectType effect_type{};
s32 mix_id{};
s32 processing_order{};
bool enabled = false;
std::vector<u8> work_buffer{};
};
template <typename T>
class EffectGeneric : public EffectBase {
public:
explicit EffectGeneric(EffectType effect_type_) : EffectBase(effect_type_) {}
T& GetParams() {
return internal_params;
}
const T& GetParams() const {
return internal_params;
}
private:
T internal_params{};
};
class EffectStubbed : public EffectBase {
public:
explicit EffectStubbed();
~EffectStubbed() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
};
struct I3dl2ReverbState {
f32 lowpass_0{};
f32 lowpass_1{};
f32 lowpass_2{};
DelayLineBase early_delay_line{};
std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{};
f32 early_gain{};
f32 late_gain{};
u32 early_to_late_taps{};
std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{};
std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{};
std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{};
f32 last_reverb_echo{};
DelayLineBase center_delay_line{};
std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{};
std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{};
f32 dry_gain{};
};
class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
public:
explicit EffectI3dl2Reverb();
~EffectI3dl2Reverb() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
I3dl2ReverbState& GetState();
const I3dl2ReverbState& GetState() const;
private:
bool skipped = false;
I3dl2ReverbState state{};
};
class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
public:
explicit EffectBiquadFilter();
~EffectBiquadFilter() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
};
class EffectAuxInfo : public EffectGeneric<AuxInfo> {
public:
explicit EffectAuxInfo();
~EffectAuxInfo() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
[[nodiscard]] VAddr GetSendInfo() const;
[[nodiscard]] VAddr GetSendBuffer() const;
[[nodiscard]] VAddr GetRecvInfo() const;
[[nodiscard]] VAddr GetRecvBuffer() const;
private:
VAddr send_info{};
VAddr send_buffer{};
VAddr recv_info{};
VAddr recv_buffer{};
bool skipped = false;
AuxAddress addresses{};
};
class EffectDelay : public EffectGeneric<DelayParams> {
public:
explicit EffectDelay();
~EffectDelay() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
private:
bool skipped = false;
};
class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
public:
explicit EffectBufferMixer();
~EffectBufferMixer() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
};
class EffectReverb : public EffectGeneric<ReverbParams> {
public:
explicit EffectReverb();
~EffectReverb() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
private:
bool skipped = false;
};
class EffectContext {
public:
explicit EffectContext(std::size_t effect_count_);
~EffectContext();
[[nodiscard]] std::size_t GetCount() const;
[[nodiscard]] EffectBase* GetInfo(std::size_t i);
[[nodiscard]] EffectBase* RetargetEffect(std::size_t i, EffectType effect);
[[nodiscard]] const EffectBase* GetInfo(std::size_t i) const;
private:
std::size_t effect_count{};
std::vector<std::unique_ptr<EffectBase>> effects;
};
} // namespace AudioCore

View File

@@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_in_manager.h"
#include "audio_core/in/audio_in.h"
#include "core/hle/kernel/k_event.h"
namespace AudioCore::AudioIn {
In::In(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_)
: manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event,
session_id_} {}
void In::Free() {
std::scoped_lock l{parent_mutex};
manager.ReleaseSessionId(system.GetSessionId());
}
System& In::GetSystem() {
return system;
}
AudioIn::State In::GetState() {
std::scoped_lock l{parent_mutex};
return system.GetState();
}
Result In::StartSystem() {
std::scoped_lock l{parent_mutex};
return system.Start();
}
void In::StartSession() {
std::scoped_lock l{parent_mutex};
system.StartSession();
}
Result In::StopSystem() {
std::scoped_lock l{parent_mutex};
return system.Stop();
}
Result In::AppendBuffer(const AudioInBuffer& buffer, u64 tag) {
std::scoped_lock l{parent_mutex};
if (system.AppendBuffer(buffer, tag)) {
return ResultSuccess;
}
return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED;
}
void In::ReleaseAndRegisterBuffers() {
std::scoped_lock l{parent_mutex};
if (system.GetState() == State::Started) {
system.ReleaseBuffers();
system.RegisterBuffers();
}
}
bool In::FlushAudioInBuffers() {
std::scoped_lock l{parent_mutex};
return system.FlushAudioInBuffers();
}
u32 In::GetReleasedBuffers(std::span<u64> tags) {
std::scoped_lock l{parent_mutex};
return system.GetReleasedBuffers(tags);
}
Kernel::KReadableEvent& In::GetBufferEvent() {
std::scoped_lock l{parent_mutex};
return event->GetReadableEvent();
}
f32 In::GetVolume() {
std::scoped_lock l{parent_mutex};
return system.GetVolume();
}
void In::SetVolume(f32 volume) {
std::scoped_lock l{parent_mutex};
system.SetVolume(volume);
}
bool In::ContainsAudioBuffer(u64 tag) {
std::scoped_lock l{parent_mutex};
return system.ContainsAudioBuffer(tag);
}
u32 In::GetBufferCount() {
std::scoped_lock l{parent_mutex};
return system.GetBufferCount();
}
u64 In::GetPlayedSampleCount() {
std::scoped_lock l{parent_mutex};
return system.GetPlayedSampleCount();
}
} // namespace AudioCore::AudioIn

View File

@@ -0,0 +1,147 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include "audio_core/in/audio_in_system.h"
namespace Core {
class System;
}
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace AudioCore::AudioIn {
class Manager;
/**
* Interface between the service and audio in system. Mainly responsible for forwarding service
* calls to the system.
*/
class In {
public:
explicit In(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id);
/**
* Free this audio in from the audio in manager.
*/
void Free();
/**
* Get this audio in's system.
*/
System& GetSystem();
/**
* Get the current state.
*
* @return Started or Stopped.
*/
AudioIn::State GetState();
/**
* Start the system
*
* @return Result code
*/
Result StartSystem();
/**
* Start the system's device session.
*/
void StartSession();
/**
* Stop the system.
*
* @return Result code
*/
Result StopSystem();
/**
* Append a new buffer to the system, the buffer event will be signalled when it is filled.
*
* @param buffer - The new buffer to append.
* @param tag - Unique tag for this buffer.
* @return Result code.
*/
Result AppendBuffer(const AudioInBuffer& buffer, u64 tag);
/**
* Release all completed buffers, and register any appended.
*/
void ReleaseAndRegisterBuffers();
/**
* Flush all buffers.
*/
bool FlushAudioInBuffers();
/**
* Get all of the currently released buffers.
*
* @param tags - Output container for the buffer tags which were released.
* @return The number of buffers released.
*/
u32 GetReleasedBuffers(std::span<u64> tags);
/**
* Get the buffer event for this audio in, this event will be signalled when a buffer is filled.
*
* @return The buffer event.
*/
Kernel::KReadableEvent& GetBufferEvent();
/**
* Get the current system volume.
*
* @return The current volume.
*/
f32 GetVolume();
/**
* Set the system volume.
*
* @param volume - The volume to set.
*/
void SetVolume(f32 volume);
/**
* Check if a buffer is in the system.
*
* @param tag - The tag to search for.
* @return True if the buffer is in the system, otherwise false.
*/
bool ContainsAudioBuffer(u64 tag);
/**
* Get the maximum number of buffers.
*
* @return The maximum number of buffers.
*/
u32 GetBufferCount();
/**
* Get the total played sample count for this audio in.
*
* @return The played sample count.
*/
u64 GetPlayedSampleCount();
private:
/// The AudioIn::Manager this audio in is registered with
Manager& manager;
/// Manager's mutex
std::recursive_mutex& parent_mutex;
/// Buffer event, signalled when buffers are ready to be released
Kernel::KEvent* event;
/// Main audio in system
System system;
};
} // namespace AudioCore::AudioIn

View File

@@ -0,0 +1,213 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "audio_core/audio_event.h"
#include "audio_core/audio_manager.h"
#include "audio_core/in/audio_in_system.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_event.h"
namespace AudioCore::AudioIn {
System::System(Core::System& system_, Kernel::KEvent* event_, const size_t session_id_)
: system{system_}, buffer_event{event_},
session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {}
System::~System() {
Finalize();
}
void System::Finalize() {
Stop();
session->Finalize();
buffer_event->GetWritableEvent().Signal();
}
void System::StartSession() {
session->Start();
}
size_t System::GetSessionId() const {
return session_id;
}
std::string_view System::GetDefaultDeviceName() {
return "BuiltInHeadset";
}
std::string_view System::GetDefaultUacDeviceName() {
return "Uac";
}
Result System::IsConfigValid(const std::string_view device_name,
const AudioInParameter& in_params) {
if ((device_name.size() > 0) &&
(device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) {
return Service::Audio::ERR_INVALID_DEVICE_NAME;
}
if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) {
return Service::Audio::ERR_INVALID_SAMPLE_RATE;
}
return ResultSuccess;
}
Result System::Initialize(std::string& device_name, const AudioInParameter& in_params,
const u32 handle_, const u64 applet_resource_user_id_) {
auto result{IsConfigValid(device_name, in_params)};
if (result.IsError()) {
return result;
}
handle = handle_;
applet_resource_user_id = applet_resource_user_id_;
if (device_name.empty() || device_name[0] == '\0') {
name = std::string(GetDefaultDeviceName());
} else {
name = std::move(device_name);
}
sample_rate = TargetSampleRate;
sample_format = SampleFormat::PcmInt16;
channel_count = in_params.channel_count <= 2 ? 2 : 6;
volume = 1.0f;
is_uac = name == "Uac";
return ResultSuccess;
}
Result System::Start() {
if (state != State::Stopped) {
return Service::Audio::ERR_OPERATION_FAILED;
}
session->Initialize(name, sample_format, channel_count, session_id, handle,
applet_resource_user_id, Sink::StreamType::In);
session->SetVolume(volume);
session->Start();
state = State::Started;
std::vector<AudioBuffer> buffers_to_flush{};
buffers.RegisterBuffers(buffers_to_flush);
session->AppendBuffers(buffers_to_flush);
return ResultSuccess;
}
Result System::Stop() {
if (state == State::Started) {
session->Stop();
session->SetVolume(0.0f);
state = State::Stopped;
}
return ResultSuccess;
}
bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
if (buffers.GetTotalBufferCount() == BufferCount) {
return false;
}
AudioBuffer new_buffer{
.played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size};
buffers.AppendBuffer(new_buffer);
RegisterBuffers();
return true;
}
void System::RegisterBuffers() {
if (state == State::Started) {
std::vector<AudioBuffer> registered_buffers{};
buffers.RegisterBuffers(registered_buffers);
session->AppendBuffers(registered_buffers);
}
}
void System::ReleaseBuffers() {
bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)};
if (signal) {
// Signal if any buffer was released, or if none are registered, we need more.
buffer_event->GetWritableEvent().Signal();
}
}
u32 System::GetReleasedBuffers(std::span<u64> tags) {
return buffers.GetReleasedBuffers(tags);
}
bool System::FlushAudioInBuffers() {
if (state != State::Started) {
return false;
}
u32 buffers_released{};
buffers.FlushBuffers(buffers_released);
if (buffers_released > 0) {
buffer_event->GetWritableEvent().Signal();
}
return true;
}
u16 System::GetChannelCount() const {
return channel_count;
}
u32 System::GetSampleRate() const {
return sample_rate;
}
SampleFormat System::GetSampleFormat() const {
return sample_format;
}
State System::GetState() {
switch (state) {
case State::Started:
case State::Stopped:
return state;
default:
LOG_ERROR(Service_Audio, "AudioIn invalid state!");
state = State::Stopped;
break;
}
return state;
}
std::string System::GetName() const {
return name;
}
f32 System::GetVolume() const {
return volume;
}
void System::SetVolume(const f32 volume_) {
volume = volume_;
session->SetVolume(volume_);
}
bool System::ContainsAudioBuffer(const u64 tag) {
return buffers.ContainsBuffer(tag);
}
u32 System::GetBufferCount() {
return buffers.GetAppendedRegisteredCount();
}
u64 System::GetPlayedSampleCount() const {
return session->GetPlayedSampleCount();
}
bool System::IsUac() const {
return is_uac;
}
} // namespace AudioCore::AudioIn

View File

@@ -0,0 +1,275 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <memory>
#include <span>
#include <string>
#include "audio_core/common/common.h"
#include "audio_core/device/audio_buffers.h"
#include "audio_core/device/device_session.h"
#include "core/hle/service/audio/errors.h"
namespace Core {
class System;
}
namespace Kernel {
class KEvent;
}
namespace AudioCore::AudioIn {
constexpr SessionTypes SessionType = SessionTypes::AudioIn;
struct AudioInParameter {
/* 0x0 */ s32_le sample_rate;
/* 0x4 */ u16_le channel_count;
/* 0x6 */ u16_le reserved;
};
static_assert(sizeof(AudioInParameter) == 0x8, "AudioInParameter is an invalid size");
struct AudioInParameterInternal {
/* 0x0 */ u32_le sample_rate;
/* 0x4 */ u32_le channel_count;
/* 0x8 */ u32_le sample_format;
/* 0xC */ u32_le state;
};
static_assert(sizeof(AudioInParameterInternal) == 0x10,
"AudioInParameterInternal is an invalid size");
struct AudioInBuffer {
/* 0x00 */ AudioInBuffer* next;
/* 0x08 */ VAddr samples;
/* 0x10 */ u64 capacity;
/* 0x18 */ u64 size;
/* 0x20 */ u64 offset;
};
static_assert(sizeof(AudioInBuffer) == 0x28, "AudioInBuffer is an invalid size");
enum class State {
Started,
Stopped,
};
/**
* Controls and drives audio input.
*/
class System {
public:
explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id);
~System();
/**
* Get the default audio input device name.
*
* @return The default audio input device name.
*/
std::string_view GetDefaultDeviceName();
/**
* Get the default USB audio input device name.
* This is preferred over non-USB as some games refuse to work with the BuiltInHeadset
* (e.g Let's Sing).
*
* @return The default USB audio input device name.
*/
std::string_view GetDefaultUacDeviceName();
/**
* Is the given initialize config valid?
*
* @param device_name - The name of the requested input device.
* @param in_params - Input parameters, see AudioInParameter.
* @return Result code.
*/
Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params);
/**
* Initialize this system.
*
* @param device_name - The name of the requested input device.
* @param in_params - Input parameters, see AudioInParameter.
* @param handle - Unused.
* @param applet_resource_user_id - Unused.
* @return Result code.
*/
Result Initialize(std::string& device_name, const AudioInParameter& in_params, u32 handle,
u64 applet_resource_user_id);
/**
* Start this system.
*
* @return Result code.
*/
Result Start();
/**
* Stop this system.
*
* @return Result code.
*/
Result Stop();
/**
* Finalize this system.
*/
void Finalize();
/**
* Start this system's device session.
*/
void StartSession();
/**
* Get this system's id.
*/
size_t GetSessionId() const;
/**
* Append a new buffer to the device.
*
* @param buffer - New buffer to append.
* @param tag - Unique tag of the buffer.
* @return True if the buffer was appended, otherwise false.
*/
bool AppendBuffer(const AudioInBuffer& buffer, u64 tag);
/**
* Register all appended buffers.
*/
void RegisterBuffers();
/**
* Release all registered buffers.
*/
void ReleaseBuffers();
/**
* Get all released buffers.
*
* @param tags - Container to be filled with the released buffers' tags.
* @return The number of buffers released.
*/
u32 GetReleasedBuffers(std::span<u64> tags);
/**
* Flush all appended and registered buffers.
*
* @return True if buffers were successfully flushed, otherwise false.
*/
bool FlushAudioInBuffers();
/**
* Get this system's current channel count.
*
* @return The channel count.
*/
u16 GetChannelCount() const;
/**
* Get this system's current sample rate.
*
* @return The sample rate.
*/
u32 GetSampleRate() const;
/**
* Get this system's current sample format.
*
* @return The sample format.
*/
SampleFormat GetSampleFormat() const;
/**
* Get this system's current state.
*
* @return The current state.
*/
State GetState();
/**
* Get this system's name.
*
* @return The system's name.
*/
std::string GetName() const;
/**
* Get this system's current volume.
*
* @return The system's current volume.
*/
f32 GetVolume() const;
/**
* Set this system's current volume.
*
* @param The new volume.
*/
void SetVolume(f32 volume);
/**
* Does the system contain this buffer?
*
* @param tag - Unique tag to search for.
* @return True if the buffer is in the system, otherwise false.
*/
bool ContainsAudioBuffer(u64 tag);
/**
* Get the maximum number of usable buffers (default 32).
*
* @return The number of buffers.
*/
u32 GetBufferCount();
/**
* Get the total number of samples played by this system.
*
* @return The number of samples.
*/
u64 GetPlayedSampleCount() const;
/**
* Is this system using a USB device?
*
* @return True if using a USB device, otherwise false.
*/
bool IsUac() const;
private:
/// Core system
Core::System& system;
/// (Unused)
u32 handle{};
/// (Unused)
u64 applet_resource_user_id{};
/// Buffer event, signalled when a buffer is ready
Kernel::KEvent* buffer_event;
/// Session id of this system
size_t session_id{};
/// Device session for this system
std::unique_ptr<DeviceSession> session;
/// Audio buffers in use by this system
AudioBuffers<BufferCount> buffers{BufferCount};
/// Sample rate of this system
u32 sample_rate{};
/// Sample format of this system
SampleFormat sample_format{SampleFormat::PcmInt16};
/// Channel count of this system
u16 channel_count{};
/// State of this system
std::atomic<State> state{State::Stopped};
/// Name of this system
std::string name{};
/// Volume of this system
f32 volume{1.0f};
/// Is this system's device USB?
bool is_uac{false};
};
} // namespace AudioCore::AudioIn

View File

@@ -1,511 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/behavior_info.h"
#include "audio_core/effect_context.h"
#include "audio_core/info_updater.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/voice_context.h"
#include "common/logging/log.h"
namespace AudioCore {
InfoUpdater::InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_,
BehaviorInfo& behavior_info_)
: in_params(in_params_), out_params(out_params_), behavior_info(behavior_info_) {
ASSERT(
AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
}
InfoUpdater::~InfoUpdater() = default;
bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
sizeof(BehaviorInfo::InParams), input_header.size.behavior);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
sizeof(BehaviorInfo::InParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
BehaviorInfo::InParams behavior_in{};
std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
input_offset += sizeof(BehaviorInfo::InParams);
// Make sure it's an audio revision we can actually support
if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
return false;
}
// Make sure that our behavior info revision matches the input
if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
LOG_ERROR(Audio,
"User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
in_behavior_info.GetUserRevision(), behavior_in.revision);
return false;
}
// Update behavior info flags
in_behavior_info.ClearError();
in_behavior_info.UpdateFlags(behavior_in.flags);
return true;
}
bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
const auto memory_pool_count = memory_pool_info.size();
const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
if (input_header.size.memory_pool != total_memory_pool_in) {
LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
total_memory_pool_in, input_header.size.memory_pool);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
input_offset += total_memory_pool_in;
// Update our memory pools
for (std::size_t i = 0; i < memory_pool_count; i++) {
if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
return false;
}
}
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
sizeof(BehaviorInfo::InParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
output_offset += total_memory_pool_out;
output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
return true;
}
bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
const auto voice_count = voice_context.GetVoiceCount();
const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
if (input_header.size.voice_channel_resource != voice_size) {
LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
voice_size, input_header.size.voice_channel_resource);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
input_offset += voice_size;
// Update our channel resources
for (std::size_t i = 0; i < voice_count; i++) {
// Grab our channel resource
auto& resource = voice_context.GetChannelResource(i);
resource.Update(resources_in[i]);
}
return true;
}
bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
[[maybe_unused]] std::vector<ServerMemoryPoolInfo>& memory_pool_info,
[[maybe_unused]] VAddr audio_codec_dsp_addr) {
const auto voice_count = voice_context.GetVoiceCount();
std::vector<VoiceInfo::InParams> voice_in(voice_count);
std::vector<VoiceInfo::OutParams> voice_out(voice_count);
const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
if (input_header.size.voice != voice_in_size) {
LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
voice_in_size, input_header.size.voice);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
input_offset += voice_in_size;
// Set all voices to not be in use
for (std::size_t i = 0; i < voice_count; i++) {
voice_context.GetInfo(i).GetInParams().in_use = false;
}
// Update our voices
for (std::size_t i = 0; i < voice_count; i++) {
auto& voice_in_params = voice_in[i];
const auto channel_count = static_cast<std::size_t>(voice_in_params.channel_count);
// Skip if it's not currently in use
if (!voice_in_params.is_in_use) {
continue;
}
// Voice states for each channel
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
ASSERT(static_cast<std::size_t>(voice_in_params.id) < voice_count);
// Grab our current voice info
auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(voice_in_params.id));
ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
// Get all our channel voice states
for (std::size_t channel = 0; channel < channel_count; channel++) {
voice_states[channel] =
&voice_context.GetState(voice_in_params.voice_channel_resource_ids[channel]);
}
if (voice_in_params.is_new) {
// Default our values for our voice
voice_info.Initialize();
// Zero out our voice states
for (std::size_t channel = 0; channel < channel_count; channel++) {
std::memset(voice_states[channel], 0, sizeof(VoiceState));
}
}
// Update our voice
voice_info.UpdateParameters(voice_in_params, behavior_info);
// TODO(ogniK): Handle mapping errors with behavior info based on in params response
// Update our wave buffers
voice_info.UpdateWaveBuffers(voice_in_params, voice_states, behavior_info);
voice_info.WriteOutStatus(voice_out[i], voice_in_params, voice_states);
}
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
output_offset += voice_out_size;
output_header.size.voice = static_cast<u32>(voice_out_size);
return true;
}
bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
const auto effect_count = effect_context.GetCount();
std::vector<EffectInfo::InParams> effect_in(effect_count);
std::vector<EffectInfo::OutParams> effect_out(effect_count);
const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
if (input_header.size.effect != total_effect_in) {
LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
total_effect_in, input_header.size.effect);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
input_offset += total_effect_in;
// Update effects
for (std::size_t i = 0; i < effect_count; i++) {
auto* info = effect_context.GetInfo(i);
if (effect_in[i].type != info->GetType()) {
info = effect_context.RetargetEffect(i, effect_in[i].type);
}
info->Update(effect_in[i]);
if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
info->GetUsage() == UsageState::Stopped) {
effect_out[i].status = UsageStatus::Removed;
} else {
effect_out[i].status = UsageStatus::Used;
}
}
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
output_offset += total_effect_out;
output_header.size.effect = static_cast<u32>(total_effect_out);
return true;
}
bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
std::size_t start_offset = input_offset;
std::size_t bytes_read{};
// Update splitter context
if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
LOG_ERROR(Audio, "Failed to update splitter context!");
return false;
}
const auto consumed = input_offset - start_offset;
if (input_header.size.splitter != consumed) {
LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
bytes_read, input_header.size.splitter);
return false;
}
return true;
}
Result InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
SplitterContext& splitter_context, EffectContext& effect_context) {
std::vector<MixInfo::InParams> mix_in_params;
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
// If we're not dirty, get ALL mix in parameters
const auto context_mix_count = mix_context.GetCount();
const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
if (input_header.size.mixer != total_mix_in) {
LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
total_mix_in, input_header.size.mixer);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
mix_in_params.resize(context_mix_count);
std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
input_offset += total_mix_in;
} else {
// Only update the "dirty" mixes
MixInfo::DirtyHeader dirty_header{};
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
sizeof(MixInfo::DirtyHeader))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
input_offset += sizeof(MixInfo::DirtyHeader);
const auto total_mix_in =
dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
if (input_header.size.mixer != total_mix_in) {
LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
total_mix_in, input_header.size.mixer);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (dirty_header.mixer_count != 0) {
mix_in_params.resize(dirty_header.mixer_count);
std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
mix_in_params.size() * sizeof(MixInfo::InParams));
input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
}
}
// Get our total input count
const auto mix_count = mix_in_params.size();
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
// Only verify our buffer count if we're not dirty
std::size_t total_buffer_count{};
for (std::size_t i = 0; i < mix_count; i++) {
const auto& in = mix_in_params[i];
total_buffer_count += in.buffer_count;
if (static_cast<std::size_t>(in.dest_mix_id) > mix_count &&
in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) {
LOG_ERROR(
Audio,
"Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
in.mix_id, in.dest_mix_id, mix_buffer_count);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
if (total_buffer_count > mix_buffer_count) {
LOG_ERROR(Audio,
"Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
mix_buffer_count, total_buffer_count);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
if (mix_buffer_count == 0) {
LOG_ERROR(Audio, "No mix buffers!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
bool should_sort = false;
for (std::size_t i = 0; i < mix_count; i++) {
const auto& mix_in = mix_in_params[i];
std::size_t target_mix{};
if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
target_mix = mix_in.mix_id;
} else {
// Non dirty supported games just use i instead of the actual mix_id
target_mix = i;
}
auto& mix_info = mix_context.GetInfo(target_mix);
auto& mix_info_params = mix_info.GetInParams();
if (mix_info_params.in_use != mix_in.in_use) {
mix_info_params.in_use = mix_in.in_use;
mix_info.ResetEffectProcessingOrder();
should_sort = true;
}
if (mix_in.in_use) {
should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
splitter_context, effect_context);
}
}
if (should_sort && behavior_info.IsSplitterSupported()) {
// Sort our splitter data
if (!mix_context.TsortInfo(splitter_context)) {
return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
}
}
// TODO(ogniK): Sort when splitter is suppoorted
return ResultSuccess;
}
bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
const auto sink_count = sink_context.GetCount();
std::vector<SinkInfo::InParams> sink_in_params(sink_count);
const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
if (input_header.size.sink != total_sink_in) {
LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
total_sink_in, input_header.size.effect);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
input_offset += total_sink_in;
// TODO(ogniK): Properly update sinks
if (!sink_in_params.empty()) {
sink_context.UpdateMainSink(sink_in_params[0]);
}
output_header.size.sink = static_cast<u32>(0x20 * sink_count);
output_offset += 0x20 * sink_count;
return true;
}
bool InfoUpdater::UpdatePerformanceBuffer() {
output_header.size.performance = 0x10;
output_offset += 0x10;
return true;
}
bool InfoUpdater::UpdateErrorInfo([[maybe_unused]] BehaviorInfo& in_behavior_info) {
const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
BehaviorInfo::OutParams behavior_info_out{};
behavior_info.CopyErrorInfo(behavior_info_out);
std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
output_offset += total_beahvior_info_out;
output_header.size.behavior = total_beahvior_info_out;
return true;
}
struct RendererInfo {
u64_le elasped_frame_count{};
INSERT_PADDING_WORDS(2);
};
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
const auto total_renderer_info_out = sizeof(RendererInfo);
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
RendererInfo out{};
out.elasped_frame_count = elapsed_frame_count;
std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
output_offset += total_renderer_info_out;
output_header.size.render_info = total_renderer_info_out;
return true;
}
bool InfoUpdater::CheckConsumedSize() const {
if (output_offset != out_params.size()) {
LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
output_offset, out_params.size(), out_params.size() - output_offset);
return false;
}
/*if (input_offset != in_params.size()) {
LOG_ERROR(Audio, "Input is not consumed!");
return false;
}*/
return true;
}
bool InfoUpdater::WriteOutputHeader() {
if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
sizeof(AudioCommon::UpdateDataHeader))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
const auto& sz = output_header.size;
output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
sz.performance + sz.splitter + sz.render_info;
std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
return true;
}
} // namespace AudioCore

View File

@@ -1,57 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include "audio_core/common.h"
#include "common/common_types.h"
namespace AudioCore {
class BehaviorInfo;
class ServerMemoryPoolInfo;
class VoiceContext;
class EffectContext;
class MixContext;
class SinkContext;
class SplitterContext;
class InfoUpdater {
public:
// TODO(ogniK): Pass process handle when we support it
InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_,
BehaviorInfo& behavior_info_);
~InfoUpdater();
bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
bool UpdateVoiceChannelResources(VoiceContext& voice_context);
bool UpdateVoices(VoiceContext& voice_context,
std::vector<ServerMemoryPoolInfo>& memory_pool_info,
VAddr audio_codec_dsp_addr);
bool UpdateEffects(EffectContext& effect_context, bool is_active);
bool UpdateSplitterInfo(SplitterContext& splitter_context);
Result UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
SplitterContext& splitter_context, EffectContext& effect_context);
bool UpdateSinks(SinkContext& sink_context);
bool UpdatePerformanceBuffer();
bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
bool UpdateRendererInfo(std::size_t elapsed_frame_count);
bool CheckConsumedSize() const;
bool WriteOutputHeader();
private:
const std::vector<u8>& in_params;
std::vector<u8>& out_params;
BehaviorInfo& behavior_info;
AudioCommon::UpdateDataHeader input_header{};
AudioCommon::UpdateDataHeader output_header{};
std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
};
} // namespace AudioCore

View File

@@ -1,60 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/memory_pool.h"
#include "common/logging/log.h"
namespace AudioCore {
ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
bool ServerMemoryPoolInfo::Update(const InParams& in_params, OutParams& out_params) {
// Our state does not need to be changed
if (in_params.state != State::RequestAttach && in_params.state != State::RequestDetach) {
return true;
}
// Address or size is null
if (in_params.address == 0 || in_params.size == 0) {
LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
in_params.address, in_params.size);
return false;
}
// Address or size is not aligned
if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
in_params.address, in_params.size);
return false;
}
if (in_params.state == State::RequestAttach) {
cpu_address = in_params.address;
size = in_params.size;
used = true;
out_params.state = State::Attached;
} else {
// Unexpected address
if (cpu_address != in_params.address) {
LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
cpu_address, in_params.address);
return false;
}
if (size != in_params.size) {
LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
in_params.size);
return false;
}
cpu_address = 0;
size = 0;
used = false;
out_params.state = State::Detached;
}
return true;
}
} // namespace AudioCore

View File

@@ -1,51 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
class ServerMemoryPoolInfo {
public:
ServerMemoryPoolInfo();
~ServerMemoryPoolInfo();
enum class State : u32_le {
Invalid = 0x0,
Aquired = 0x1,
RequestDetach = 0x2,
Detached = 0x3,
RequestAttach = 0x4,
Attached = 0x5,
Released = 0x6,
};
struct InParams {
u64_le address{};
u64_le size{};
State state{};
INSERT_PADDING_WORDS(3);
};
static_assert(sizeof(InParams) == 0x20, "InParams are an invalid size");
struct OutParams {
State state{};
INSERT_PADDING_WORDS(3);
};
static_assert(sizeof(OutParams) == 0x10, "OutParams are an invalid size");
bool Update(const InParams& in_params, OutParams& out_params);
private:
// There's another entry here which is the DSP address, however since we're not talking to the
// DSP we can just use the same address provided by the guest without needing to remap
u64_le cpu_address{};
u64_le size{};
bool used{};
};
} // namespace AudioCore

View File

@@ -1,297 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "audio_core/behavior_info.h"
#include "audio_core/common.h"
#include "audio_core/effect_context.h"
#include "audio_core/mix_context.h"
#include "audio_core/splitter_context.h"
namespace AudioCore {
MixContext::MixContext() = default;
MixContext::~MixContext() = default;
void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
std::size_t effect_count) {
info_count = mix_count;
infos.resize(info_count);
auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
sorted_info.reserve(infos.size());
for (auto& info : infos) {
sorted_info.push_back(&info);
}
for (auto& info : infos) {
info.SetEffectCount(effect_count);
}
// Only initialize our edge matrix and node states if splitters are supported
if (behavior_info.IsSplitterSupported()) {
node_states.Initialize(mix_count);
edge_matrix.Initialize(mix_count);
}
}
void MixContext::UpdateDistancesFromFinalMix() {
// Set all distances to be invalid
for (std::size_t i = 0; i < info_count; i++) {
GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
}
for (std::size_t i = 0; i < info_count; i++) {
auto& info = GetInfo(i);
auto& in_params = info.GetInParams();
// Populate our sorted info
sorted_info[i] = &info;
if (!in_params.in_use) {
continue;
}
auto mix_id = in_params.mix_id;
// Needs to be referenced out of scope
s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) {
if (mix_id == AudioCommon::FINAL_MIX) {
// If we're at the final mix, we're done
break;
} else if (mix_id == AudioCommon::NO_MIX) {
// If we have no more mix ids, we're done
distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
break;
} else {
const auto& dest_mix = GetInfo(mix_id);
const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
// If our current mix isn't pointing to a final mix, follow through
mix_id = dest_mix.GetInParams().dest_mix_id;
} else {
// Our current mix + 1 = final distance
distance_to_final_mix = dest_mix_distance + 1;
break;
}
}
}
// If we're out of range for our distance, mark it as no final mix
if (distance_to_final_mix >= static_cast<s32>(info_count)) {
distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
}
in_params.final_mix_distance = distance_to_final_mix;
}
}
void MixContext::CalcMixBufferOffset() {
s32 offset{};
for (std::size_t i = 0; i < info_count; i++) {
auto& info = GetSortedInfo(i);
auto& in_params = info.GetInParams();
if (in_params.in_use) {
// Only update if in use
in_params.buffer_offset = offset;
offset += in_params.buffer_count;
}
}
}
void MixContext::SortInfo() {
// Get the distance to the final mix
UpdateDistancesFromFinalMix();
// Sort based on the distance to the final mix
std::sort(sorted_info.begin(), sorted_info.end(),
[](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
return lhs->GetInParams().final_mix_distance >
rhs->GetInParams().final_mix_distance;
});
// Calculate the mix buffer offset
CalcMixBufferOffset();
}
bool MixContext::TsortInfo(SplitterContext& splitter_context) {
// If we're not using mixes, just calculate the mix buffer offset
if (!splitter_context.UsingSplitter()) {
CalcMixBufferOffset();
return true;
}
// Sort our node states
if (!node_states.Tsort(edge_matrix)) {
return false;
}
// Get our sorted list
const auto sorted_list = node_states.GetIndexList();
std::size_t info_id{};
for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
// Set our sorted info
sorted_info[info_id++] = &GetInfo(*itr);
}
// Calculate the mix buffer offset
CalcMixBufferOffset();
return true;
}
std::size_t MixContext::GetCount() const {
return info_count;
}
ServerMixInfo& MixContext::GetInfo(std::size_t i) {
ASSERT(i < info_count);
return infos.at(i);
}
const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
ASSERT(i < info_count);
return infos.at(i);
}
ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
ASSERT(i < info_count);
return *sorted_info.at(i);
}
const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
ASSERT(i < info_count);
return *sorted_info.at(i);
}
ServerMixInfo& MixContext::GetFinalMixInfo() {
return infos.at(AudioCommon::FINAL_MIX);
}
const ServerMixInfo& MixContext::GetFinalMixInfo() const {
return infos.at(AudioCommon::FINAL_MIX);
}
EdgeMatrix& MixContext::GetEdgeMatrix() {
return edge_matrix;
}
const EdgeMatrix& MixContext::GetEdgeMatrix() const {
return edge_matrix;
}
ServerMixInfo::ServerMixInfo() {
Cleanup();
}
ServerMixInfo::~ServerMixInfo() = default;
const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
return in_params;
}
ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
return in_params;
}
bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
BehaviorInfo& behavior_info, SplitterContext& splitter_context,
EffectContext& effect_context) {
in_params.volume = mix_in.volume;
in_params.sample_rate = mix_in.sample_rate;
in_params.buffer_count = mix_in.buffer_count;
in_params.in_use = mix_in.in_use;
in_params.mix_id = mix_in.mix_id;
in_params.node_id = mix_in.node_id;
for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
in_params.mix_volume[i].begin());
}
bool require_sort = false;
if (behavior_info.IsSplitterSupported()) {
require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
} else {
in_params.dest_mix_id = mix_in.dest_mix_id;
in_params.splitter_id = AudioCommon::NO_SPLITTER;
}
ResetEffectProcessingOrder();
const auto effect_count = effect_context.GetCount();
for (std::size_t i = 0; i < effect_count; i++) {
auto* effect_info = effect_context.GetInfo(i);
if (effect_info->GetMixID() == in_params.mix_id) {
effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
}
}
// TODO(ogniK): Update effect processing order
return require_sort;
}
bool ServerMixInfo::HasAnyConnection() const {
return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
in_params.mix_id != AudioCommon::NO_MIX;
}
void ServerMixInfo::Cleanup() {
in_params.volume = 0.0f;
in_params.sample_rate = 0;
in_params.buffer_count = 0;
in_params.in_use = false;
in_params.mix_id = AudioCommon::NO_MIX;
in_params.node_id = 0;
in_params.buffer_offset = 0;
in_params.dest_mix_id = AudioCommon::NO_MIX;
in_params.splitter_id = AudioCommon::NO_SPLITTER;
std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
}
void ServerMixInfo::SetEffectCount(std::size_t count) {
effect_processing_order.resize(count);
ResetEffectProcessingOrder();
}
void ServerMixInfo::ResetEffectProcessingOrder() {
for (auto& order : effect_processing_order) {
order = AudioCommon::NO_EFFECT_ORDER;
}
}
s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
return effect_processing_order.at(i);
}
bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
SplitterContext& splitter_context) {
// Mixes are identical
if (in_params.dest_mix_id == mix_in.dest_mix_id &&
in_params.splitter_id == mix_in.splitter_id &&
((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
!splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
return false;
}
// Remove current edges for mix id
edge_matrix.RemoveEdges(in_params.mix_id);
if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
// If we have a valid destination mix id, set our edge matrix
edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
} else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
// Recurse our splitter linked and set our edges
auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
const auto length = splitter_info.GetLength();
for (s32 i = 0; i < length; i++) {
const auto* splitter_destination =
splitter_context.GetDestinationData(mix_in.splitter_id, i);
if (splitter_destination == nullptr) {
continue;
}
if (splitter_destination->ValidMixId()) {
edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
}
}
}
in_params.dest_mix_id = mix_in.dest_mix_id;
in_params.splitter_id = mix_in.splitter_id;
return true;
}
} // namespace AudioCore

View File

@@ -1,113 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <vector>
#include "audio_core/common.h"
#include "audio_core/splitter_context.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace AudioCore {
class BehaviorInfo;
class EffectContext;
class MixInfo {
public:
struct DirtyHeader {
u32_le magic{};
u32_le mixer_count{};
INSERT_PADDING_BYTES(0x18);
};
static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
struct InParams {
float_le volume{};
s32_le sample_rate{};
s32_le buffer_count{};
bool in_use{};
INSERT_PADDING_BYTES(3);
s32_le mix_id{};
s32_le effect_count{};
u32_le node_id{};
INSERT_PADDING_WORDS(2);
std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
mix_volume{};
s32_le dest_mix_id{};
s32_le splitter_id{};
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
};
class ServerMixInfo {
public:
struct InParams {
float volume{};
s32 sample_rate{};
s32 buffer_count{};
bool in_use{};
s32 mix_id{};
u32 node_id{};
std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
mix_volume{};
s32 dest_mix_id{};
s32 splitter_id{};
s32 buffer_offset{};
s32 final_mix_distance{};
};
ServerMixInfo();
~ServerMixInfo();
[[nodiscard]] const ServerMixInfo::InParams& GetInParams() const;
[[nodiscard]] ServerMixInfo::InParams& GetInParams();
bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
BehaviorInfo& behavior_info, SplitterContext& splitter_context,
EffectContext& effect_context);
[[nodiscard]] bool HasAnyConnection() const;
void Cleanup();
void SetEffectCount(std::size_t count);
void ResetEffectProcessingOrder();
[[nodiscard]] s32 GetEffectOrder(std::size_t i) const;
private:
std::vector<s32> effect_processing_order;
InParams in_params{};
bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
SplitterContext& splitter_context);
};
class MixContext {
public:
MixContext();
~MixContext();
void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
std::size_t effect_count);
void SortInfo();
bool TsortInfo(SplitterContext& splitter_context);
[[nodiscard]] std::size_t GetCount() const;
[[nodiscard]] ServerMixInfo& GetInfo(std::size_t i);
[[nodiscard]] const ServerMixInfo& GetInfo(std::size_t i) const;
[[nodiscard]] ServerMixInfo& GetSortedInfo(std::size_t i);
[[nodiscard]] const ServerMixInfo& GetSortedInfo(std::size_t i) const;
[[nodiscard]] ServerMixInfo& GetFinalMixInfo();
[[nodiscard]] const ServerMixInfo& GetFinalMixInfo() const;
[[nodiscard]] EdgeMatrix& GetEdgeMatrix();
[[nodiscard]] const EdgeMatrix& GetEdgeMatrix() const;
private:
void CalcMixBufferOffset();
void UpdateDistancesFromFinalMix();
NodeStates node_states{};
EdgeMatrix edge_matrix{};
std::size_t info_count{};
std::vector<ServerMixInfo> infos{};
std::vector<ServerMixInfo*> sorted_info{};
};
} // namespace AudioCore

View File

@@ -1,32 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "audio_core/sink.h"
namespace AudioCore {
class NullSink final : public Sink {
public:
explicit NullSink(std::string_view) {}
~NullSink() override = default;
SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/,
const std::string& /*name*/) override {
return null_sink_stream;
}
private:
struct NullSinkStreamImpl final : SinkStream {
void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {}
std::size_t SamplesInQueue(u32 /*num_channels*/) const override {
return 0;
}
void Flush() override {}
} null_sink_stream;
};
} // namespace AudioCore

View File

@@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_out_manager.h"
#include "audio_core/out/audio_out.h"
#include "core/hle/kernel/k_event.h"
namespace AudioCore::AudioOut {
Out::Out(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_)
: manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event,
session_id_} {}
void Out::Free() {
std::scoped_lock l{parent_mutex};
manager.ReleaseSessionId(system.GetSessionId());
}
System& Out::GetSystem() {
return system;
}
AudioOut::State Out::GetState() {
std::scoped_lock l{parent_mutex};
return system.GetState();
}
Result Out::StartSystem() {
std::scoped_lock l{parent_mutex};
return system.Start();
}
void Out::StartSession() {
std::scoped_lock l{parent_mutex};
system.StartSession();
}
Result Out::StopSystem() {
std::scoped_lock l{parent_mutex};
return system.Stop();
}
Result Out::AppendBuffer(const AudioOutBuffer& buffer, const u64 tag) {
std::scoped_lock l{parent_mutex};
if (system.AppendBuffer(buffer, tag)) {
return ResultSuccess;
}
return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED;
}
void Out::ReleaseAndRegisterBuffers() {
std::scoped_lock l{parent_mutex};
if (system.GetState() == State::Started) {
system.ReleaseBuffers();
system.RegisterBuffers();
}
}
bool Out::FlushAudioOutBuffers() {
std::scoped_lock l{parent_mutex};
return system.FlushAudioOutBuffers();
}
u32 Out::GetReleasedBuffers(std::span<u64> tags) {
std::scoped_lock l{parent_mutex};
return system.GetReleasedBuffers(tags);
}
Kernel::KReadableEvent& Out::GetBufferEvent() {
std::scoped_lock l{parent_mutex};
return event->GetReadableEvent();
}
f32 Out::GetVolume() {
std::scoped_lock l{parent_mutex};
return system.GetVolume();
}
void Out::SetVolume(const f32 volume) {
std::scoped_lock l{parent_mutex};
system.SetVolume(volume);
}
bool Out::ContainsAudioBuffer(const u64 tag) {
std::scoped_lock l{parent_mutex};
return system.ContainsAudioBuffer(tag);
}
u32 Out::GetBufferCount() {
std::scoped_lock l{parent_mutex};
return system.GetBufferCount();
}
u64 Out::GetPlayedSampleCount() {
std::scoped_lock l{parent_mutex};
return system.GetPlayedSampleCount();
}
} // namespace AudioCore::AudioOut

View File

@@ -0,0 +1,147 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include "audio_core/out/audio_out_system.h"
namespace Core {
class System;
}
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace AudioCore::AudioOut {
class Manager;
/**
* Interface between the service and audio out system. Mainly responsible for forwarding service
* calls to the system.
*/
class Out {
public:
explicit Out(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id);
/**
* Free this audio out from the audio out manager.
*/
void Free();
/**
* Get this audio out's system.
*/
System& GetSystem();
/**
* Get the current state.
*
* @return Started or Stopped.
*/
AudioOut::State GetState();
/**
* Start the system
*
* @return Result code
*/
Result StartSystem();
/**
* Start the system's device session.
*/
void StartSession();
/**
* Stop the system.
*
* @return Result code
*/
Result StopSystem();
/**
* Append a new buffer to the system, the buffer event will be signalled when it is filled.
*
* @param buffer - The new buffer to append.
* @param tag - Unique tag for this buffer.
* @return Result code.
*/
Result AppendBuffer(const AudioOutBuffer& buffer, u64 tag);
/**
* Release all completed buffers, and register any appended.
*/
void ReleaseAndRegisterBuffers();
/**
* Flush all buffers.
*/
bool FlushAudioOutBuffers();
/**
* Get all of the currently released buffers.
*
* @param tags - Output container for the buffer tags which were released.
* @return The number of buffers released.
*/
u32 GetReleasedBuffers(std::span<u64> tags);
/**
* Get the buffer event for this audio out, this event will be signalled when a buffer is
* filled.
* @return The buffer event.
*/
Kernel::KReadableEvent& GetBufferEvent();
/**
* Get the current system volume.
*
* @return The current volume.
*/
f32 GetVolume();
/**
* Set the system volume.
*
* @param volume - The volume to set.
*/
void SetVolume(f32 volume);
/**
* Check if a buffer is in the system.
*
* @param tag - The tag to search for.
* @return True if the buffer is in the system, otherwise false.
*/
bool ContainsAudioBuffer(u64 tag);
/**
* Get the maximum number of buffers.
*
* @return The maximum number of buffers.
*/
u32 GetBufferCount();
/**
* Get the total played sample count for this audio out.
*
* @return The played sample count.
*/
u64 GetPlayedSampleCount();
private:
/// The AudioOut::Manager this audio out is registered with
Manager& manager;
/// Manager's mutex
std::recursive_mutex& parent_mutex;
/// Buffer event, signalled when buffers are ready to be released
Kernel::KEvent* event;
/// Main audio out system
System system;
};
} // namespace AudioCore::AudioOut

View File

@@ -0,0 +1,207 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "audio_core/audio_event.h"
#include "audio_core/audio_manager.h"
#include "audio_core/out/audio_out_system.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_event.h"
namespace AudioCore::AudioOut {
System::System(Core::System& system_, Kernel::KEvent* event_, size_t session_id_)
: system{system_}, buffer_event{event_},
session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {}
System::~System() {
Finalize();
}
void System::Finalize() {
Stop();
session->Finalize();
buffer_event->GetWritableEvent().Signal();
}
std::string_view System::GetDefaultOutputDeviceName() {
return "DeviceOut";
}
Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) {
if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) {
return Service::Audio::ERR_INVALID_DEVICE_NAME;
}
if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) {
return Service::Audio::ERR_INVALID_SAMPLE_RATE;
}
if (in_params.channel_count == 0 || in_params.channel_count == 2 ||
in_params.channel_count == 6) {
return ResultSuccess;
}
return Service::Audio::ERR_INVALID_CHANNEL_COUNT;
}
Result System::Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle_,
u64& applet_resource_user_id_) {
auto result = IsConfigValid(device_name, in_params);
if (result.IsError()) {
return result;
}
handle = handle_;
applet_resource_user_id = applet_resource_user_id_;
if (device_name.empty() || device_name[0] == '\0') {
name = std::string(GetDefaultOutputDeviceName());
} else {
name = std::move(device_name);
}
sample_rate = TargetSampleRate;
sample_format = SampleFormat::PcmInt16;
channel_count = in_params.channel_count <= 2 ? 2 : 6;
volume = 1.0f;
return ResultSuccess;
}
void System::StartSession() {
session->Start();
}
size_t System::GetSessionId() const {
return session_id;
}
Result System::Start() {
if (state != State::Stopped) {
return Service::Audio::ERR_OPERATION_FAILED;
}
session->Initialize(name, sample_format, channel_count, session_id, handle,
applet_resource_user_id, Sink::StreamType::Out);
session->SetVolume(volume);
session->Start();
state = State::Started;
std::vector<AudioBuffer> buffers_to_flush{};
buffers.RegisterBuffers(buffers_to_flush);
session->AppendBuffers(buffers_to_flush);
return ResultSuccess;
}
Result System::Stop() {
if (state == State::Started) {
session->Stop();
session->SetVolume(0.0f);
state = State::Stopped;
}
return ResultSuccess;
}
bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
if (buffers.GetTotalBufferCount() == BufferCount) {
return false;
}
AudioBuffer new_buffer{
.played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size};
buffers.AppendBuffer(new_buffer);
RegisterBuffers();
return true;
}
void System::RegisterBuffers() {
if (state == State::Started) {
std::vector<AudioBuffer> registered_buffers{};
buffers.RegisterBuffers(registered_buffers);
session->AppendBuffers(registered_buffers);
}
}
void System::ReleaseBuffers() {
bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)};
if (signal) {
// Signal if any buffer was released, or if none are registered, we need more.
buffer_event->GetWritableEvent().Signal();
}
}
u32 System::GetReleasedBuffers(std::span<u64> tags) {
return buffers.GetReleasedBuffers(tags);
}
bool System::FlushAudioOutBuffers() {
if (state != State::Started) {
return false;
}
u32 buffers_released{};
buffers.FlushBuffers(buffers_released);
if (buffers_released > 0) {
buffer_event->GetWritableEvent().Signal();
}
return true;
}
u16 System::GetChannelCount() const {
return channel_count;
}
u32 System::GetSampleRate() const {
return sample_rate;
}
SampleFormat System::GetSampleFormat() const {
return sample_format;
}
State System::GetState() {
switch (state) {
case State::Started:
case State::Stopped:
return state;
default:
LOG_ERROR(Service_Audio, "AudioOut invalid state!");
state = State::Stopped;
break;
}
return state;
}
std::string System::GetName() const {
return name;
}
f32 System::GetVolume() const {
return volume;
}
void System::SetVolume(const f32 volume_) {
volume = volume_;
session->SetVolume(volume_);
}
bool System::ContainsAudioBuffer(const u64 tag) {
return buffers.ContainsBuffer(tag);
}
u32 System::GetBufferCount() {
return buffers.GetAppendedRegisteredCount();
}
u64 System::GetPlayedSampleCount() const {
return session->GetPlayedSampleCount();
}
} // namespace AudioCore::AudioOut

View File

@@ -0,0 +1,257 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <memory>
#include <span>
#include <string>
#include "audio_core/common/common.h"
#include "audio_core/device/audio_buffers.h"
#include "audio_core/device/device_session.h"
#include "core/hle/service/audio/errors.h"
namespace Core {
class System;
}
namespace Kernel {
class KEvent;
}
namespace AudioCore::AudioOut {
constexpr SessionTypes SessionType = SessionTypes::AudioOut;
struct AudioOutParameter {
/* 0x0 */ s32_le sample_rate;
/* 0x4 */ u16_le channel_count;
/* 0x6 */ u16_le reserved;
};
static_assert(sizeof(AudioOutParameter) == 0x8, "AudioOutParameter is an invalid size");
struct AudioOutParameterInternal {
/* 0x0 */ u32_le sample_rate;
/* 0x4 */ u32_le channel_count;
/* 0x8 */ u32_le sample_format;
/* 0xC */ u32_le state;
};
static_assert(sizeof(AudioOutParameterInternal) == 0x10,
"AudioOutParameterInternal is an invalid size");
struct AudioOutBuffer {
/* 0x00 */ AudioOutBuffer* next;
/* 0x08 */ VAddr samples;
/* 0x10 */ u64 capacity;
/* 0x18 */ u64 size;
/* 0x20 */ u64 offset;
};
static_assert(sizeof(AudioOutBuffer) == 0x28, "AudioOutBuffer is an invalid size");
enum class State {
Started,
Stopped,
};
/**
* Controls and drives audio output.
*/
class System {
public:
explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id);
~System();
/**
* Get the default audio output device name.
*
* @return The default audio output device name.
*/
std::string_view GetDefaultOutputDeviceName();
/**
* Is the given initialize config valid?
*
* @param device_name - The name of the requested output device.
* @param in_params - Input parameters, see AudioOutParameter.
* @return Result code.
*/
Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params);
/**
* Initialize this system.
*
* @param device_name - The name of the requested output device.
* @param in_params - Input parameters, see AudioOutParameter.
* @param handle - Unused.
* @param applet_resource_user_id - Unused.
* @return Result code.
*/
Result Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle,
u64& applet_resource_user_id);
/**
* Start this system.
*
* @return Result code.
*/
Result Start();
/**
* Stop this system.
*
* @return Result code.
*/
Result Stop();
/**
* Finalize this system.
*/
void Finalize();
/**
* Start this system's device session.
*/
void StartSession();
/**
* Get this system's id.
*/
size_t GetSessionId() const;
/**
* Append a new buffer to the device.
*
* @param buffer - New buffer to append.
* @param tag - Unique tag of the buffer.
* @return True if the buffer was appended, otherwise false.
*/
bool AppendBuffer(const AudioOutBuffer& buffer, u64 tag);
/**
* Register all appended buffers.
*/
void RegisterBuffers();
/**
* Release all registered buffers.
*/
void ReleaseBuffers();
/**
* Get all released buffers.
*
* @param tags - Container to be filled with the released buffers' tags.
* @return The number of buffers released.
*/
u32 GetReleasedBuffers(std::span<u64> tags);
/**
* Flush all appended and registered buffers.
*
* @return True if buffers were successfully flushed, otherwise false.
*/
bool FlushAudioOutBuffers();
/**
* Get this system's current channel count.
*
* @return The channel count.
*/
u16 GetChannelCount() const;
/**
* Get this system's current sample rate.
*
* @return The sample rate.
*/
u32 GetSampleRate() const;
/**
* Get this system's current sample format.
*
* @return The sample format.
*/
SampleFormat GetSampleFormat() const;
/**
* Get this system's current state.
*
* @return The current state.
*/
State GetState();
/**
* Get this system's name.
*
* @return The system's name.
*/
std::string GetName() const;
/**
* Get this system's current volume.
*
* @return The system's current volume.
*/
f32 GetVolume() const;
/**
* Set this system's current volume.
*
* @param The new volume.
*/
void SetVolume(f32 volume);
/**
* Does the system contain this buffer?
*
* @param tag - Unique tag to search for.
* @return True if the buffer is in the system, otherwise false.
*/
bool ContainsAudioBuffer(u64 tag);
/**
* Get the maximum number of usable buffers (default 32).
*
* @return The number of buffers.
*/
u32 GetBufferCount();
/**
* Get the total number of samples played by this system.
*
* @return The number of samples.
*/
u64 GetPlayedSampleCount() const;
private:
/// Core system
Core::System& system;
/// (Unused)
u32 handle{};
/// (Unused)
u64 applet_resource_user_id{};
/// Buffer event, signalled when a buffer is ready
Kernel::KEvent* buffer_event;
/// Session id of this system
size_t session_id{};
/// Device session for this system
std::unique_ptr<DeviceSession> session;
/// Audio buffers in use by this system
AudioBuffers<BufferCount> buffers{BufferCount};
/// Sample rate of this system
u32 sample_rate{};
/// Sample format of this system
SampleFormat sample_format{SampleFormat::PcmInt16};
/// Channel count of this system
u16 channel_count{};
/// State of this system
std::atomic<State> state{State::Stopped};
/// Name of this system
std::string name{};
/// Volume of this system
f32 volume{1.0f};
};
} // namespace AudioCore::AudioOut

View File

@@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/adsp.h"
#include "audio_core/renderer/adsp/command_buffer.h"
#include "audio_core/sink/sink.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/memory.h"
namespace AudioCore::AudioRenderer::ADSP {
ADSP::ADSP(Core::System& system_, Sink::Sink& sink_)
: system{system_}, memory{system.Memory()}, sink{sink_} {}
ADSP::~ADSP() {
ClearCommandBuffers();
}
State ADSP::GetState() const {
if (running) {
return State::Started;
}
return State::Stopped;
}
AudioRenderer_Mailbox* ADSP::GetRenderMailbox() {
return &render_mailbox;
}
void ADSP::ClearRemainCount(const u32 session_id) {
render_mailbox.ClearRemainCount(session_id);
}
u64 ADSP::GetSignalledTick() const {
return render_mailbox.GetSignalledTick();
}
u64 ADSP::GetTimeTaken() const {
return render_mailbox.GetRenderTimeTaken();
}
u64 ADSP::GetRenderTimeTaken(const u32 session_id) {
return render_mailbox.GetCommandBuffer(session_id).render_time_taken;
}
u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
return render_mailbox.GetRemainCommandCount(session_id);
}
void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) {
render_mailbox.SetCommandBuffer(session_id, command_buffer);
}
u64 ADSP::GetRenderingStartTick(const u32 session_id) {
return render_mailbox.GetSignalledTick() +
render_mailbox.GetCommandBuffer(session_id).render_time_taken;
}
bool ADSP::Start() {
if (running) {
return running;
}
running = true;
systems_active++;
audio_renderer = std::make_unique<AudioRenderer>(system);
audio_renderer->Start(&render_mailbox);
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK);
if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
LOG_ERROR(
Service_Audio,
"Host Audio Renderer -- Failed to receive initialize message response from ADSP!");
}
return running;
}
void ADSP::Stop() {
systems_active--;
if (running && systems_active == 0) {
{
std::scoped_lock l{mailbox_lock};
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown);
if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) {
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
"message response from ADSP!");
}
}
audio_renderer->Stop();
running = false;
}
}
void ADSP::Signal() {
const auto signalled_tick{system.CoreTiming().GetClockTicks()};
render_mailbox.SetSignalledTick(signalled_tick);
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render);
}
void ADSP::Wait() {
std::scoped_lock l{mailbox_lock};
auto response{render_mailbox.HostWaitMessage()};
if (response != RenderMessage::AudioRenderer_RenderResponse) {
LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}",
static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse),
static_cast<u32>(response));
}
ClearCommandBuffers();
}
void ADSP::ClearCommandBuffers() {
render_mailbox.ClearCommandBuffers();
}
} // namespace AudioCore::AudioRenderer::ADSP

View File

@@ -0,0 +1,173 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <mutex>
#include "audio_core/renderer/adsp/audio_renderer.h"
#include "common/common_types.h"
namespace Core {
namespace Memory {
class Memory;
}
class System;
} // namespace Core
namespace AudioCore {
namespace Sink {
class Sink;
}
namespace AudioRenderer::ADSP {
struct CommandBuffer;
enum class State {
Started,
Stopped,
};
/**
* Represents the ADSP embedded within the audio sysmodule.
* This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
*
* The kernel will run apps you program for it, Nintendo have the following:
*
* Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
* audio samples end up, and we skip it entirely, since we have very different backends and
* mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
*
* AudioRenderer - Receives command lists generated by the audio render
* system, processes them, and sends the samples to Gmix.
*
* OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix.
* Not much research done here, TODO if needed.
*
* We only implement the AudioRenderer for now.
*
* Communication for the apps is done through mailboxes, and some shared memory.
*/
class ADSP {
public:
explicit ADSP(Core::System& system, Sink::Sink& sink);
~ADSP();
/**
* Start the ADSP.
*
* @return True if started or already running, otherwise false.
*/
bool Start();
/**
* Stop the ADSP.
*
* @return True if started or already running, otherwise false.
*/
void Stop();
/**
* Get the ADSP's state.
*
* @return Started or Stopped.
*/
State GetState() const;
/**
* Get the AudioRenderer mailbox to communicate with it.
*
* @return The AudioRenderer mailbox.
*/
AudioRenderer_Mailbox* GetRenderMailbox();
/**
* Get the tick the ADSP was signalled.
*
* @return The tick the ADSP was signalled.
*/
u64 GetSignalledTick() const;
/**
* Get the total time it took for the ADSP to run the last command lists (both command lists).
*
* @return The tick the ADSP was signalled.
*/
u64 GetTimeTaken() const;
/**
* Get the last time a given command list took to run.
*
* @param session_id - The session id to check (0 or 1).
* @return The time it took.
*/
u64 GetRenderTimeTaken(u32 session_id);
/**
* Clear the remaining command count for a given session.
*
* @param session_id - The session id to check (0 or 1).
*/
void ClearRemainCount(u32 session_id);
/**
* Get the remaining number of commands left to process for a command list.
*
* @param session_id - The session id to check (0 or 1).
* @return The number of commands remaining.
*/
u32 GetRemainCommandCount(u32 session_id) const;
/**
* Get the last tick a command list started processing.
*
* @param session_id - The session id to check (0 or 1).
* @return The last tick the given command list started.
*/
u64 GetRenderingStartTick(u32 session_id);
/**
* Set a command buffer to be processed.
*
* @param session_id - The session id to check (0 or 1).
* @param command_buffer - The command buffer to process.
*/
void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer);
/**
* Clear the command buffers (does not clear the time taken or the remaining command count)
*/
void ClearCommandBuffers();
/**
* Signal the AudioRenderer to begin processing.
*/
void Signal();
/**
* Wait for the AudioRenderer to finish processing.
*/
void Wait();
private:
/// Core system
Core::System& system;
/// Core memory
Core::Memory::Memory& memory;
/// Number of systems active, used to prevent accidental shutdowns
u8 systems_active{0};
/// ADSP running state
std::atomic<bool> running{false};
/// Output sink used by the ADSP
Sink::Sink& sink;
/// AudioRenderer app
std::unique_ptr<AudioRenderer> audio_renderer{};
/// Communication for the AudioRenderer
AudioRenderer_Mailbox render_mailbox{};
/// Mailbox lock ffor the render mailbox
std::mutex mailbox_lock;
};
} // namespace AudioRenderer::ADSP
} // namespace AudioCore

View File

@@ -0,0 +1,226 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <chrono>
#include "audio_core/audio_core.h"
#include "audio_core/common/common.h"
#include "audio_core/renderer/adsp/audio_renderer.h"
#include "audio_core/sink/sink.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
namespace AudioCore::AudioRenderer::ADSP {
void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) {
adsp_messages.enqueue(message_);
adsp_event.Set();
}
RenderMessage AudioRenderer_Mailbox::HostWaitMessage() {
host_event.Wait();
RenderMessage msg{RenderMessage::Invalid};
if (!host_messages.try_dequeue(msg)) {
LOG_ERROR(Service_Audio, "Failed to dequeue host message!");
}
return msg;
}
void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) {
host_messages.enqueue(message_);
host_event.Set();
}
RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
adsp_event.Wait();
RenderMessage msg{RenderMessage::Invalid};
if (!adsp_messages.try_dequeue(msg)) {
LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!");
}
return msg;
}
CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) {
return command_buffers[session_id];
}
void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) {
command_buffers[session_id] = buffer;
}
u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const {
return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken;
}
u64 AudioRenderer_Mailbox::GetSignalledTick() const {
return signalled_tick;
}
void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) {
signalled_tick = tick;
}
void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) {
command_buffers[session_id].remaining_command_count = 0;
}
u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const {
return command_buffers[session_id].remaining_command_count;
}
void AudioRenderer_Mailbox::ClearCommandBuffers() {
command_buffers[0].buffer = 0;
command_buffers[0].size = 0;
command_buffers[0].reset_buffers = false;
command_buffers[1].buffer = 0;
command_buffers[1].size = 0;
command_buffers[1].reset_buffers = false;
}
AudioRenderer::AudioRenderer(Core::System& system_)
: system{system_}, sink{system.AudioCore().GetOutputSink()} {
CreateSinkStreams();
}
AudioRenderer::~AudioRenderer() {
Stop();
for (auto& stream : streams) {
if (stream) {
sink.CloseStream(stream);
}
stream = nullptr;
}
}
void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
if (running) {
return;
}
mailbox = mailbox_;
thread = std::thread(&AudioRenderer::ThreadFunc, this);
for (auto& stream : streams) {
stream->Start();
}
running = true;
}
void AudioRenderer::Stop() {
if (!running) {
return;
}
for (auto& stream : streams) {
stream->Stop();
}
thread.join();
running = false;
}
void AudioRenderer::CreateSinkStreams() {
u32 channels{sink.GetDeviceChannels()};
for (u32 i = 0; i < MaxRendererSessions; i++) {
std::string name{fmt::format("ADSP_RenderStream-{}", i)};
streams[i] =
sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
}
}
void AudioRenderer::ThreadFunc() {
constexpr char name[]{"yuzu:AudioRenderer"};
MicroProfileOnThreadCreate(name);
Common::SetCurrentThreadName(name);
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
LOG_ERROR(Service_Audio,
"ADSP Audio Renderer -- Failed to receive initialize message from host!");
return;
}
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
constexpr u64 max_process_time{2'304'000ULL};
while (true) {
auto message{mailbox->ADSPWaitMessage()};
switch (message) {
case RenderMessage::AudioRenderer_Shutdown:
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown);
return;
case RenderMessage::AudioRenderer_Render: {
std::array<bool, MaxRendererSessions> buffers_reset{};
std::array<u64, MaxRendererSessions> render_times_taken{};
const auto start_time{system.CoreTiming().GetClockTicks()};
for (u32 index = 0; index < 2; index++) {
auto& command_buffer{mailbox->GetCommandBuffer(index)};
auto& command_list_processor{command_list_processors[index]};
// Check this buffer is valid, as it may not be used.
if (command_buffer.buffer != 0) {
// If there are no remaining commands (from the previous list),
// this is a new command list, initalize it.
if (command_buffer.remaining_command_count == 0) {
command_list_processor.Initialize(system, command_buffer.buffer,
command_buffer.size, streams[index]);
}
if (command_buffer.reset_buffers && !buffers_reset[index]) {
streams[index]->ClearQueue();
buffers_reset[index] = true;
}
u64 max_time{max_process_time};
if (index == 1 && command_buffer.applet_resource_user_id ==
mailbox->GetCommandBuffer(0).applet_resource_user_id) {
max_time = max_process_time -
Core::Timing::CyclesToNs(render_times_taken[0]).count();
if (render_times_taken[0] > max_process_time) {
max_time = 0;
}
}
max_time = std::min(command_buffer.time_limit, max_time);
command_list_processor.SetProcessTimeMax(max_time);
// Process the command list
{
MICROPROFILE_SCOPE(Audio_Renderer);
render_times_taken[index] =
command_list_processor.Process(index) - start_time;
}
if (index == 0) {
auto stream{command_list_processor.GetOutputSinkStream()};
system.AudioCore().SetStreamQueue(stream->GetQueueSize());
}
const auto end_time{system.CoreTiming().GetClockTicks()};
command_buffer.remaining_command_count =
command_list_processor.GetRemainingCommandCount();
command_buffer.render_time_taken = end_time - start_time;
}
}
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
} break;
default:
LOG_WARNING(Service_Audio,
"ADSP AudioRenderer received an invalid message, msg={:02X}!",
static_cast<u32>(message));
break;
}
}
}
} // namespace AudioCore::AudioRenderer::ADSP

View File

@@ -0,0 +1,203 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <thread>
#include "audio_core/renderer/adsp/command_buffer.h"
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "common/common_types.h"
#include "common/reader_writer_queue.h"
#include "common/thread.h"
namespace Core {
namespace Timing {
struct EventType;
}
class System;
} // namespace Core
namespace AudioCore {
namespace Sink {
class Sink;
}
namespace AudioRenderer::ADSP {
enum class RenderMessage {
/* 0x00 */ Invalid,
/* 0x01 */ AudioRenderer_MapUnmap_Map,
/* 0x02 */ AudioRenderer_MapUnmap_MapResponse,
/* 0x03 */ AudioRenderer_MapUnmap_Unmap,
/* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse,
/* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache,
/* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse,
/* 0x07 */ AudioRenderer_MapUnmap_Shutdown,
/* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse,
/* 0x16 */ AudioRenderer_InitializeOK = 0x16,
/* 0x20 */ AudioRenderer_RenderResponse = 0x20,
/* 0x2A */ AudioRenderer_Render = 0x2A,
/* 0x34 */ AudioRenderer_Shutdown = 0x34,
};
/**
* A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer
* running on the ADSP.
*/
class AudioRenderer_Mailbox {
public:
/**
* Send a message from the host to the AudioRenderer.
*
* @param message_ - The message to send to the AudioRenderer.
*/
void HostSendMessage(RenderMessage message);
/**
* Host wait for a message from the AudioRenderer.
*
* @return The message returned from the AudioRenderer.
*/
RenderMessage HostWaitMessage();
/**
* Send a message from the AudioRenderer to the host.
*
* @param message_ - The message to send to the host.
*/
void ADSPSendMessage(RenderMessage message);
/**
* AudioRenderer wait for a message from the host.
*
* @return The message returned from the AudioRenderer.
*/
RenderMessage ADSPWaitMessage();
/**
* Get the command buffer with the given session id (0 or 1).
*
* @param session_id - The session id to get (0 or 1).
* @return The command buffer.
*/
CommandBuffer& GetCommandBuffer(s32 session_id);
/**
* Set the command buffer with the given session id (0 or 1).
*
* @param session_id - The session id to get (0 or 1).
* @param buffer - The command buffer to set.
*/
void SetCommandBuffer(u32 session_id, CommandBuffer& buffer);
/**
* Get the total render time taken for the last command lists sent.
*
* @return Total render time taken for the last command lists.
*/
u64 GetRenderTimeTaken() const;
/**
* Get the tick the AudioRenderer was signalled.
*
* @return The tick the AudioRenderer was signalled.
*/
u64 GetSignalledTick() const;
/**
* Set the tick the AudioRenderer was signalled.
*
* @param tick - The tick the AudioRenderer was signalled.
*/
void SetSignalledTick(u64 tick);
/**
* Clear the remaining command count.
*
* @param session_id - Index for which command list to clear (0 or 1).
*/
void ClearRemainCount(u32 session_id);
/**
* Get the remaining command count for a given command list.
*
* @param session_id - Index for which command list to clear (0 or 1).
* @return The remaining command count.
*/
u32 GetRemainCommandCount(u32 session_id) const;
/**
* Clear the command buffers (does not clear the time taken or the remaining command count).
*/
void ClearCommandBuffers();
private:
/// Host signalling event
Common::Event host_event{};
/// AudioRenderer signalling event
Common::Event adsp_event{};
/// Host message queue
Common::ReaderWriterQueue<RenderMessage> host_messages{};
/// AudioRenderer message queue
Common::ReaderWriterQueue<RenderMessage> adsp_messages{};
/// Command buffers
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
/// Tick the AudioRnederer was signalled
u64 signalled_tick{};
};
/**
* The AudioRenderer application running on the ADSP.
*/
class AudioRenderer {
public:
explicit AudioRenderer(Core::System& system);
~AudioRenderer();
/**
* Start the AudioRenderer.
*
* @param The mailbox to use for this session.
*/
void Start(AudioRenderer_Mailbox* mailbox);
/**
* Stop the AudioRenderer.
*/
void Stop();
private:
/**
* Main AudioRenderer thread, responsible for processing the command lists.
*/
void ThreadFunc();
/**
* Creates the streams which will receive the processed samples.
*/
void CreateSinkStreams();
/// Core system
Core::System& system;
/// Main thread
std::thread thread{};
/// The current state
std::atomic<bool> running{};
/// The active mailbox
AudioRenderer_Mailbox* mailbox{};
/// The command lists to process
std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
/// The output sink the AudioRenderer will use
Sink::Sink& sink;
/// The streams which will receive the processed samples
std::array<Sink::SinkStream*, MaxRendererSessions> streams;
};
} // namespace AudioRenderer::ADSP
} // namespace AudioCore

View File

@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "audio_core/common/common.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer::ADSP {
struct CommandBuffer {
CpuAddr buffer;
u64 size;
u64 time_limit;
u32 remaining_command_count;
bool reset_buffers;
u64 applet_resource_user_id;
u64 render_time_taken;
};
} // namespace AudioCore::AudioRenderer::ADSP

View File

@@ -0,0 +1,109 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/command_list_header.h"
#include "audio_core/renderer/command/commands.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/memory.h"
namespace AudioCore::AudioRenderer::ADSP {
void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size,
Sink::SinkStream* stream_) {
system = &system_;
memory = &system->Memory();
stream = stream_;
header = reinterpret_cast<CommandListHeader*>(buffer);
commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
commands_buffer_size = size;
command_count = header->command_count;
sample_count = header->sample_count;
target_sample_rate = header->sample_rate;
mix_buffers = header->samples_buffer;
buffer_count = header->buffer_count;
processed_command_count = 0;
}
void CommandListProcessor::SetProcessTimeMax(const u64 time) {
max_process_time = time;
}
u32 CommandListProcessor::GetRemainingCommandCount() const {
return command_count - processed_command_count;
}
void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
commands_buffer_size = size;
}
Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
return stream;
}
u64 CommandListProcessor::Process(u32 session_id) {
const auto start_time_{system->CoreTiming().GetClockTicks()};
const auto command_base{CpuAddr(commands)};
if (processed_command_count > 0) {
current_processing_time += start_time_ - end_time;
} else {
start_time = start_time_;
current_processing_time = 0;
}
std::string dump{fmt::format("\nSession {}\n", session_id)};
for (u32 index = 0; index < command_count; index++) {
auto& command{*reinterpret_cast<ICommand*>(commands)};
if (command.magic != 0xCAFEBABE) {
LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}",
command.magic);
return system->CoreTiming().GetClockTicks() - start_time_;
}
auto current_offset{CpuAddr(commands) - command_base};
if (current_offset + command.size > commands_buffer_size) {
LOG_ERROR(Service_Audio,
"Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}",
commands_buffer_size,
CpuAddr(commands) + command.size - sizeof(CommandListHeader));
return system->CoreTiming().GetClockTicks() - start_time_;
}
if (Settings::values.dump_audio_commands) {
command.Dump(*this, dump);
}
if (!command.Verify(*this)) {
break;
}
if (command.enabled) {
command.Process(*this);
} else {
dump += fmt::format("\tDisabled!\n");
}
processed_command_count++;
commands += command.size;
}
if (Settings::values.dump_audio_commands && dump != last_dump) {
LOG_WARNING(Service_Audio, "{}", dump);
last_dump = dump;
}
end_time = system->CoreTiming().GetClockTicks();
return end_time - start_time_;
}
} // namespace AudioCore::AudioRenderer::ADSP

View File

@@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/common/common.h"
#include "common/common_types.h"
namespace Core {
namespace Memory {
class Memory;
}
class System;
} // namespace Core
namespace AudioCore {
namespace Sink {
class SinkStream;
}
namespace AudioRenderer {
struct CommandListHeader;
namespace ADSP {
/**
* A processor for command lists given to the AudioRenderer.
*/
class CommandListProcessor {
public:
/**
* Initialize the processor.
*
* @param system_ - The core system.
* @param buffer - The command buffer to process.
* @param size - The size of the buffer.
* @param stream_ - The stream to be used for sending the samples.
*/
void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
/**
* Set the maximum processing time for this command list.
*
* @param time - The maximum process time.
*/
void SetProcessTimeMax(u64 time);
/**
* Get the remaining command count for this list.
*
* @return The remaining command count.
*/
u32 GetRemainingCommandCount() const;
/**
* Set the command buffer.
*
* @param buffer - The buffer to use.
* @param size - The size of the buffer.
*/
void SetBuffer(CpuAddr buffer, u64 size);
/**
* Get the stream for this command list.
*
* @return The stream associated with this command list.
*/
Sink::SinkStream* GetOutputSinkStream() const;
/**
* Process the command list.
*
* @param index - Index of the current command list.
* @return The time taken to process.
*/
u64 Process(u32 session_id);
/// Core system
Core::System* system{};
/// Core memory
Core::Memory::Memory* memory{};
/// Stream for the processed samples
Sink::SinkStream* stream{};
/// Header info for this command list
CommandListHeader* header{};
/// The command buffer
u8* commands{};
/// The command buffer size
u64 commands_buffer_size{};
/// The maximum processing time alloted
u64 max_process_time{};
/// The number of commands in the buffer
u32 command_count{};
/// The target sample count for output
u32 sample_count{};
/// The target sample rate for output
u32 target_sample_rate{};
/// The mixing buffers used by the commands
std::span<s32> mix_buffers{};
/// The number of mix buffers
u32 buffer_count{};
/// The number of processed commands so far
u32 processed_command_count{};
/// The processing start time of this list
u64 start_time{};
/// The current processing time for this list
u64 current_processing_time{};
/// The end processing time for this list
u64 end_time{};
/// Last command list string generated, used for dumping audio commands to console
std::string last_dump{};
};
} // namespace ADSP
} // namespace AudioRenderer
} // namespace AudioCore

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_core.h"
#include "audio_core/common/feature_support.h"
#include "audio_core/renderer/audio_device.h"
#include "audio_core/sink/sink.h"
#include "core/core.h"
namespace AudioCore::AudioRenderer {
AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
const u32 revision)
: output_sink{system.AudioCore().GetOutputSink()},
applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
const size_t max_count) {
std::span<AudioDeviceName> names{};
if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
names = usb_device_names;
} else {
names = device_names;
}
u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
for (u32 i = 0; i < out_count; i++) {
out_buffer.push_back(names[i]);
}
return out_count;
}
u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
const size_t max_count) {
u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
for (u32 i = 0; i < out_count; i++) {
out_buffer.push_back(output_device_names[i]);
}
return out_count;
}
void AudioDevice::SetDeviceVolumes(const f32 volume) {
output_sink.SetDeviceVolume(volume);
}
f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) {
return output_sink.GetDeviceVolume();
}
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/audio_render_manager.h"
namespace Core {
class System;
}
namespace AudioCore {
namespace Sink {
class Sink;
}
namespace AudioRenderer {
/**
* An interface to an output audio device available to the Switch.
*/
class AudioDevice {
public:
struct AudioDeviceName {
std::array<char, 0x100> name;
AudioDeviceName(const char* name_) {
std::strncpy(name.data(), name_, name.size());
}
};
std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput",
"AudioBuiltInSpeakerOutput", "AudioTvOutput",
"AudioUsbDeviceOutput"};
std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput",
"AudioBuiltInSpeakerOutput", "AudioTvOutput"};
std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput",
"AudioExternalOutput"};
explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
/**
* Get a list of the available output devices.
*
* @param out_buffer - Output buffer to write the available device names.
* @param max_count - Maximum number of devices to write (count of out_buffer).
* @return Number of device names written.
*/
u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
/**
* Get a list of the available output devices.
* Different to above somehow...
*
* @param out_buffer - Output buffer to write the available device names.
* @param max_count - Maximum number of devices to write (count of out_buffer).
* @return Number of device names written.
*/
u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
/**
* Set the volume of all streams in the backend sink.
*
* @param volume - Volume to set.
*/
void SetDeviceVolumes(f32 volume);
/**
* Get the volume for a given device name.
* Note: This is not fully implemented, we only assume 1 device for all streams.
*
* @param name - Name of the device to check. Unused.
* @return Volume of the device.
*/
f32 GetDeviceVolume(std::string_view name);
private:
/// Backend output sink for the device
Sink::Sink& output_sink;
/// Resource id this device is used for
const u64 applet_resource_user_id;
/// User audio renderer revision
const u32 user_revision;
};
} // namespace AudioRenderer
} // namespace AudioCore

View File

@@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_render_manager.h"
#include "audio_core/common/audio_renderer_parameter.h"
#include "audio_core/renderer/audio_renderer.h"
#include "audio_core/renderer/system_manager.h"
#include "core/core.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/service/audio/errors.h"
namespace AudioCore::AudioRenderer {
Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event)
: core{system_}, manager{manager_}, system{system_, rendered_event} {}
Result Renderer::Initialize(const AudioRendererParameterInternal& params,
Kernel::KTransferMemory* transfer_memory,
const u64 transfer_memory_size, const u32 process_handle,
const u64 applet_resource_user_id, const s32 session_id) {
if (params.execution_mode == ExecutionMode::Auto) {
if (!manager.AddSystem(system)) {
LOG_ERROR(Service_Audio,
"Both Audio Render sessions are in use, cannot create any more");
return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
}
system_registered = true;
}
initialized = true;
system.Initialize(params, transfer_memory, transfer_memory_size, process_handle,
applet_resource_user_id, session_id);
return ResultSuccess;
}
void Renderer::Finalize() {
auto session_id{system.GetSessionId()};
system.Finalize();
if (system_registered) {
manager.RemoveSystem(system);
system_registered = false;
}
manager.ReleaseSessionId(session_id);
}
System& Renderer::GetSystem() {
return system;
}
void Renderer::Start() {
system.Start();
}
void Renderer::Stop() {
system.Stop();
}
Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance,
std::span<u8> output) {
return system.Update(input, performance, output);
}
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/renderer/system.h"
#include "core/hle/service/audio/errors.h"
namespace Core {
class System;
}
namespace Kernel {
class KTransferMemory;
}
namespace AudioCore {
struct AudioRendererParameterInternal;
namespace AudioRenderer {
class Manager;
/**
* Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls.
*/
class Renderer {
public:
explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event);
/**
* Initialize the renderer.
* Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes
* everything to a default state.
*
* @param params - Input parameters to initialize the system with.
* @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
* @param transfer_memory_size - Size of the transfer memory. Unused.
* @param process_handle - Process handle, also used for memory. Unused.
* @param applet_resource_user_id - Applet id for this renderer. Unused.
* @param session_id - Session id of this renderer.
* @return Result code.
*/
Result Initialize(const AudioRendererParameterInternal& params,
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
u32 process_handle, u64 applet_resource_user_id, s32 session_id);
/**
* Finalize the renderer for shutdown.
*/
void Finalize();
/**
* Get the renderer's system.
*
* @return Reference to the system.
*/
System& GetSystem();
/**
* Start the renderer.
*/
void Start();
/**
* Stop the renderer.
*/
void Stop();
/**
* Update the audio renderer with new information.
* Called via RequestUpdate from the AudRen:U service.
*
* @param input - Input buffer containing the new data.
* @param performance - Optional performance buffer for outputting performance metrics.
* @param output - Output data from the renderer.
* @return Result code.
*/
Result RequestUpdate(std::span<const u8> input, std::span<u8> performance,
std::span<u8> output);
private:
/// System core
Core::System& core;
/// Manager this renderer is registered with
Manager& manager;
/// Is the audio renderer initialized?
bool initialized{};
/// Is the system registered with the manager?
bool system_registered{};
/// Audio render system, main driver of audio rendering
System system;
};
} // namespace AudioRenderer
} // namespace AudioCore

View File

@@ -0,0 +1,191 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/common/feature_support.h"
#include "audio_core/renderer/behavior/behavior_info.h"
namespace AudioCore::AudioRenderer {
BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {}
u32 BehaviorInfo::GetProcessRevisionNum() const {
return process_revision;
}
u32 BehaviorInfo::GetProcessRevision() const {
return Common::MakeMagic('R', 'E', 'V',
static_cast<char>(static_cast<u8>('0') + process_revision));
}
u32 BehaviorInfo::GetUserRevisionNum() const {
return user_revision;
}
u32 BehaviorInfo::GetUserRevision() const {
return Common::MakeMagic('R', 'E', 'V',
static_cast<char>(static_cast<u8>('0') + user_revision));
}
void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) {
user_revision = GetRevisionNum(user_revision_);
}
void BehaviorInfo::ClearError() {
error_count = 0;
}
void BehaviorInfo::AppendError(ErrorInfo& error) {
LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
error.error_code.raw, error.address);
if (error_count < MaxErrors) {
errors[error_count++] = error;
}
}
void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) {
auto error_count_{std::min(error_count, MaxErrors)};
std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo));
for (size_t i = 0; i < error_count_; i++) {
out_errors[i] = errors[i];
}
out_count = error_count_;
}
void BehaviorInfo::UpdateFlags(const Flags flags_) {
flags = flags_;
}
bool BehaviorInfo::IsMemoryForceMappingEnabled() const {
return flags.IsMemoryForceMappingEnabled;
}
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision);
}
bool BehaviorInfo::IsSplitterSupported() const {
return CheckFeatureSupported(SupportTags::Splitter, user_revision);
}
bool BehaviorInfo::IsSplitterBugFixed() const {
return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision);
}
bool BehaviorInfo::IsEffectInfoVersion2Supported() const {
return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision);
}
bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const {
return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize,
user_revision);
}
bool BehaviorInfo::IsWaveBufferVer2Supported() const {
return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision);
}
bool BehaviorInfo::IsLongSizePreDelaySupported() const {
return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision);
}
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const {
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2,
user_revision);
}
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const {
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3,
user_revision);
}
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const {
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
user_revision);
}
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const {
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
user_revision);
}
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent,
user_revision);
}
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent,
user_revision);
}
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent,
user_revision);
}
bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision);
}
bool BehaviorInfo::IsElapsedFrameCountSupported() const {
return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision);
}
bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const {
return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision);
}
size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const {
if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) {
return 2;
}
return 1;
}
bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision);
}
bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint,
user_revision);
}
bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const {
return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision);
}
bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const {
return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision);
}
bool BehaviorInfo::UseBiquadFilterFloatProcessing() const {
return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision);
}
bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision);
}
bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const {
return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision);
}
bool BehaviorInfo::IsDeviceApiVersion2Supported() const {
return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision);
}
bool BehaviorInfo::IsDelayChannelMappingChanged() const {
return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision);
}
bool BehaviorInfo::IsReverbChannelMappingChanged() const {
return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision);
}
bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
}
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,376 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include "audio_core/common/common.h"
#include "common/common_types.h"
#include "core/hle/service/audio/errors.h"
namespace AudioCore::AudioRenderer {
/**
* Holds host and user revisions, checks whether render features can be enabled, and reports errors.
*/
class BehaviorInfo {
static constexpr u32 MaxErrors = 10;
public:
struct ErrorInfo {
/* 0x00 */ Result error_code{0};
/* 0x04 */ u32 unk_04;
/* 0x08 */ CpuAddr address;
};
static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!");
struct Flags {
u64 IsMemoryForceMappingEnabled : 1;
};
struct InParameter {
/* 0x00 */ u32 revision;
/* 0x08 */ Flags flags;
};
static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!");
struct OutStatus {
/* 0x00 */ std::array<ErrorInfo, MaxErrors> errors;
/* 0xA0 */ u32 error_count;
/* 0xA4 */ char unkA4[0xC];
};
static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!");
BehaviorInfo();
/**
* Get the host revision as a number.
*
* @return The host revision.
*/
u32 GetProcessRevisionNum() const;
/**
* Get the host revision in chars, e.g REV8.
* Rev 10 and higher use the ascii characters above 9.
* E.g:
* Rev 10 = REV:
* Rev 11 = REV;
*
* @return The host revision.
*/
u32 GetProcessRevision() const;
/**
* Get the user revision as a number.
*
* @return The user revision.
*/
u32 GetUserRevisionNum() const;
/**
* Get the user revision in chars, e.g REV8.
* Rev 10 and higher use the ascii characters above 9. REV: REV; etc.
*
* @return The user revision.
*/
u32 GetUserRevision() const;
/**
* Set the user revision.
*
* @param user_revision - The user's revision.
*/
void SetUserLibRevision(u32 user_revision);
/**
* Clear the current error count.
*/
void ClearError();
/**
* Append an error to the error list.
*
* @param error - The new error.
*/
void AppendError(ErrorInfo& error);
/**
* Copy errors to the given output container.
*
* @param out_errors - Output container to receive the errors.
* @param out_count - The number of errors written.
*/
void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count);
/**
* Update the behaviour flags.
*
* @param flags - New flags to use.
*/
void UpdateFlags(Flags flags);
/**
* Check if memory pools can be forcibly mapped.
*
* @return True if enabled, otherwise false.
*/
bool IsMemoryForceMappingEnabled() const;
/**
* Check if the ADPCM context bug is fixed.
* The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being
* used.
*
* @return True if fixed, otherwise false.
*/
bool IsAdpcmLoopContextBugFixed() const;
/**
* Check if the splitter is supported.
*
* @return True if supported, otherwise false.
*/
bool IsSplitterSupported() const;
/**
* Check if the splitter bug is fixed.
* Update is given the wrong number of splitter destinations, leading to invalid data
* being processed.
*
* @return True if supported, otherwise false.
*/
bool IsSplitterBugFixed() const;
/**
* Check if effects version 2 are supported.
* This gives support for returning effect states from the AudioRenderer, currently only used
* for Limiter statistics.
*
* @return True if supported, otherwise false.
*/
bool IsEffectInfoVersion2Supported() const;
/**
* Check if a variadic command buffer is supported.
* As of Rev 5 with the added optional performance metric logging, the command
* buffer can be a variable size, so take that into account for calcualting its size.
*
* @return True if supported, otherwise false.
*/
bool IsVariadicCommandBufferSizeSupported() const;
/**
* Check if wave buffers version 2 are supported.
* See WaveBufferVersion1 and WaveBufferVersion2.
*
* @return True if supported, otherwise false.
*/
bool IsWaveBufferVer2Supported() const;
/**
* Check if long size pre delay is supported.
* This allows a longer initial delay time for the Reverb command.
*
* @return True if supported, otherwise false.
*/
bool IsLongSizePreDelaySupported() const;
/**
* Check if the command time estimator version 2 is supported.
*
* @return True if supported, otherwise false.
*/
bool IsCommandProcessingTimeEstimatorVersion2Supported() const;
/**
* Check if the command time estimator version 3 is supported.
*
* @return True if supported, otherwise false.
*/
bool IsCommandProcessingTimeEstimatorVersion3Supported() const;
/**
* Check if the command time estimator version 4 is supported.
*
* @return True if supported, otherwise false.
*/
bool IsCommandProcessingTimeEstimatorVersion4Supported() const;
/**
* Check if the command time estimator version 5 is supported.
*
* @return True if supported, otherwise false.
*/
bool IsCommandProcessingTimeEstimatorVersion5Supported() const;
/**
* Check if the AudioRenderer can use up to 70% of the allocated processing timeslice.
*
* @return True if supported, otherwise false.
*/
bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
/**
* Check if the AudioRenderer can use up to 75% of the allocated processing timeslice.
*
* @return True if supported, otherwise false.
*/
bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
/**
* Check if the AudioRenderer can use up to 80% of the allocated processing timeslice.
*
* @return True if supported, otherwise false.
*/
bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
/**
* Check if voice flushing is supported
* This allowws low-priority voices to be dropped if the AudioRenderer is running behind.
*
* @return True if supported, otherwise false.
*/
bool IsFlushVoiceWaveBuffersSupported() const;
/**
* Check if counting the number of elapsed frames is supported.
* This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
* processed a command list.
*
* @return True if supported, otherwise false.
*/
bool IsElapsedFrameCountSupported() const;
/**
* Check if performance metrics version 2 are supported.
* This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
* (Unused?).
*
* @return True if supported, otherwise false.
*/
bool IsPerformanceMetricsDataFormatVersion2Supported() const;
/**
* Get the supported performance metrics version.
* Version 2 logs some extra fields in output, such as number of voices dropped,
* processing start time, if the AudioRenderer exceeded its time, etc.
*
* @return Version supported, either 1 or 2.
*/
size_t GetPerformanceMetricsDataFormat() const;
/**
* Check if skipping voice pitch and sample rate conversion is supported.
* This speeds up the data source commands by skipping resampling if unwanted.
* See AudioCore::AudioRenderer::DecodeFromWaveBuffers
*
* @return True if supported, otherwise false.
*/
bool IsVoicePitchAndSrcSkippedSupported() const;
/**
* Check if resetting played sample count at loop points is supported.
* This resets the number of samples played in a voice state when a loop point is reached.
* See AudioCore::AudioRenderer::DecodeFromWaveBuffers
*
* @return True if supported, otherwise false.
*/
bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
/**
* Check if the clear state bug for biquad filters is fixed.
* The biquad state was not marked as needing re-initialisation when the effect was updated, it
* was only initialized once with a new effect.
*
* @return True if fixed, otherwise false.
*/
bool IsBiquadFilterEffectStateClearBugFixed() const;
/**
* Check if Q23 precision is supported for fixed point.
*
* @return True if supported, otherwise false.
*/
bool IsVolumeMixParameterPrecisionQ23Supported() const;
/**
* Check if float processing for biuad filters is supported.
*
* @return True if supported, otherwise false.
*/
bool UseBiquadFilterFloatProcessing() const;
/**
* Check if dirty-only mix updates are supported.
* This saves a lot of buffer size as mixes can be large and not change much.
*
* @return True if supported, otherwise false.
*/
bool IsMixInParameterDirtyOnlyUpdateSupported() const;
/**
* Check if multi-tap biquad filters are supported.
*
* @return True if supported, otherwise false.
*/
bool UseMultiTapBiquadFilterProcessing() const;
/**
* Check if device api version 2 is supported.
* In the SDK but not in any sysmodule? Not sure, left here for completeness anyway.
*
* @return True if supported, otherwise false.
*/
bool IsDeviceApiVersion2Supported() const;
/**
* Check if new channel mappings are used for Delay commands.
* Older commands used:
* front left/front right/back left/back right/center/lfe
* Whereas everywhere else in the code uses:
* front left/front right/center/lfe/back left/back right
* This corrects that and makes everything standardised.
*
* @return True if supported, otherwise false.
*/
bool IsDelayChannelMappingChanged() const;
/**
* Check if new channel mappings are used for Reverb commands.
* Older commands used:
* front left/front right/back left/back right/center/lfe
* Whereas everywhere else in the code uses:
* front left/front right/center/lfe/back left/back right
* This corrects that and makes everything standardised.
*
* @return True if supported, otherwise false.
*/
bool IsReverbChannelMappingChanged() const;
/**
* Check if new channel mappings are used for I3dl2Reverb commands.
* Older commands used:
* front left/front right/back left/back right/center/lfe
* Whereas everywhere else in the code uses:
* front left/front right/center/lfe/back left/back right
* This corrects that and makes everything standardised.
*
* @return True if supported, otherwise false.
*/
bool IsI3dl2ReverbChannelMappingChanged() const;
/// Host version
u32 process_revision;
/// User version
u32 user_revision{};
/// Behaviour flags
Flags flags{};
/// Errors generated and reported during Update
std::array<ErrorInfo, MaxErrors> errors{};
/// Error count
u32 error_count{};
};
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,539 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/common/feature_support.h"
#include "audio_core/renderer/behavior/behavior_info.h"
#include "audio_core/renderer/behavior/info_updater.h"
#include "audio_core/renderer/effect/effect_context.h"
#include "audio_core/renderer/effect/effect_reset.h"
#include "audio_core/renderer/memory/memory_pool_info.h"
#include "audio_core/renderer/mix/mix_context.h"
#include "audio_core/renderer/performance/performance_manager.h"
#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
#include "audio_core/renderer/sink/device_sink_info.h"
#include "audio_core/renderer/sink/sink_context.h"
#include "audio_core/renderer/splitter/splitter_context.h"
#include "audio_core/renderer/voice/voice_context.h"
namespace AudioCore::AudioRenderer {
InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_,
const u32 process_handle_, BehaviorInfo& behaviour_)
: input{input_.data() + sizeof(UpdateDataHeader)},
input_origin{input_}, output{output_.data() + sizeof(UpdateDataHeader)},
output_origin{output_}, in_header{reinterpret_cast<const UpdateDataHeader*>(
input_origin.data())},
out_header{reinterpret_cast<UpdateDataHeader*>(output_origin.data())},
expected_input_size{input_.size()}, expected_output_size{output_.size()},
process_handle{process_handle_}, behaviour{behaviour_} {
std::construct_at<UpdateDataHeader>(out_header, behaviour.GetProcessRevision());
}
Result InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
const auto voice_count{voice_context.GetCount()};
std::span<const VoiceChannelResource::InParameter> in_params{
reinterpret_cast<const VoiceChannelResource::InParameter*>(input), voice_count};
for (u32 i = 0; i < voice_count; i++) {
auto& resource{voice_context.GetChannelResource(i)};
resource.in_use = in_params[i].in_use;
if (in_params[i].in_use) {
resource.mix_volumes = in_params[i].mix_volumes;
}
}
const auto consumed_input_size{voice_count *
static_cast<u32>(sizeof(VoiceChannelResource::InParameter))};
if (consumed_input_size != in_header->voice_resources_size) {
LOG_ERROR(Service_Audio,
"Consumed an incorrect voice resource size, header size={}, consumed={}",
in_header->voice_resources_size, consumed_input_size);
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
input += consumed_input_size;
return ResultSuccess;
}
Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
std::span<MemoryPoolInfo> memory_pools,
const u32 memory_pool_count) {
const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
behaviour.IsMemoryForceMappingEnabled());
const auto voice_count{voice_context.GetCount()};
std::span<const VoiceInfo::InParameter> in_params{
reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count};
std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output),
voice_count};
for (u32 i = 0; i < voice_count; i++) {
auto& voice_info{voice_context.GetInfo(i)};
voice_info.in_use = false;
}
u32 new_voice_count{0};
for (u32 i = 0; i < voice_count; i++) {
const auto& in_param{in_params[i]};
std::array<VoiceState*, MaxChannels> voice_states{};
if (!in_param.in_use) {
continue;
}
auto& voice_info{voice_context.GetInfo(in_param.id)};
for (u32 channel = 0; channel < in_param.channel_count; channel++) {
voice_states[channel] = &voice_context.GetState(in_param.channel_resource_ids[channel]);
}
if (in_param.is_new) {
voice_info.Initialize();
for (u32 channel = 0; channel < in_param.channel_count; channel++) {
std::memset(voice_states[channel], 0, sizeof(VoiceState));
}
}
BehaviorInfo::ErrorInfo update_error{};
voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour);
if (!update_error.error_code.IsSuccess()) {
behaviour.AppendError(update_error);
}
std::array<std::array<BehaviorInfo::ErrorInfo, 2>, MaxWaveBuffers> wavebuffer_errors{};
voice_info.UpdateWaveBuffers(wavebuffer_errors, MaxWaveBuffers * 2, in_param, voice_states,
pool_mapper, behaviour);
for (auto& wavebuffer_error : wavebuffer_errors) {
for (auto& error : wavebuffer_error) {
if (error.error_code.IsError()) {
behaviour.AppendError(error);
}
}
}
voice_info.WriteOutStatus(out_params[i], in_param, voice_states);
new_voice_count += in_param.channel_count;
}
auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))};
auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))};
if (consumed_input_size != in_header->voices_size) {
LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}",
in_header->voices_size, consumed_input_size);
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
out_header->voices_size = consumed_output_size;
out_header->size += consumed_output_size;
input += consumed_input_size;
output += consumed_output_size;
voice_context.SetActiveCount(new_voice_count);
return ResultSuccess;
}
Result InfoUpdater::UpdateEffects(EffectContext& effect_context, const bool renderer_active,
std::span<MemoryPoolInfo> memory_pools,
const u32 memory_pool_count) {
if (behaviour.IsEffectInfoVersion2Supported()) {
return UpdateEffectsVersion2(effect_context, renderer_active, memory_pools,
memory_pool_count);
} else {
return UpdateEffectsVersion1(effect_context, renderer_active, memory_pools,
memory_pool_count);
}
}
Result InfoUpdater::UpdateEffectsVersion1(EffectContext& effect_context, const bool renderer_active,
std::span<MemoryPoolInfo> memory_pools,
const u32 memory_pool_count) {
PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
behaviour.IsMemoryForceMappingEnabled());
const auto effect_count{effect_context.GetCount()};
std::span<const EffectInfoBase::InParameterVersion1> in_params{
reinterpret_cast<const EffectInfoBase::InParameterVersion1*>(input), effect_count};
std::span<EffectInfoBase::OutStatusVersion1> out_params{
reinterpret_cast<EffectInfoBase::OutStatusVersion1*>(output), effect_count};
for (u32 i = 0; i < effect_count; i++) {
auto effect_info{&effect_context.GetInfo(i)};
if (effect_info->GetType() != in_params[i].type) {
effect_info->ForceUnmapBuffers(pool_mapper);
ResetEffect(effect_info, in_params[i].type);
}
BehaviorInfo::ErrorInfo error_info{};
effect_info->Update(error_info, in_params[i], pool_mapper);
if (error_info.error_code.IsError()) {
behaviour.AppendError(error_info);
}
effect_info->StoreStatus(out_params[i], renderer_active);
}
auto consumed_input_size{effect_count *
static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion1))};
auto consumed_output_size{effect_count *
static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion1))};
if (consumed_input_size != in_header->effects_size) {
LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}",
in_header->effects_size, consumed_input_size);
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
out_header->effects_size = consumed_output_size;
out_header->size += consumed_output_size;
input += consumed_input_size;
output += consumed_output_size;
return ResultSuccess;
}
Result InfoUpdater::UpdateEffectsVersion2(EffectContext& effect_context, const bool renderer_active,
std::span<MemoryPoolInfo> memory_pools,
const u32 memory_pool_count) {
PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
behaviour.IsMemoryForceMappingEnabled());
const auto effect_count{effect_context.GetCount()};
std::span<const EffectInfoBase::InParameterVersion2> in_params{
reinterpret_cast<const EffectInfoBase::InParameterVersion2*>(input), effect_count};
std::span<EffectInfoBase::OutStatusVersion2> out_params{
reinterpret_cast<EffectInfoBase::OutStatusVersion2*>(output), effect_count};
for (u32 i = 0; i < effect_count; i++) {
auto effect_info{&effect_context.GetInfo(i)};
if (effect_info->GetType() != in_params[i].type) {
effect_info->ForceUnmapBuffers(pool_mapper);
ResetEffect(effect_info, in_params[i].type);
}
BehaviorInfo::ErrorInfo error_info{};
effect_info->Update(error_info, in_params[i], pool_mapper);
if (error_info.error_code.IsError()) {
behaviour.AppendError(error_info);
}
effect_info->StoreStatus(out_params[i], renderer_active);
if (in_params[i].is_new) {
effect_info->InitializeResultState(effect_context.GetDspSharedResultState(i));
effect_info->InitializeResultState(effect_context.GetResultState(i));
}
effect_info->UpdateResultState(out_params[i].result_state,
effect_context.GetResultState(i));
}
auto consumed_input_size{effect_count *
static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion2))};
auto consumed_output_size{effect_count *
static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion2))};
if (consumed_input_size != in_header->effects_size) {
LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}",
in_header->effects_size, consumed_input_size);
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
out_header->effects_size = consumed_output_size;
out_header->size += consumed_output_size;
input += consumed_input_size;
output += consumed_output_size;
return ResultSuccess;
}
Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_count,
EffectContext& effect_context, SplitterContext& splitter_context) {
s32 mix_count{0};
u32 consumed_input_size{0};
if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)};
mix_count = in_dirty_params->count;
input += sizeof(MixInfo::InDirtyParameter);
consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) +
mix_count * sizeof(MixInfo::InParameter));
} else {
mix_count = mix_context.GetCount();
consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
}
if (mix_buffer_count == 0) {
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
std::span<const MixInfo::InParameter> in_params{
reinterpret_cast<const MixInfo::InParameter*>(input), static_cast<size_t>(mix_count)};
u32 total_buffer_count{0};
for (s32 i = 0; i < mix_count; i++) {
const auto& params{in_params[i]};
if (params.in_use) {
total_buffer_count += params.buffer_count;
if (params.dest_mix_id > static_cast<s32>(mix_context.GetCount()) &&
params.dest_mix_id != UnusedMixId && params.mix_id != FinalMixId) {
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
}
}
if (total_buffer_count > mix_buffer_count) {
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
bool mix_dirty{false};
for (s32 i = 0; i < mix_count; i++) {
const auto& params{in_params[i]};
s32 mix_id{i};
if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
mix_id = params.mix_id;
}
auto mix_info{mix_context.GetInfo(mix_id)};
if (mix_info->in_use != params.in_use) {
mix_info->in_use = params.in_use;
if (!params.in_use) {
mix_info->ClearEffectProcessingOrder();
}
mix_dirty = true;
}
if (params.in_use) {
mix_dirty |= mix_info->Update(mix_context.GetEdgeMatrix(), params, effect_context,
splitter_context, behaviour);
}
}
if (mix_dirty) {
if (behaviour.IsSplitterSupported() && splitter_context.UsingSplitter()) {
if (!mix_context.TSortInfo(splitter_context)) {
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
} else {
mix_context.SortInfo();
}
}
if (consumed_input_size != in_header->mix_size) {
LOG_ERROR(Service_Audio, "Consumed an incorrect mixes size, header size={}, consumed={}",
in_header->mix_size, consumed_input_size);
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
input += mix_count * sizeof(MixInfo::InParameter);
return ResultSuccess;
}
Result InfoUpdater::UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
const u32 memory_pool_count) {
PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
behaviour.IsMemoryForceMappingEnabled());
std::span<const SinkInfoBase::InParameter> in_params{
reinterpret_cast<const SinkInfoBase::InParameter*>(input), memory_pool_count};
std::span<SinkInfoBase::OutStatus> out_params{
reinterpret_cast<SinkInfoBase::OutStatus*>(output), memory_pool_count};
const auto sink_count{sink_context.GetCount()};
for (u32 i = 0; i < sink_count; i++) {
const auto& params{in_params[i]};
auto sink_info{sink_context.GetInfo(i)};
if (sink_info->GetType() != params.type) {
sink_info->CleanUp();
switch (params.type) {
case SinkInfoBase::Type::Invalid:
std::construct_at<SinkInfoBase>(reinterpret_cast<SinkInfoBase*>(sink_info));
break;
case SinkInfoBase::Type::DeviceSink:
std::construct_at<DeviceSinkInfo>(reinterpret_cast<DeviceSinkInfo*>(sink_info));
break;
case SinkInfoBase::Type::CircularBufferSink:
std::construct_at<CircularBufferSinkInfo>(
reinterpret_cast<CircularBufferSinkInfo*>(sink_info));
break;
default:
LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(params.type));
break;
}
}
BehaviorInfo::ErrorInfo error_info{};
sink_info->Update(error_info, out_params[i], params, pool_mapper);
if (error_info.error_code.IsError()) {
behaviour.AppendError(error_info);
}
}
const auto consumed_input_size{sink_count *
static_cast<u32>(sizeof(SinkInfoBase::InParameter))};
const auto consumed_output_size{sink_count * static_cast<u32>(sizeof(SinkInfoBase::OutStatus))};
if (consumed_input_size != in_header->sinks_size) {
LOG_ERROR(Service_Audio, "Consumed an incorrect sinks size, header size={}, consumed={}",
in_header->sinks_size, consumed_input_size);
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
input += consumed_input_size;
output += consumed_output_size;
out_header->sinks_size = consumed_output_size;
out_header->size += consumed_output_size;
return ResultSuccess;
}
Result InfoUpdater::UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools,
const u32 memory_pool_count) {
PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
behaviour.IsMemoryForceMappingEnabled());
std::span<const MemoryPoolInfo::InParameter> in_params{
reinterpret_cast<const MemoryPoolInfo::InParameter*>(input), memory_pool_count};
std::span<MemoryPoolInfo::OutStatus> out_params{
reinterpret_cast<MemoryPoolInfo::OutStatus*>(output), memory_pool_count};
for (size_t i = 0; i < memory_pool_count; i++) {
auto state{pool_mapper.Update(memory_pools[i], in_params[i], out_params[i])};
if (state != MemoryPoolInfo::ResultState::Success &&
state != MemoryPoolInfo::ResultState::BadParam &&
state != MemoryPoolInfo::ResultState::MapFailed &&
state != MemoryPoolInfo::ResultState::InUse) {
LOG_WARNING(Service_Audio, "Invalid ResultState from updating memory pools");
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
}
const auto consumed_input_size{memory_pool_count *
static_cast<u32>(sizeof(MemoryPoolInfo::InParameter))};
const auto consumed_output_size{memory_pool_count *
static_cast<u32>(sizeof(MemoryPoolInfo::OutStatus))};
if (consumed_input_size != in_header->memory_pool_size) {
LOG_ERROR(Service_Audio,
"Consumed an incorrect memory pool size, header size={}, consumed={}",
in_header->memory_pool_size, consumed_input_size);
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
input += consumed_input_size;
output += consumed_output_size;
out_header->memory_pool_size = consumed_output_size;
out_header->size += consumed_output_size;
return ResultSuccess;
}
Result InfoUpdater::UpdatePerformanceBuffer(std::span<u8> performance_output,
const u64 performance_output_size,
PerformanceManager* performance_manager) {
auto in_params{reinterpret_cast<const PerformanceManager::InParameter*>(input)};
auto out_params{reinterpret_cast<PerformanceManager::OutStatus*>(output)};
if (performance_manager != nullptr) {
out_params->history_size =
performance_manager->CopyHistories(performance_output.data(), performance_output_size);
performance_manager->SetDetailTarget(in_params->target_node_id);
} else {
out_params->history_size = 0;
}
const auto consumed_input_size{static_cast<u32>(sizeof(PerformanceManager::InParameter))};
const auto consumed_output_size{static_cast<u32>(sizeof(PerformanceManager::OutStatus))};
if (consumed_input_size != in_header->performance_buffer_size) {
LOG_ERROR(Service_Audio,
"Consumed an incorrect performance size, header size={}, consumed={}",
in_header->performance_buffer_size, consumed_input_size);
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
input += consumed_input_size;
output += consumed_output_size;
out_header->performance_buffer_size = consumed_output_size;
out_header->size += consumed_output_size;
return ResultSuccess;
}
Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
const auto in_params{reinterpret_cast<const BehaviorInfo::InParameter*>(input)};
if (!CheckValidRevision(in_params->revision)) {
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
if (in_params->revision != behaviour_.GetUserRevision()) {
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
behaviour_.ClearError();
behaviour_.UpdateFlags(in_params->flags);
if (in_header->behaviour_size != sizeof(BehaviorInfo::InParameter)) {
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
input += sizeof(BehaviorInfo::InParameter);
return ResultSuccess;
}
Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) {
auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);
const auto consumed_output_size{static_cast<u32>(sizeof(BehaviorInfo::OutStatus))};
output += consumed_output_size;
out_header->behaviour_size = consumed_output_size;
out_header->size += consumed_output_size;
return ResultSuccess;
}
Result InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
u32 consumed_size{0};
if (!splitter_context.Update(input, consumed_size)) {
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
input += consumed_size;
return ResultSuccess;
}
Result InfoUpdater::UpdateRendererInfo(const u64 elapsed_frames) {
struct RenderInfo {
/* 0x00 */ u64 frames_elapsed;
/* 0x08 */ char unk08[0x8];
};
static_assert(sizeof(RenderInfo) == 0x10, "RenderInfo has the wrong size!");
auto out_params{reinterpret_cast<RenderInfo*>(output)};
out_params->frames_elapsed = elapsed_frames;
const auto consumed_output_size{static_cast<u32>(sizeof(RenderInfo))};
output += consumed_output_size;
out_header->render_info_size = consumed_output_size;
out_header->size += consumed_output_size;
return ResultSuccess;
}
Result InfoUpdater::CheckConsumedSize() {
if (CpuAddr(input) - CpuAddr(input_origin.data()) != expected_input_size) {
return Service::Audio::ERR_INVALID_UPDATE_DATA;
} else if (CpuAddr(output) - CpuAddr(output_origin.data()) != expected_output_size) {
return Service::Audio::ERR_INVALID_UPDATE_DATA;
}
return ResultSuccess;
}
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,205 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "common/common_types.h"
#include "core/hle/service/audio/errors.h"
namespace AudioCore::AudioRenderer {
class BehaviorInfo;
class VoiceContext;
class MixContext;
class SinkContext;
class SplitterContext;
class EffectContext;
class MemoryPoolInfo;
class PerformanceManager;
class InfoUpdater {
struct UpdateDataHeader {
explicit UpdateDataHeader(u32 revision_) : revision{revision_} {}
/* 0x00 */ u32 revision;
/* 0x04 */ u32 behaviour_size{};
/* 0x08 */ u32 memory_pool_size{};
/* 0x0C */ u32 voices_size{};
/* 0x10 */ u32 voice_resources_size{};
/* 0x14 */ u32 effects_size{};
/* 0x18 */ u32 mix_size{};
/* 0x1C */ u32 sinks_size{};
/* 0x20 */ u32 performance_buffer_size{};
/* 0x24 */ char unk24[4];
/* 0x28 */ u32 render_info_size{};
/* 0x2C */ char unk2C[0x10];
/* 0x3C */ u32 size{sizeof(UpdateDataHeader)};
};
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!");
public:
explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle,
BehaviorInfo& behaviour);
/**
* Update the voice channel resources.
*
* @param voice_context - Voice context to update.
* @return Result code.
*/
Result UpdateVoiceChannelResources(VoiceContext& voice_context);
/**
* Update voices.
*
* @param voice_context - Voice context to update.
* @param memory_pools - Memory pools to use for these voices.
* @param memory_pool_count - Number of memory pools.
* @return Result code.
*/
Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools,
u32 memory_pool_count);
/**
* Update effects.
*
* @param effect_context - Effect context to update.
* @param renderer_active - Whether the AudioRenderer is active.
* @param memory_pools - Memory pools to use for these voices.
* @param memory_pool_count - Number of memory pools.
* @return Result code.
*/
Result UpdateEffects(EffectContext& effect_context, bool renderer_active,
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
/**
* Update mixes.
*
* @param mix_context - Mix context to update.
* @param mix_buffer_count - Number of mix buffers.
* @param effect_context - Effect context to update effort order.
* @param splitter_context - Splitter context for the mixes.
* @return Result code.
*/
Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context,
SplitterContext& splitter_context);
/**
* Update sinks.
*
* @param sink_context - Sink context to update.
* @param memory_pools - Memory pools to use for these voices.
* @param memory_pool_count - Number of memory pools.
* @return Result code.
*/
Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
u32 memory_pool_count);
/**
* Update memory pools.
*
* @param memory_pools - Memory pools to use for these voices.
* @param memory_pool_count - Number of memory pools.
* @return Result code.
*/
Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
/**
* Update the performance buffer.
*
* @param output - Output buffer for performance metrics.
* @param output_size - Output buffer size.
* @param performance_manager - Performance manager..
* @return Result code.
*/
Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size,
PerformanceManager* performance_manager);
/**
* Update behaviour.
*
* @param behaviour - Behaviour to update.
* @return Result code.
*/
Result UpdateBehaviorInfo(BehaviorInfo& behaviour);
/**
* Update errors.
*
* @param behaviour - Behaviour to update.
* @return Result code.
*/
Result UpdateErrorInfo(BehaviorInfo& behaviour);
/**
* Update splitter.
*
* @param splitter_context - Splitter context to update.
* @return Result code.
*/
Result UpdateSplitterInfo(SplitterContext& splitter_context);
/**
* Update renderer info.
*
* @param elapsed_frames - Number of elapsed frames.
* @return Result code.
*/
Result UpdateRendererInfo(u64 elapsed_frames);
/**
* Check that the input.output sizes match their expected values.
*
* @return Result code.
*/
Result CheckConsumedSize();
private:
/**
* Update effects version 1.
*
* @param effect_context - Effect context to update.
* @param renderer_active - Is the AudioRenderer active?
* @param memory_pools - Memory pools to use for these voices.
* @param memory_pool_count - Number of memory pools.
* @return Result code.
*/
Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active,
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
/**
* Update effects version 2.
*
* @param effect_context - Effect context to update.
* @param renderer_active - Is the AudioRenderer active?
* @param memory_pools - Memory pools to use for these voices.
* @param memory_pool_count - Number of memory pools.
* @return Result code.
*/
Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active,
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
/// Input buffer
u8 const* input;
/// Input buffer start
std::span<const u8> input_origin;
/// Output buffer start
u8* output;
/// Output buffer start
std::span<u8> output_origin;
/// Input header
const UpdateDataHeader* in_header;
/// Output header
UpdateDataHeader* out_header;
/// Expected input size, see CheckConsumedSize
u64 expected_input_size;
/// Expected output size, see CheckConsumedSize
u64 expected_output_size;
/// Unused
u32 process_handle;
/// Behaviour
BehaviorInfo& behaviour;
};
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,714 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/behavior/behavior_info.h"
#include "audio_core/renderer/command/command_buffer.h"
#include "audio_core/renderer/command/command_list_header.h"
#include "audio_core/renderer/command/command_processing_time_estimator.h"
#include "audio_core/renderer/effect/biquad_filter.h"
#include "audio_core/renderer/effect/delay.h"
#include "audio_core/renderer/effect/reverb.h"
#include "audio_core/renderer/memory/memory_pool_info.h"
#include "audio_core/renderer/mix/mix_info.h"
#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
#include "audio_core/renderer/sink/device_sink_info.h"
#include "audio_core/renderer/sink/sink_info_base.h"
#include "audio_core/renderer/voice/voice_info.h"
#include "audio_core/renderer/voice/voice_state.h"
namespace AudioCore::AudioRenderer {
template <typename T, CommandId Id>
T& CommandBuffer::GenerateStart(const s32 node_id) {
if (size + sizeof(T) >= command_list.size_bytes()) {
LOG_ERROR(
Service_Audio,
"Attempting to write commands beyond the end of allocated command buffer memory!");
UNREACHABLE();
}
auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))};
cmd.magic = CommandMagic;
cmd.enabled = true;
cmd.type = Id;
cmd.size = sizeof(T);
cmd.node_id = node_id;
return cmd;
}
template <typename T>
void CommandBuffer::GenerateEnd(T& cmd) {
cmd.estimated_process_time = time_estimator->Estimate(cmd);
estimated_process_time += cmd.estimated_process_time;
size += sizeof(T);
count++;
}
void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id,
const MemoryPoolInfo& memory_pool_,
VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>(
node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
cmd.channel_index = channel;
cmd.channel_count = voice_info.channel_count;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd);
}
void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>(
node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
cmd.channel_index = channel;
cmd.channel_count = voice_info.channel_count;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd);
}
void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id,
const MemoryPoolInfo& memory_pool_,
VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>(
node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
cmd.channel_index = channel;
cmd.channel_count = voice_info.channel_count;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd);
}
void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>(
node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
cmd.channel_index = channel;
cmd.channel_count = voice_info.channel_count;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd);
}
void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id,
const MemoryPoolInfo& memory_pool_,
VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
cmd.data_address = voice_info.data_address.GetReference(true);
cmd.data_size = voice_info.data_address.GetSize();
GenerateEnd<AdpcmDataSourceVersion1Command>(cmd);
}
void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
cmd.channel_index = channel;
cmd.channel_count = voice_info.channel_count;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
cmd.data_address = voice_info.data_address.GetReference(true);
cmd.data_size = voice_info.data_address.GetSize();
GenerateEnd<AdpcmDataSourceVersion2Command>(cmd);
}
void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset,
const s16 input_index, const f32 volume,
const u8 precision) {
auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)};
cmd.precision = precision;
cmd.input_index = buffer_offset + input_index;
cmd.output_index = buffer_offset + input_index;
cmd.volume = volume;
GenerateEnd<VolumeCommand>(cmd);
}
void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info,
const s16 buffer_count, const u8 precision) {
auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)};
cmd.input_index = buffer_count;
cmd.output_index = buffer_count;
cmd.prev_volume = voice_info.prev_volume;
cmd.volume = voice_info.volume;
cmd.precision = precision;
GenerateEnd<VolumeRampCommand>(cmd);
}
void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel,
const u32 biquad_index,
const bool use_float_processing) {
auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
cmd.input = buffer_count + channel;
cmd.output = buffer_count + channel;
cmd.biquad = voice_info.biquads[biquad_index];
cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
cmd.needs_init = !voice_info.biquad_initialized[biquad_index];
cmd.use_float_processing = use_float_processing;
GenerateEnd<BiquadFilterCommand>(cmd);
}
void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 buffer_offset, const s8 channel,
const bool needs_init,
const bool use_float_processing) {
auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
const auto& parameter{
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
const auto state{
reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())};
cmd.input = buffer_offset + parameter.inputs[channel];
cmd.output = buffer_offset + parameter.outputs[channel];
cmd.biquad.b = parameter.b;
cmd.biquad.a = parameter.a;
cmd.state = memory_pool->Translate(CpuAddr(state),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
cmd.needs_init = needs_init;
cmd.use_float_processing = use_float_processing;
GenerateEnd<BiquadFilterCommand>(cmd);
}
void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index,
const s16 output_index, const s16 buffer_offset,
const f32 volume, const u8 precision) {
auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)};
cmd.input_index = input_index;
cmd.output_index = output_index;
cmd.volume = volume;
cmd.precision = precision;
GenerateEnd<MixCommand>(cmd);
}
void CommandBuffer::GenerateMixRampCommand(const s32 node_id,
[[maybe_unused]] const s16 buffer_count,
const s16 input_index, const s16 output_index,
const f32 volume, const f32 prev_volume,
const CpuAddr prev_samples, const u8 precision) {
if (volume == 0.0f && prev_volume == 0.0f) {
return;
}
auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)};
cmd.input_index = input_index;
cmd.output_index = output_index;
cmd.prev_volume = prev_volume;
cmd.volume = volume;
cmd.previous_sample = prev_samples;
cmd.precision = precision;
GenerateEnd<MixRampCommand>(cmd);
}
void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count,
const s16 input_index, s16 output_index,
std::span<const f32> volumes,
std::span<const f32> prev_volumes,
const CpuAddr prev_samples, const u8 precision) {
auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)};
cmd.buffer_count = buffer_count;
for (s32 i = 0; i < buffer_count; i++) {
cmd.inputs[i] = input_index;
cmd.outputs[i] = output_index++;
cmd.prev_volumes[i] = prev_volumes[i];
cmd.volumes[i] = volumes[i];
}
cmd.previous_samples = prev_samples;
cmd.precision = precision;
GenerateEnd<MixRampGroupedCommand>(cmd);
}
void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state,
std::span<const s32> buffer, const s16 buffer_count,
s16 buffer_offset, const bool was_playing) {
auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)};
cmd.enabled = was_playing;
for (u32 i = 0; i < MaxMixBuffers; i++) {
cmd.inputs[i] = buffer_offset++;
}
cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()),
MaxMixBuffers * sizeof(s32));
cmd.buffer_count = buffer_count;
cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32));
GenerateEnd<DepopPrepareCommand>(cmd);
}
void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info,
std::span<const s32> depop_buffer) {
auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)};
cmd.input = mix_info.buffer_offset;
cmd.count = mix_info.buffer_count;
cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f;
cmd.depop_buffer =
memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32));
GenerateEnd<DepopForMixBuffersCommand>(cmd);
}
void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 buffer_offset) {
auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)};
const auto& parameter{
*reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())};
const auto state{effect_info.GetStateBuffer()};
if (IsChannelCountValid(parameter.channel_count)) {
const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))};
if (state_buffer) {
for (s16 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) {
UseOldChannelMapping(cmd.inputs, cmd.outputs);
}
cmd.parameter = parameter;
cmd.effect_enabled = effect_info.IsEnabled();
cmd.state = state_buffer;
cmd.workbuffer = effect_info.GetWorkbuffer(-1);
}
}
GenerateEnd<DelayCommand>(cmd);
}
void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset,
UpsamplerInfo& upsampler_info, const u32 input_count,
std::span<const s8> inputs, const s16 buffer_count,
const u32 sample_count_, const u32 sample_rate_) {
auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)};
cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos,
upsampler_info.sample_count * sizeof(s32));
cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels);
cmd.buffer_count = buffer_count;
cmd.unk_20 = 0;
cmd.source_sample_count = sample_count_;
cmd.source_sample_rate = sample_rate_;
upsampler_info.input_count = input_count;
for (u32 i = 0; i < input_count; i++) {
upsampler_info.inputs[i] = buffer_offset + inputs[i];
}
cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo));
GenerateEnd<UpsampleCommand>(cmd);
}
void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs,
const s16 buffer_offset,
std::span<const f32> downmix_coeff) {
auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)};
for (u32 i = 0; i < MaxChannels; i++) {
cmd.inputs[i] = buffer_offset + inputs[i];
cmd.outputs[i] = buffer_offset + inputs[i];
}
for (u32 i = 0; i < 4; i++) {
cmd.down_mix_coeff[i] = downmix_coeff[i];
}
GenerateEnd<DownMix6chTo2chCommand>(cmd);
}
void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 input_index, const s16 output_index,
const s16 buffer_offset, const u32 update_count,
const u32 count_max, const u32 write_offset) {
auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)};
if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
cmd.input = buffer_offset + input_index;
cmd.output = buffer_offset + output_index;
cmd.send_buffer_info = effect_info.GetSendBufferInfo();
cmd.send_buffer = effect_info.GetSendBuffer();
cmd.return_buffer_info = effect_info.GetReturnBufferInfo();
cmd.return_buffer = effect_info.GetReturnBuffer();
cmd.count_max = count_max;
cmd.write_offset = write_offset;
cmd.update_count = update_count;
cmd.effect_enabled = effect_info.IsEnabled();
}
GenerateEnd<AuxCommand>(cmd);
}
void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset,
SinkInfoBase& sink_info, const u32 session_id,
std::span<s32> samples_buffer) {
auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)};
const auto& parameter{
*reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
cmd.session_id = session_id;
if (state.upsampler_info != nullptr) {
const auto size_{state.upsampler_info->sample_count * parameter.input_count};
const auto size_bytes{size_ * sizeof(s32)};
const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)};
cmd.sample_buffer = {reinterpret_cast<s32*>(addr),
parameter.input_count * state.upsampler_info->sample_count};
} else {
cmd.sample_buffer = samples_buffer;
}
cmd.input_count = parameter.input_count;
for (u32 i = 0; i < parameter.input_count; i++) {
cmd.inputs[i] = buffer_offset + parameter.inputs[i];
}
GenerateEnd<DeviceSinkCommand>(cmd);
}
void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info,
const s16 buffer_offset) {
auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)};
const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>(
sink_info.GetParameter())};
auto state{
*reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())};
cmd.input_count = parameter.input_count;
for (u32 i = 0; i < parameter.input_count; i++) {
cmd.inputs[i] = buffer_offset + parameter.inputs[i];
}
cmd.address = state.address_info.GetReference(true);
cmd.size = parameter.size;
cmd.pos = state.current_pos;
GenerateEnd<CircularBufferSinkCommand>(cmd);
}
void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 buffer_offset,
const bool long_size_pre_delay_supported) {
auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)};
const auto& parameter{
*reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())};
const auto state{effect_info.GetStateBuffer()};
if (IsChannelCountValid(parameter.channel_count)) {
const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))};
if (state_buffer) {
for (s16 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) {
UseOldChannelMapping(cmd.inputs, cmd.outputs);
}
cmd.parameter = parameter;
cmd.effect_enabled = effect_info.IsEnabled();
cmd.state = state_buffer;
cmd.workbuffer = effect_info.GetWorkbuffer(-1);
cmd.long_size_pre_delay_supported = long_size_pre_delay_supported;
}
}
GenerateEnd<ReverbCommand>(cmd);
}
void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 buffer_offset) {
auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)};
const auto& parameter{
*reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())};
const auto state{effect_info.GetStateBuffer()};
if (IsChannelCountValid(parameter.channel_count)) {
const auto state_buffer{
memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))};
if (state_buffer) {
for (s16 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) {
UseOldChannelMapping(cmd.inputs, cmd.outputs);
}
cmd.parameter = parameter;
cmd.effect_enabled = effect_info.IsEnabled();
cmd.state = state_buffer;
cmd.workbuffer = effect_info.GetWorkbuffer(-1);
}
}
GenerateEnd<I3dl2ReverbCommand>(cmd);
}
void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state,
const PerformanceEntryAddresses& entry_addresses) {
auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)};
cmd.state = state;
cmd.entry_address = entry_addresses;
GenerateEnd<PerformanceCommand>(cmd);
}
void CommandBuffer::GenerateClearMixCommand(const s32 node_id) {
auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)};
GenerateEnd<ClearMixBufferCommand>(cmd);
}
void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 buffer_offset, const s8 channel) {
auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)};
const auto& parameter{
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
cmd.input_index = buffer_offset + parameter.inputs[channel];
cmd.output_index = buffer_offset + parameter.outputs[channel];
GenerateEnd<CopyMixBufferCommand>(cmd);
}
void CommandBuffer::GenerateLightLimiterCommand(
const s32 node_id, const s16 buffer_offset,
const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state,
const bool enabled, const CpuAddr workbuffer) {
auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)};
if (IsChannelCountValid(parameter.channel_count)) {
const auto state_buffer{
memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
if (state_buffer) {
for (s8 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
std::memcpy(&cmd.parameter, &parameter, sizeof(LightLimiterInfo::ParameterVersion1));
cmd.effect_enabled = enabled;
cmd.state = state_buffer;
cmd.workbuffer = workbuffer;
}
}
GenerateEnd<LightLimiterVersion1Command>(cmd);
}
void CommandBuffer::GenerateLightLimiterCommand(
const s32 node_id, const s16 buffer_offset,
const LightLimiterInfo::ParameterVersion2& parameter,
const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state,
const bool enabled, const CpuAddr workbuffer) {
auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)};
if (IsChannelCountValid(parameter.channel_count)) {
const auto state_buffer{
memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
if (state_buffer) {
for (s8 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
cmd.parameter = parameter;
cmd.effect_enabled = enabled;
cmd.state = state_buffer;
if (cmd.parameter.statistics_enabled) {
cmd.result_state = memory_pool->Translate(
CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal));
} else {
cmd.result_state = 0;
}
cmd.workbuffer = workbuffer;
}
}
GenerateEnd<LightLimiterVersion2Command>(cmd);
}
void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)};
cmd.input = buffer_count + channel;
cmd.output = buffer_count + channel;
cmd.biquads = voice_info.biquads;
cmd.states[0] =
memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
cmd.states[1] =
memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
cmd.needs_init[0] = !voice_info.biquad_initialized[0];
cmd.needs_init[1] = !voice_info.biquad_initialized[1];
cmd.filter_tap_count = MaxBiquadFilters;
GenerateEnd<MultiTapBiquadFilterCommand>(cmd);
}
void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 input_index, const s16 output_index,
const s16 buffer_offset, const u32 update_count,
const u32 count_max, const u32 write_offset) {
auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)};
if (effect_info.GetSendBuffer()) {
cmd.input = buffer_offset + input_index;
cmd.output = buffer_offset + output_index;
cmd.send_buffer_info = effect_info.GetSendBufferInfo();
cmd.send_buffer = effect_info.GetSendBuffer();
cmd.count_max = count_max;
cmd.write_offset = write_offset;
cmd.update_count = update_count;
cmd.effect_enabled = effect_info.IsEnabled();
}
GenerateEnd<CaptureCommand>(cmd);
}
void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id) {
auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)};
auto& parameter{
*reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())};
auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())};
if (IsChannelCountValid(parameter.channel_count)) {
auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))};
if (state_buffer) {
for (u16 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
cmd.parameter = parameter;
cmd.workbuffer = state_buffer;
cmd.enabled = effect_info.IsEnabled();
}
}
GenerateEnd<CompressorCommand>(cmd);
}
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,466 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/renderer/command/commands.h"
#include "audio_core/renderer/effect/light_limiter.h"
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
struct UpsamplerInfo;
struct VoiceState;
class EffectInfoBase;
class ICommandProcessingTimeEstimator;
class MixInfo;
class MemoryPoolInfo;
class SinkInfoBase;
class VoiceInfo;
/**
* Utility functions to generate and add commands into the current command list.
*/
class CommandBuffer {
public:
/**
* Generate a PCM s16 version 1 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
VoiceInfo& voice_info, const VoiceState& voice_state,
s16 buffer_count, s8 channel);
/**
* Generate a PCM s16 version 2 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state, s16 buffer_count,
s8 channel);
/**
* Generate a PCM f32 version 1 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
VoiceInfo& voice_info, const VoiceState& voice_state,
s16 buffer_count, s8 channel);
/**
* Generate a PCM f32 version 2 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state, s16 buffer_count,
s8 channel);
/**
* Generate an ADPCM version 1 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
VoiceInfo& voice_info, const VoiceState& voice_state,
s16 buffer_count, s8 channel);
/**
* Generate an ADPCM version 2 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state, s16 buffer_count, s8 channel);
/**
* Generate a volume command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer index to generate this command at.
* @param input_index - Channel index and mix buffer offset for this command.
* @param volume - Mix volume added to the input samples.
* @param precision - Number of decimal bits for fixed point operations.
*/
void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume,
u8 precision);
/**
* Generate a volume ramp command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command takes its volumes from.
* @param buffer_count - Number of active mix buffers, command will generate at this index.
* @param precision - Number of decimal bits for fixed point operations.
*/
void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count,
u8 precision);
/**
* Generate a biquad filter command from a voice, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command takes biquad parameters from.
* @param voice_state - Used by the AudioRenderer to track previous samples.
* @param buffer_count - Number of active mix buffers,
* command will generate at this index + channel.
* @param channel - Channel index for this filter to work on.
* @param biquad_index - Which biquad filter to use for this command (0-1).
* @param use_float_processing - Should int or float processing be used?
*/
void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state, s16 buffer_count, s8 channel,
u32 biquad_index, bool use_float_processing);
/**
* Generate a biquad filter effect command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - The effect info this command takes biquad parameters from.
* @param buffer_offset - Mix buffer offset this command will use,
* command will generate at this index + channel.
* @param channel - Channel index for this filter to work on.
* @param needs_init - True if the biquad state needs initialisation.
* @param use_float_processing - Should int or float processing be used?
*/
void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
s8 channel, bool needs_init, bool use_float_processing);
/**
* Generate a mix command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param input_index - Input mix buffer index for this command.
* Added to the buffer offset.
* @param output_index - Output mix buffer index for this command.
* Added to the buffer offset.
* @param buffer_offset - Mix buffer offset this command will use.
* @param volume - Volume to be applied to the input.
* @param precision - Number of decimal bits for fixed point operations.
*/
void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset,
f32 volume, u8 precision);
/**
* Generate a mix ramp command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_count - Number of active mix buffers.
* @param input_index - Input mix buffer index for this command.
* Added to buffer_count.
* @param output_index - Output mix buffer index for this command.
* Added to buffer_count.
* @param volume - Current mix volume used for calculating the ramp.
* @param prev_volume - Previous mix volume, used for calculating the ramp,
* also applied to the input.
* @param precision - Number of decimal bits for fixed point operations.
*/
void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision);
/**
* Generate a mix ramp grouped command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_count - Number of active mix buffers.
* @param input_index - Input mix buffer index for this command.
* Added to buffer_count.
* @param output_index - Output mix buffer index for this command.
* Added to buffer_count.
* @param volumes - Current mix volumes used for calculating the ramp.
* @param prev_volumes - Previous mix volumes, used for calculating the ramp,
* also applied to the input.
* @param precision - Number of decimal bits for fixed point operations.
*/
void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
s16 output_index, std::span<const f32> volumes,
std::span<const f32> prev_volumes, CpuAddr prev_samples,
u8 precision);
/**
* Generate a depop prepare command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_state - State to track the previous depop samples for each mix buffer.
* @param buffer - State to track the current depop samples for each mix buffer.
* @param buffer_count - Number of active mix buffers.
* @param buffer_offset - Base mix buffer index to generate the channel depops at.
* @param was_playing - Command only needs to work if the voice was previously playing.
*/
void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state,
std::span<const s32> buffer, s16 buffer_count,
s16 buffer_offset, bool was_playing);
/**
* Generate a depop command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param mix_info - Mix info to get the buffer count and base offsets from.
* @param depop_buffer - Buffer of current depop sample values to be added to the input
* channels.
*/
void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info,
std::span<const s32> depop_buffer);
/**
* Generate a delay command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - Delay effect info to generate this command from.
* @param buffer_offset - Base mix buffer offset to apply the apply the delay.
*/
void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
/**
* Generate an upsample command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer offset to upsample.
* @param upsampler_info - Upsampler info to control the upsampling.
* @param input_count - Number of input channels to upsample.
* @param inputs - Input mix buffer indexes.
* @param buffer_count - Number of active mix buffers.
* @param sample_count - Source sample count of the input.
* @param sample_rate - Source sample rate of the input.
*/
void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info,
u32 input_count, std::span<const s8> inputs, s16 buffer_count,
u32 sample_count, u32 sample_rate);
/**
* Generate a downmix 6 -> 2 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param inputs - Input mix buffer indexes.
* @param buffer_offset - Base mix buffer offset of the channels to downmix.
* @param downmix_coeff - Downmixing coefficients.
*/
void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset,
std::span<const f32> downmix_coeff);
/**
* Generate an aux buffer command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - Aux effect info to generate this command from.
* @param input_index - Input mix buffer index for this command.
* Added to buffer_offset.
* @param output_index - Output mix buffer index for this command.
* Added to buffer_offset.
* @param buffer_offset - Base mix buffer offset to use.
* @param update_count - Number of samples to write back to the game as updated, can be 0.
* @param count_max - Maximum number of samples to read or write.
* @param write_offset - Current read or write offset within the buffer.
*/
void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max,
u32 write_offset);
/**
* Generate a device sink command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer offset to use.
* @param sink_info - The sink_info to generate this command from.
* @session_id - System session id this command is generated from.
* @samples_buffer - The buffer to be sent to the sink if upsampling is not used.
*/
void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
u32 session_id, std::span<s32> samples_buffer);
/**
* Generate a circular buffer sink command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param sink_info - The sink_info to generate this command from.
* @param buffer_offset - Base mix buffer offset to use.
*/
void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset);
/**
* Generate a reverb command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - Reverb effect info to generate this command from.
* @param buffer_offset - Base mix buffer offset to use.
* @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb
* begins?
*/
void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
bool long_size_pre_delay_supported);
/**
* Generate an I3DL2 reverb command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - I3DL2Reverb effect info to generate this command from.
* @param buffer_offset - Base mix buffer offset to use.
*/
void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
/**
* Generate a performance command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param state - State of the performance.
* @param entry_addresses - The addresses to be filled in by the AudioRenderer.
*/
void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
const PerformanceEntryAddresses& entry_addresses);
/**
* Generate a clear mix command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
*/
void GenerateClearMixCommand(s32 node_id);
/**
* Generate a copy mix command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - BiquadFilter effect info to generate this command from.
* @param buffer_offset - Base mix buffer offset to use.
* @param channel - Index to the effect's parameters input indexes for this command.
*/
void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
s8 channel);
/**
* Generate a light limiter version 1 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer offset to use.
* @param parameter - Effect parameter to generate from.
* @param state - State used by the AudioRenderer between commands.
* @param enabled - Is this command enabled?
* @param workbuffer - Game-supplied memory for the state.
*/
void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
const LightLimiterInfo::ParameterVersion1& parameter,
const LightLimiterInfo::State& state, bool enabled,
CpuAddr workbuffer);
/**
* Generate a light limiter version 2 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer offset to use.
* @param parameter - Effect parameter to generate from.
* @param statistics - Statistics reported by the AudioRenderer on the limiter's state.
* @param state - State used by the AudioRenderer between commands.
* @param enabled - Is this command enabled?
* @param workbuffer - Game-supplied memory for the state.
*/
void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
const LightLimiterInfo::ParameterVersion2& parameter,
const LightLimiterInfo::StatisticsInternal& statistics,
const LightLimiterInfo::State& state, bool enabled,
CpuAddr workbuffer);
/**
* Generate a multitap biquad filter command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command takes biquad parameters from.
* @param voice_state - Used by the AudioRenderer to track previous samples.
* @param buffer_count - Number of active mix buffers,
* command will generate at this index + channel.
* @param channel - Channel index for this filter to work on.
*/
void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state, s16 buffer_count,
s8 channel);
/**
* Generate a capture command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - Capture effect info to generate this command from.
* @param input_index - Input mix buffer index for this command.
* Added to buffer_offset.
* @param output_index - Output mix buffer index for this command (unused).
* Added to buffer_offset.
* @param buffer_offset - Base mix buffer offset to use.
* @param update_count - Number of samples to write back to the game as updated, can be 0.
* @param count_max - Maximum number of samples to read or write.
* @param write_offset - Current read or write offset within the buffer.
*/
void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
s16 output_index, s16 buffer_offset, u32 update_count,
u32 count_max, u32 write_offset);
/**
* Generate a compressor command, adding it to the command list.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info - Capture effect info to generate this command from.
* @param node_id - Node id of the voice this command is generated for.
*/
void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
/// Command list buffer generated commands will be added to
std::span<u8> command_list{};
/// Input sample count, unused
u32 sample_count{};
/// Input sample rate, unused
u32 sample_rate{};
/// Current size of the command buffer
u64 size{};
/// Current number of commands added
u32 count{};
/// Current estimated processing time for all commands
u32 estimated_process_time{};
/// Used for mapping buffers for the AudioRenderer
MemoryPoolInfo* memory_pool{};
/// Used for estimating command process times
ICommandProcessingTimeEstimator* time_estimator{};
/// Used to check which rendering features are currently enabled
BehaviorInfo* behavior{};
private:
template <typename T, CommandId Id>
T& GenerateStart(const s32 node_id);
template <typename T>
void GenerateEnd(T& cmd);
};
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,796 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/common/audio_renderer_parameter.h"
#include "audio_core/renderer/behavior/behavior_info.h"
#include "audio_core/renderer/command/command_buffer.h"
#include "audio_core/renderer/command/command_generator.h"
#include "audio_core/renderer/command/command_list_header.h"
#include "audio_core/renderer/effect/aux_.h"
#include "audio_core/renderer/effect/biquad_filter.h"
#include "audio_core/renderer/effect/buffer_mixer.h"
#include "audio_core/renderer/effect/capture.h"
#include "audio_core/renderer/effect/effect_context.h"
#include "audio_core/renderer/effect/light_limiter.h"
#include "audio_core/renderer/mix/mix_context.h"
#include "audio_core/renderer/performance/detail_aspect.h"
#include "audio_core/renderer/performance/entry_aspect.h"
#include "audio_core/renderer/sink/device_sink_info.h"
#include "audio_core/renderer/sink/sink_context.h"
#include "audio_core/renderer/splitter/splitter_context.h"
#include "audio_core/renderer/voice/voice_context.h"
#include "common/alignment.h"
namespace AudioCore::AudioRenderer {
CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_,
const CommandListHeader& command_list_header_,
const AudioRendererSystemContext& render_context_,
VoiceContext& voice_context_, MixContext& mix_context_,
EffectContext& effect_context_, SinkContext& sink_context_,
SplitterContext& splitter_context_,
PerformanceManager* performance_manager_)
: command_buffer{command_buffer_}, command_header{command_list_header_},
render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_},
effect_context{effect_context_}, sink_context{sink_context_},
splitter_context{splitter_context_}, performance_manager{performance_manager_} {
command_buffer.GenerateClearMixCommand(InvalidNodeId);
}
void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info,
const VoiceState& voice_state, const s8 channel) {
if (voice_info.mix_id == UnusedMixId) {
if (voice_info.splitter_id != UnusedSplitterId) {
auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)};
u32 dest_id{0};
while (destination != nullptr) {
if (destination->IsConfigured()) {
auto mix_id{destination->GetMixId()};
if (mix_id < mix_context.GetCount()) {
auto mix_info{mix_context.GetInfo(mix_id)};
command_buffer.GenerateDepopPrepareCommand(
voice_info.node_id, voice_state, render_context.depop_buffer,
mix_info->buffer_count, mix_info->buffer_offset,
voice_info.was_playing);
}
}
dest_id++;
destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id);
}
}
} else {
auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
command_buffer.GenerateDepopPrepareCommand(
voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count,
mix_info->buffer_offset, voice_info.was_playing);
}
if (voice_info.was_playing) {
return;
}
if (render_context.behavior->IsWaveBufferVer2Supported()) {
switch (voice_info.sample_format) {
case SampleFormat::PcmInt16:
command_buffer.GeneratePcmInt16Version2Command(
voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
channel);
break;
case SampleFormat::PcmFloat:
command_buffer.GeneratePcmFloatVersion2Command(
voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
channel);
break;
case SampleFormat::Adpcm:
command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state,
render_context.mix_buffer_count, channel);
break;
default:
LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
static_cast<u32>(voice_info.sample_format));
break;
}
} else {
switch (voice_info.sample_format) {
case SampleFormat::PcmInt16:
command_buffer.GeneratePcmInt16Version1Command(
voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
render_context.mix_buffer_count, channel);
break;
case SampleFormat::PcmFloat:
command_buffer.GeneratePcmFloatVersion1Command(
voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
render_context.mix_buffer_count, channel);
break;
case SampleFormat::Adpcm:
command_buffer.GenerateAdpcmVersion1Command(
voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
render_context.mix_buffer_count, channel);
break;
default:
LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
static_cast<u32>(voice_info.sample_format));
break;
}
}
}
void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
std::span<const f32> prev_mix_volumes,
const VoiceState& voice_state, s16 output_index,
const s16 buffer_count, const s16 input_index,
const s32 node_id) {
u8 precision{15};
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
precision = 23;
}
if (buffer_count > 8) {
const auto prev_samples{render_context.memory_pool_info->Translate(
CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))};
command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index,
output_index, mix_volumes, prev_mix_volumes,
prev_samples, precision);
} else {
for (s16 i = 0; i < buffer_count; i++, output_index++) {
const auto prev_samples{render_context.memory_pool_info->Translate(
CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))};
command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index,
mix_volumes[i], prev_mix_volumes[i], prev_samples,
precision);
}
}
}
void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel,
const s32 node_id) {
const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled};
const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()};
if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() &&
use_float_processing) {
command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state,
buffer_count, channel);
} else {
for (u32 i = 0; i < MaxBiquadFilters; i++) {
if (voice_info.biquads[i].enabled) {
command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state,
buffer_count, channel, i,
use_float_processing);
}
}
}
}
void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
u8 precision{15};
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
precision = 23;
}
for (s8 channel = 0; channel < voice_info.channel_count; channel++) {
const auto resource_id{voice_info.channel_resource_ids[channel]};
auto& voice_state{voice_context.GetDspSharedState(resource_id)};
auto& channel_resource{voice_context.GetChannelResource(resource_id)};
PerformanceDetailType detail_type{PerformanceDetailType::Invalid};
switch (voice_info.sample_format) {
case SampleFormat::PcmInt16:
detail_type = PerformanceDetailType::Unk1;
break;
case SampleFormat::PcmFloat:
detail_type = PerformanceDetailType::Unk10;
break;
default:
detail_type = PerformanceDetailType::Unk2;
break;
}
DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id,
detail_type);
GenerateDataSourceCommand(voice_info, voice_state, channel);
if (data_source_detail.initialized) {
command_buffer.GeneratePerformanceCommand(data_source_detail.node_id,
PerformanceState::Stop,
data_source_detail.performance_entry_address);
}
if (voice_info.was_playing) {
voice_info.prev_volume = 0.0f;
continue;
}
if (!voice_info.HasAnyConnection()) {
continue;
}
DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id,
PerformanceDetailType::Unk4);
GenerateBiquadFilterCommandForVoice(
voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id);
if (biquad_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
biquad_detail_aspect.node_id, PerformanceState::Stop,
biquad_detail_aspect.performance_entry_address);
}
DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice,
voice_info.node_id, PerformanceDetailType::Unk3);
command_buffer.GenerateVolumeRampCommand(
voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision);
if (volume_ramp_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
volume_ramp_detail_aspect.node_id, PerformanceState::Stop,
volume_ramp_detail_aspect.performance_entry_address);
}
voice_info.prev_volume = voice_info.volume;
if (voice_info.mix_id == UnusedMixId) {
if (voice_info.splitter_id != UnusedSplitterId) {
auto i{channel};
auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)};
while (destination != nullptr) {
if (destination->IsConfigured()) {
const auto mix_id{destination->GetMixId()};
if (mix_id < mix_context.GetCount() &&
static_cast<s32>(mix_id) != UnusedSplitterId) {
auto mix_info{mix_context.GetInfo(mix_id)};
GenerateVoiceMixCommand(
destination->GetMixVolume(), destination->GetMixVolumePrev(),
voice_state, mix_info->buffer_offset, mix_info->buffer_count,
render_context.mix_buffer_count + channel, voice_info.node_id);
destination->MarkAsNeedToUpdateInternalState();
}
}
i += voice_info.channel_count;
destination = splitter_context.GetDesintationData(voice_info.splitter_id, i);
}
}
} else {
DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice,
voice_info.node_id, PerformanceDetailType::Unk3);
auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes,
voice_state, mix_info->buffer_offset, mix_info->buffer_count,
render_context.mix_buffer_count + channel, voice_info.node_id);
if (volume_mix_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
volume_mix_detail_aspect.node_id, PerformanceState::Stop,
volume_mix_detail_aspect.performance_entry_address);
}
channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
}
voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled;
voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled;
}
}
void CommandGenerator::GenerateVoiceCommands() {
const auto voice_count{voice_context.GetCount()};
for (u32 i = 0; i < voice_count; i++) {
auto sorted_info{voice_context.GetSortedInfo(i)};
if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) {
continue;
}
EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id);
GenerateVoiceCommand(*sorted_info);
if (voice_entry_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id,
PerformanceState::Stop,
voice_entry_aspect.performance_entry_address);
}
}
splitter_context.UpdateInternalState();
}
void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset,
EffectInfoBase& effect_info, const s32 node_id) {
u8 precision{15};
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
precision = 23;
}
if (effect_info.IsEnabled()) {
const auto& parameter{
*reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())};
for (u32 i = 0; i < parameter.mix_count; i++) {
if (parameter.volumes[i] != 0.0f) {
command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i],
buffer_offset + parameter.outputs[i],
buffer_offset, parameter.volumes[i], precision);
}
}
}
}
void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
const s32 node_id) {
command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset);
}
void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
const s32 node_id,
const bool long_size_pre_delay_supported) {
command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset,
long_size_pre_delay_supported);
}
void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset,
EffectInfoBase& effect_info,
const s32 node_id) {
command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset);
}
void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
const s32 node_id) {
if (effect_info.IsEnabled()) {
effect_info.GetWorkbuffer(0);
effect_info.GetWorkbuffer(1);
}
if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
const auto& parameter{
*reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
auto channel_index{parameter.mix_buffer_count - 1};
u32 write_offset{0};
for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
auto new_update_count{command_header.sample_count + write_offset};
const auto update_count{channel_index > 0 ? 0 : new_update_count};
command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i],
parameter.outputs[i], buffer_offset, update_count,
parameter.count_max, write_offset);
write_offset = new_update_count;
}
}
}
void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset,
EffectInfoBase& effect_info,
const s32 node_id) {
const auto& parameter{
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
if (effect_info.IsEnabled()) {
bool needs_init{false};
switch (parameter.state) {
case EffectInfoBase::ParameterState::Initialized:
needs_init = true;
break;
case EffectInfoBase::ParameterState::Updating:
case EffectInfoBase::ParameterState::Updated:
if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) {
needs_init = false;
} else {
needs_init = parameter.state == EffectInfoBase::ParameterState::Updating;
}
break;
default:
LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}",
static_cast<u32>(parameter.state));
break;
}
for (s8 channel = 0; channel < parameter.channel_count; channel++) {
command_buffer.GenerateBiquadFilterCommand(
node_id, effect_info, buffer_offset, channel, needs_init,
render_context.behavior->UseBiquadFilterFloatProcessing());
}
} else {
for (s8 channel = 0; channel < parameter.channel_count; channel++) {
command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset,
channel);
}
}
}
void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset,
EffectInfoBase& effect_info,
const s32 node_id,
const u32 effect_index) {
const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())};
if (render_context.behavior->IsEffectInfoVersion2Supported()) {
const auto& parameter{
*reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())};
const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(
&effect_context.GetDspSharedResultState(effect_index))};
command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state,
state, effect_info.IsEnabled(),
effect_info.GetWorkbuffer(-1));
} else {
const auto& parameter{
*reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())};
command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state,
effect_info.IsEnabled(),
effect_info.GetWorkbuffer(-1));
}
}
void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
const s32 node_id) {
if (effect_info.IsEnabled()) {
effect_info.GetWorkbuffer(0);
}
if (effect_info.GetSendBuffer()) {
const auto& parameter{
*reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
auto channel_index{parameter.mix_buffer_count - 1};
u32 write_offset{0};
for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
auto new_update_count{command_header.sample_count + write_offset};
const auto update_count{channel_index > 0 ? 0 : new_update_count};
command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i],
parameter.outputs[i], buffer_offset, update_count,
parameter.count_max, write_offset);
write_offset = new_update_count;
}
}
}
void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset,
EffectInfoBase& effect_info, const s32 node_id) {
command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id);
}
void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) {
const auto effect_count{effect_context.GetCount()};
for (u32 i = 0; i < effect_count; i++) {
const auto effect_index{mix_info.effect_order_buffer[i]};
if (effect_index == -1) {
break;
}
auto& effect_info = effect_context.GetInfo(effect_index);
if (effect_info.ShouldSkip()) {
continue;
}
const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix
: PerformanceEntryType::SubMix};
switch (effect_info.GetType()) {
case EffectInfoBase::Type::Mix: {
DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk5);
GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (mix_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
mix_detail_aspect.node_id, PerformanceState::Stop,
mix_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::Aux: {
DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk7);
GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (aux_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
aux_detail_aspect.node_id, PerformanceState::Stop,
aux_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::Delay: {
DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk6);
GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (delay_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
delay_detail_aspect.node_id, PerformanceState::Stop,
delay_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::Reverb: {
DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk8);
GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
render_context.behavior->IsLongSizePreDelaySupported());
if (reverb_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
reverb_detail_aspect.node_id, PerformanceState::Stop,
reverb_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::I3dl2Reverb: {
DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk9);
GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (i3dl2_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
i3dl2_detail_aspect.node_id, PerformanceState::Stop,
i3dl2_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::BiquadFilter: {
DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk4);
GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info,
mix_info.node_id);
if (biquad_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
biquad_detail_aspect.node_id, PerformanceState::Stop,
biquad_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::LightLimiter: {
DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk11);
GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
effect_index);
if (light_limiter_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
light_limiter_detail_aspect.node_id, PerformanceState::Stop,
light_limiter_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::Capture: {
DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk12);
GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (capture_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
capture_detail_aspect.node_id, PerformanceState::Stop,
capture_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::Compressor: {
DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk13);
GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (capture_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
capture_detail_aspect.node_id, PerformanceState::Stop,
capture_detail_aspect.performance_entry_address);
}
} break;
default:
LOG_ERROR(Service_Audio, "Invalid effect type {}",
static_cast<u32>(effect_info.GetType()));
break;
}
effect_info.UpdateForCommandGeneration();
}
}
void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
u8 precision{15};
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
precision = 23;
}
if (!mix_info.HasAnyConnection()) {
return;
}
if (mix_info.dst_mix_id == UnusedMixId) {
if (mix_info.dst_splitter_id != UnusedSplitterId) {
s16 dest_id{0};
auto destination{
splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)};
while (destination != nullptr) {
if (destination->IsConfigured()) {
auto splitter_mix_id{destination->GetMixId()};
if (splitter_mix_id < mix_context.GetCount()) {
auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)};
const s16 input_index{static_cast<s16>(mix_info.buffer_offset +
(dest_id % mix_info.buffer_count))};
for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) {
auto volume{mix_info.volume * destination->GetMixVolume(i)};
if (volume != 0.0f) {
command_buffer.GenerateMixCommand(
mix_info.node_id, input_index,
splitter_mix_info->buffer_offset + i, mix_info.buffer_offset,
volume, precision);
}
}
}
}
dest_id++;
destination =
splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id);
}
}
} else {
auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)};
for (s16 i = 0; i < mix_info.buffer_count; i++) {
for (s16 j = 0; j < dest_mix_info->buffer_count; j++) {
auto volume{mix_info.volume * mix_info.mix_volumes[i][j]};
if (volume != 0.0f) {
command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i,
dest_mix_info->buffer_offset + j,
mix_info.buffer_offset, volume, precision);
}
}
}
}
}
void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) {
command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info,
render_context.depop_buffer);
GenerateEffectCommand(mix_info);
DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id,
PerformanceDetailType::Unk5);
GenerateMixCommands(mix_info);
if (mix_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop,
mix_detail_aspect.performance_entry_address);
}
}
void CommandGenerator::GenerateSubMixCommands() {
const auto submix_count{mix_context.GetCount()};
for (s32 i = 0; i < submix_count; i++) {
auto sorted_info{mix_context.GetSortedInfo(i)};
if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) {
continue;
}
EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id);
GenerateSubMixCommand(*sorted_info);
if (submix_entry_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
submix_entry_aspect.node_id, PerformanceState::Stop,
submix_entry_aspect.performance_entry_address);
}
}
}
void CommandGenerator::GenerateFinalMixCommand() {
auto& final_mix_info{*mix_context.GetFinalMixInfo()};
command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info,
render_context.depop_buffer);
GenerateEffectCommand(final_mix_info);
u8 precision{15};
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
precision = 23;
}
for (s16 i = 0; i < final_mix_info.buffer_count; i++) {
DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id,
PerformanceDetailType::Unk3);
command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset,
i, final_mix_info.volume, precision);
if (volume_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop,
volume_aspect.performance_entry_address);
}
}
}
void CommandGenerator::GenerateFinalMixCommands() {
auto final_mix_info{mix_context.GetFinalMixInfo()};
EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id);
GenerateFinalMixCommand();
if (final_mix_entry.initialized) {
command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop,
final_mix_entry.performance_entry_address);
}
}
void CommandGenerator::GenerateSinkCommands() {
const auto sink_count{sink_context.GetCount()};
for (u32 i = 0; i < sink_count; i++) {
auto sink_info{sink_context.GetInfo(i)};
if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) {
auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())};
if (command_header.sample_rate != TargetSampleRate &&
state->upsampler_info == nullptr) {
auto device_state{sink_info->GetDeviceState()};
device_state->upsampler_info = render_context.upsampler_manager->Allocate();
}
EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink,
sink_info->GetNodeId());
auto final_mix{mix_context.GetFinalMixInfo()};
GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
if (device_sink_entry.initialized) {
command_buffer.GeneratePerformanceCommand(
device_sink_entry.node_id, PerformanceState::Stop,
device_sink_entry.performance_entry_address);
}
}
}
for (u32 i = 0; i < sink_count; i++) {
auto sink_info{sink_context.GetInfo(i)};
if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) {
EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink,
sink_info->GetNodeId());
auto final_mix{mix_context.GetFinalMixInfo()};
GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
if (circular_buffer_entry.initialized) {
command_buffer.GeneratePerformanceCommand(
circular_buffer_entry.node_id, PerformanceState::Stop,
circular_buffer_entry.performance_entry_address);
}
}
}
}
void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
if (sink_info.ShouldSkip()) {
return;
}
switch (sink_info.GetType()) {
case SinkInfoBase::Type::DeviceSink:
GenerateDeviceSinkCommand(buffer_offset, sink_info);
break;
case SinkInfoBase::Type::CircularBufferSink:
command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info,
buffer_offset);
break;
default:
LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType()));
break;
}
sink_info.UpdateForCommandGeneration();
}
void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
auto& parameter{
*reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
if (render_context.channels == 2 && parameter.downmix_enabled) {
command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs,
buffer_offset, parameter.downmix_coeff);
}
if (state.upsampler_info != nullptr) {
command_buffer.GenerateUpsampleCommand(
InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count,
parameter.inputs, command_header.buffer_count, command_header.sample_count,
command_header.sample_rate);
}
command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info,
render_context.session_id,
command_header.samples_buffer);
}
void CommandGenerator::GeneratePerformanceCommand(
s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) {
command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses);
}
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,349 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/renderer/command/commands.h"
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_types.h"
namespace AudioCore {
struct AudioRendererSystemContext;
namespace AudioRenderer {
class CommandBuffer;
struct CommandListHeader;
class VoiceContext;
class MixContext;
class EffectContext;
class SplitterContext;
class SinkContext;
class BehaviorInfo;
class VoiceInfo;
struct VoiceState;
class MixInfo;
class SinkInfoBase;
/**
* Generates all commands to build up a command list, which are sent to the AudioRender for
* processing.
*/
class CommandGenerator {
public:
explicit CommandGenerator(CommandBuffer& command_buffer,
const CommandListHeader& command_list_header,
const AudioRendererSystemContext& render_context,
VoiceContext& voice_context, MixContext& mix_context,
EffectContext& effect_context, SinkContext& sink_context,
SplitterContext& splitter_context,
PerformanceManager* performance_manager);
/**
* Calculate the buffer size needed for commands.
*
* @param behavior - Used to check what features are enabled.
* @param params - Input rendering parameters for numbers of voices/mixes/sinks etc.
*/
static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior,
const AudioRendererParameterInternal& params) {
u64 size{0};
// Effects
size += params.effects * sizeof(EffectInfoBase);
// Voices
u64 voice_size{0};
if (behavior.IsWaveBufferVer2Supported()) {
voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command),
sizeof(PcmInt16DataSourceVersion2Command)),
sizeof(PcmFloatDataSourceVersion2Command));
} else {
voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command),
sizeof(PcmInt16DataSourceVersion1Command)),
sizeof(PcmFloatDataSourceVersion1Command));
}
voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters;
voice_size += sizeof(VolumeRampCommand);
voice_size += sizeof(MixRampGroupedCommand);
size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size);
// Sub mixes
size += sizeof(DepopForMixBuffersCommand) +
(sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers;
// Final mix
size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers;
// Splitters
size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers;
// Sinks
size +=
params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand));
// Performance
size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 +
PerformanceManager::MaxDetailEntries) *
sizeof(PerformanceCommand);
return size;
}
/**
* Get the current command buffer used to generate commands.
*
* @return The command buffer.
*/
CommandBuffer& GetCommandBuffer() {
return command_buffer;
}
/**
* Get the current performance manager,
*
* @return The performance manager. May be nullptr.
*/
PerformanceManager* GetPerformanceManager() {
return performance_manager;
}
/**
* Generate a data source command.
* These are the basis for all audio output.
*
* @param voice_info - Generate the command from this voice.
* @param voice_state - State used by the AudioRenderer across calls.
* @param channel - Channel index to generate the command into.
*/
void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state,
s8 channel);
/**
* Generate voice mixing commands.
* These are used to mix buffers together, to mix one input to many outputs,
* and also used as copy commands to move data around and prevent it being accidentally
* overwritten, e.g by another data source command into the same channel.
*
* @param mix_volumes - Current volumes of the mix.
* @param prev_mix_volumes - Previous volumes of the mix.
* @param voice_state - State used by the AudioRenderer across calls.
* @param output_index - Output mix buffer index.
* @param buffer_count - Number of active mix buffers.
* @param input_index - Input mix buffer index.
* @param node_id - Node id of the voice this command is generated for.
*/
void GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
std::span<const f32> prev_mix_volumes,
const VoiceState& voice_state, s16 output_index, s16 buffer_count,
s16 input_index, s32 node_id);
/**
* Generate a biquad filter command for a voice.
*
* @param voice_info - Voice info this command is generated from.
* @param voice_state - State used by the AudioRenderer across calls.
* @param buffer_count - Number of active mix buffers.
* @param channel - Channel index of this command.
* @param node_id - Node id of the voice this command is generated for.
*/
void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state,
s16 buffer_count, s8 channel, s32 node_id);
/**
* Generate commands for a voice.
* Includes a data source, biquad filter, volume and mixing.
*
* @param voice_info - Voice info these commands are generated from.
*/
void GenerateVoiceCommand(VoiceInfo& voice_info);
/**
* Generate commands for all voices.
*/
void GenerateVoiceCommands();
/**
* Generate a mixing command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - BufferMixer effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base,
s32 node_id);
/**
* Generate a delay effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Delay effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id);
/**
* Generate a reverb effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Reverb effect info.
* @param node_id - Node id of the mix this command is generated for.
* @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts.
*/
void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id,
bool long_size_pre_delay_supported);
/**
* Generate an I3DL2 reverb effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - I3DL2Reverb effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id);
/**
* Generate an aux effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Aux effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
/**
* Generate a biquad filter effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Aux effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id);
/**
* Generate a light limiter effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Limiter effect info.
* @param node_id - Node id of the mix this command is generated for.
* @param effect_index - Index for the statistics state.
*/
void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id, u32 effect_index);
/**
* Generate a capture effect command.
* Writes a mix buffer back to game memory.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Capture effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
/**
* Generate a compressor effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Compressor effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
const s32 node_id);
/**
* Generate all effect commands for a mix.
*
* @param mix_info - Mix to generate effects from.
*/
void GenerateEffectCommand(MixInfo& mix_info);
/**
* Generate all mix commands.
*
* @param mix_info - Mix to generate effects from.
*/
void GenerateMixCommands(MixInfo& mix_info);
/**
* Generate a submix command.
* Generates all effects and all mixing commands.
*
* @param mix_info - Mix to generate effects from.
*/
void GenerateSubMixCommand(MixInfo& mix_info);
/**
* Generate all submix command.
*/
void GenerateSubMixCommands();
/**
* Generate the final mix.
*/
void GenerateFinalMixCommand();
/**
* Generate the final mix commands.
*/
void GenerateFinalMixCommands();
/**
* Generate all sink commands.
*/
void GenerateSinkCommands();
/**
* Generate a sink command.
* Sends samples out to the backend, or a game-supplied circular buffer.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param sink_info - Sink info to generate the commands from.
*/
void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
/**
* Generate a device sink command.
* Sends samples out to the backend.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param sink_info - Sink info to generate the commands from.
*/
void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
/**
* Generate a performance command.
* Used to report performance metrics of the AudioRenderer back to the game.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param sink_info - Sink info to generate the commands from.
*/
void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
const PerformanceEntryAddresses& entry_addresses);
private:
/// Commands will be written by this buffer
CommandBuffer& command_buffer;
/// Header information for the commands generated
const CommandListHeader& command_header;
/// Various things to control generation
const AudioRendererSystemContext& render_context;
/// Used for generating voices
VoiceContext& voice_context;
/// Used for generating mixes
MixContext& mix_context;
/// Used for generating effects
EffectContext& effect_context;
/// Used for generating sinks
SinkContext& sink_context;
/// Used for generating submixes
SplitterContext& splitter_context;
/// Used for generating performance
PerformanceManager* performance_manager;
};
} // namespace AudioRenderer
} // namespace AudioCore

View File

@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/common/common.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
struct CommandListHeader {
u64 buffer_size;
u32 command_count;
std::span<s32> samples_buffer;
s16 buffer_count;
u32 sample_count;
u32 sample_rate;
};
} // namespace AudioCore::AudioRenderer

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,254 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "audio_core/renderer/command/commands.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
/**
* Estimate the processing time required for all commands.
*/
class ICommandProcessingTimeEstimator {
public:
virtual ~ICommandProcessingTimeEstimator() = default;
virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0;
virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0;
virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0;
virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0;
virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0;
virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0;
virtual u32 Estimate(const VolumeCommand& command) const = 0;
virtual u32 Estimate(const VolumeRampCommand& command) const = 0;
virtual u32 Estimate(const BiquadFilterCommand& command) const = 0;
virtual u32 Estimate(const MixCommand& command) const = 0;
virtual u32 Estimate(const MixRampCommand& command) const = 0;
virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0;
virtual u32 Estimate(const DepopPrepareCommand& command) const = 0;
virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0;
virtual u32 Estimate(const DelayCommand& command) const = 0;
virtual u32 Estimate(const UpsampleCommand& command) const = 0;
virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0;
virtual u32 Estimate(const AuxCommand& command) const = 0;
virtual u32 Estimate(const DeviceSinkCommand& command) const = 0;
virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0;
virtual u32 Estimate(const ReverbCommand& command) const = 0;
virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0;
virtual u32 Estimate(const PerformanceCommand& command) const = 0;
virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0;
virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0;
virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0;
virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0;
virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0;
virtual u32 Estimate(const CaptureCommand& command) const = 0;
virtual u32 Estimate(const CompressorCommand& command) const = 0;
};
class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator {
public:
CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_)
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
u32 Estimate(const VolumeCommand& command) const override;
u32 Estimate(const VolumeRampCommand& command) const override;
u32 Estimate(const BiquadFilterCommand& command) const override;
u32 Estimate(const MixCommand& command) const override;
u32 Estimate(const MixRampCommand& command) const override;
u32 Estimate(const MixRampGroupedCommand& command) const override;
u32 Estimate(const DepopPrepareCommand& command) const override;
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
u32 Estimate(const DelayCommand& command) const override;
u32 Estimate(const UpsampleCommand& command) const override;
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
u32 Estimate(const AuxCommand& command) const override;
u32 Estimate(const DeviceSinkCommand& command) const override;
u32 Estimate(const CircularBufferSinkCommand& command) const override;
u32 Estimate(const ReverbCommand& command) const override;
u32 Estimate(const I3dl2ReverbCommand& command) const override;
u32 Estimate(const PerformanceCommand& command) const override;
u32 Estimate(const ClearMixBufferCommand& command) const override;
u32 Estimate(const CopyMixBufferCommand& command) const override;
u32 Estimate(const LightLimiterVersion1Command& command) const override;
u32 Estimate(const LightLimiterVersion2Command& command) const override;
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
u32 Estimate(const CaptureCommand& command) const override;
u32 Estimate(const CompressorCommand& command) const override;
private:
u32 sample_count{};
u32 buffer_count{};
};
class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator {
public:
CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_)
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
u32 Estimate(const VolumeCommand& command) const override;
u32 Estimate(const VolumeRampCommand& command) const override;
u32 Estimate(const BiquadFilterCommand& command) const override;
u32 Estimate(const MixCommand& command) const override;
u32 Estimate(const MixRampCommand& command) const override;
u32 Estimate(const MixRampGroupedCommand& command) const override;
u32 Estimate(const DepopPrepareCommand& command) const override;
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
u32 Estimate(const DelayCommand& command) const override;
u32 Estimate(const UpsampleCommand& command) const override;
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
u32 Estimate(const AuxCommand& command) const override;
u32 Estimate(const DeviceSinkCommand& command) const override;
u32 Estimate(const CircularBufferSinkCommand& command) const override;
u32 Estimate(const ReverbCommand& command) const override;
u32 Estimate(const I3dl2ReverbCommand& command) const override;
u32 Estimate(const PerformanceCommand& command) const override;
u32 Estimate(const ClearMixBufferCommand& command) const override;
u32 Estimate(const CopyMixBufferCommand& command) const override;
u32 Estimate(const LightLimiterVersion1Command& command) const override;
u32 Estimate(const LightLimiterVersion2Command& command) const override;
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
u32 Estimate(const CaptureCommand& command) const override;
u32 Estimate(const CompressorCommand& command) const override;
private:
u32 sample_count{};
u32 buffer_count{};
};
class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator {
public:
CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_)
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
u32 Estimate(const VolumeCommand& command) const override;
u32 Estimate(const VolumeRampCommand& command) const override;
u32 Estimate(const BiquadFilterCommand& command) const override;
u32 Estimate(const MixCommand& command) const override;
u32 Estimate(const MixRampCommand& command) const override;
u32 Estimate(const MixRampGroupedCommand& command) const override;
u32 Estimate(const DepopPrepareCommand& command) const override;
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
u32 Estimate(const DelayCommand& command) const override;
u32 Estimate(const UpsampleCommand& command) const override;
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
u32 Estimate(const AuxCommand& command) const override;
u32 Estimate(const DeviceSinkCommand& command) const override;
u32 Estimate(const CircularBufferSinkCommand& command) const override;
u32 Estimate(const ReverbCommand& command) const override;
u32 Estimate(const I3dl2ReverbCommand& command) const override;
u32 Estimate(const PerformanceCommand& command) const override;
u32 Estimate(const ClearMixBufferCommand& command) const override;
u32 Estimate(const CopyMixBufferCommand& command) const override;
u32 Estimate(const LightLimiterVersion1Command& command) const override;
u32 Estimate(const LightLimiterVersion2Command& command) const override;
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
u32 Estimate(const CaptureCommand& command) const override;
u32 Estimate(const CompressorCommand& command) const override;
private:
u32 sample_count{};
u32 buffer_count{};
};
class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator {
public:
CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_)
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
u32 Estimate(const VolumeCommand& command) const override;
u32 Estimate(const VolumeRampCommand& command) const override;
u32 Estimate(const BiquadFilterCommand& command) const override;
u32 Estimate(const MixCommand& command) const override;
u32 Estimate(const MixRampCommand& command) const override;
u32 Estimate(const MixRampGroupedCommand& command) const override;
u32 Estimate(const DepopPrepareCommand& command) const override;
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
u32 Estimate(const DelayCommand& command) const override;
u32 Estimate(const UpsampleCommand& command) const override;
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
u32 Estimate(const AuxCommand& command) const override;
u32 Estimate(const DeviceSinkCommand& command) const override;
u32 Estimate(const CircularBufferSinkCommand& command) const override;
u32 Estimate(const ReverbCommand& command) const override;
u32 Estimate(const I3dl2ReverbCommand& command) const override;
u32 Estimate(const PerformanceCommand& command) const override;
u32 Estimate(const ClearMixBufferCommand& command) const override;
u32 Estimate(const CopyMixBufferCommand& command) const override;
u32 Estimate(const LightLimiterVersion1Command& command) const override;
u32 Estimate(const LightLimiterVersion2Command& command) const override;
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
u32 Estimate(const CaptureCommand& command) const override;
u32 Estimate(const CompressorCommand& command) const override;
private:
u32 sample_count{};
u32 buffer_count{};
};
class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator {
public:
CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_)
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
u32 Estimate(const VolumeCommand& command) const override;
u32 Estimate(const VolumeRampCommand& command) const override;
u32 Estimate(const BiquadFilterCommand& command) const override;
u32 Estimate(const MixCommand& command) const override;
u32 Estimate(const MixRampCommand& command) const override;
u32 Estimate(const MixRampGroupedCommand& command) const override;
u32 Estimate(const DepopPrepareCommand& command) const override;
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
u32 Estimate(const DelayCommand& command) const override;
u32 Estimate(const UpsampleCommand& command) const override;
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
u32 Estimate(const AuxCommand& command) const override;
u32 Estimate(const DeviceSinkCommand& command) const override;
u32 Estimate(const CircularBufferSinkCommand& command) const override;
u32 Estimate(const ReverbCommand& command) const override;
u32 Estimate(const I3dl2ReverbCommand& command) const override;
u32 Estimate(const PerformanceCommand& command) const override;
u32 Estimate(const ClearMixBufferCommand& command) const override;
u32 Estimate(const CopyMixBufferCommand& command) const override;
u32 Estimate(const LightLimiterVersion1Command& command) const override;
u32 Estimate(const LightLimiterVersion2Command& command) const override;
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
u32 Estimate(const CaptureCommand& command) const override;
u32 Estimate(const CompressorCommand& command) const override;
private:
u32 sample_count{};
u32 buffer_count{};
};
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "audio_core/renderer/command/data_source/adpcm.h"
#include "audio_core/renderer/command/data_source/pcm_float.h"
#include "audio_core/renderer/command/data_source/pcm_int16.h"
#include "audio_core/renderer/command/effect/aux_.h"
#include "audio_core/renderer/command/effect/biquad_filter.h"
#include "audio_core/renderer/command/effect/capture.h"
#include "audio_core/renderer/command/effect/compressor.h"
#include "audio_core/renderer/command/effect/delay.h"
#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
#include "audio_core/renderer/command/effect/light_limiter.h"
#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
#include "audio_core/renderer/command/effect/reverb.h"
#include "audio_core/renderer/command/icommand.h"
#include "audio_core/renderer/command/mix/clear_mix.h"
#include "audio_core/renderer/command/mix/copy_mix.h"
#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
#include "audio_core/renderer/command/mix/depop_prepare.h"
#include "audio_core/renderer/command/mix/mix.h"
#include "audio_core/renderer/command/mix/mix_ramp.h"
#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
#include "audio_core/renderer/command/mix/volume.h"
#include "audio_core/renderer/command/mix/volume_ramp.h"
#include "audio_core/renderer/command/performance/performance.h"
#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
#include "audio_core/renderer/command/resample/upsample.h"
#include "audio_core/renderer/command/sink/circular_buffer.h"
#include "audio_core/renderer/command/sink/device.h"

View File

@@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/data_source/adpcm.h"
#include "audio_core/renderer/command/data_source/decode.h"
namespace AudioCore::AudioRenderer {
void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample "
"rate {} target sample rate {} src quality {}\n",
output_index, sample_rate, processor.target_sample_rate, src_quality);
}
void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::Adpcm},
.output{out_buffer},
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
.wave_buffers{wave_buffers},
.channel{0},
.channel_count{1},
.src_quality{src_quality},
.pitch{pitch},
.source_sample_rate{sample_rate},
.target_sample_rate{processor.target_sample_rate},
.sample_count{processor.sample_count},
.data_address{data_address},
.data_size{data_size},
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
};
DecodeFromWaveBuffers(*processor.memory, args);
}
bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample "
"rate {} target sample rate {} src quality {}\n",
output_index, sample_rate, processor.target_sample_rate, src_quality);
}
void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::Adpcm},
.output{out_buffer},
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
.wave_buffers{wave_buffers},
.channel{0},
.channel_count{1},
.src_quality{src_quality},
.pitch{pitch},
.source_sample_rate{sample_rate},
.target_sample_rate{processor.target_sample_rate},
.sample_count{processor.sample_count},
.data_address{data_address},
.data_size{data_size},
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
};
DecodeFromWaveBuffers(*processor.memory, args);
}
bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <string>
#include "audio_core/common/common.h"
#include "audio_core/common/wave_buffer.h"
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers
* into the output_index mix buffer.
*/
struct AdpcmDataSourceVersion1Command : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
/// Pitch used for sample rate conversion
f32 pitch;
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
/// Voice state, updated each call and written back to game
CpuAddr voice_state;
/// Coefficients data address
CpuAddr data_address;
/// Coefficients data size
u64 data_size;
};
/**
* AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers
* into the output_index mix buffer.
*/
struct AdpcmDataSourceVersion2Command : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
/// Pitch used for sample rate conversion
f32 pitch;
/// Target channel to read within the wavebuffer
s8 channel_index;
/// Number of channels within the wavebuffer
s8 channel_count;
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
/// Voice state, updated each call and written back to game
CpuAddr voice_state;
/// Coefficients data address
CpuAddr data_address;
/// Coefficients data size
u64 data_size;
};
} // namespace AudioCore::AudioRenderer

View File

@@ -0,0 +1,428 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <vector>
#include "audio_core/renderer/command/data_source/decode.h"
#include "audio_core/renderer/command/resample/resample.h"
#include "common/fixed_point.h"
#include "common/logging/log.h"
#include "core/memory.h"
namespace AudioCore::AudioRenderer {
constexpr u32 TempBufferSize = 0x3F00;
constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
/**
* Decode PCM data. Only s16 or f32 is supported.
*
* @tparam T - Type to decode. Only s16 and f32 are supported.
* @param memory - Core memory for reading samples.
* @param out_buffer - Output mix buffer to receive the samples.
* @param req - Information for how to decode.
* @return Number of samples decoded.
*/
template <typename T>
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
const DecodeArg& req) {
constexpr s32 min{std::numeric_limits<s16>::min()};
constexpr s32 max{std::numeric_limits<s16>::max()};
if (req.buffer == 0 || req.buffer_size == 0) {
return 0;
}
if (req.start_offset >= req.end_offset) {
return 0;
}
auto samples_to_decode{
std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)};
u32 channel_count{static_cast<u32>(req.channel_count)};
switch (req.channel_count) {
default: {
const VAddr source{req.buffer +
(((req.start_offset + req.offset) * channel_count) * sizeof(T))};
const u64 size{channel_count * samples_to_decode};
const u64 size_bytes{size * sizeof(T)};
std::vector<T> samples(size);
memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
if constexpr (std::is_floating_point_v<T>) {
for (u32 i = 0; i < samples_to_decode; i++) {
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
std::numeric_limits<s16>::max())};
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
}
} else {
for (u32 i = 0; i < samples_to_decode; i++) {
out_buffer[i] = samples[i * channel_count + req.target_channel];
}
}
} break;
case 1:
if (req.target_channel != 0) {
LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}",
req.target_channel);
return 0;
}
const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
std::vector<T> samples(samples_to_decode);
memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
if constexpr (std::is_floating_point_v<T>) {
for (u32 i = 0; i < samples_to_decode; i++) {
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
std::numeric_limits<s16>::max())};
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
}
} else {
std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
}
break;
}
return samples_to_decode;
}
/**
* Decode ADPCM data.
*
* @param memory - Core memory for reading samples.
* @param out_buffer - Output mix buffer to receive the samples.
* @param req - Information for how to decode.
* @return Number of samples decoded.
*/
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
const DecodeArg& req) {
constexpr u32 SamplesPerFrame{14};
constexpr u32 NibblesPerFrame{16};
if (req.buffer == 0 || req.buffer_size == 0) {
return 0;
}
if (req.end_offset < req.start_offset) {
return 0;
}
auto end{(req.end_offset % SamplesPerFrame) +
NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
if (req.end_offset % SamplesPerFrame) {
end += 3;
} else {
end += 1;
}
if (req.buffer_size < end / 2) {
return 0;
}
auto samples_to_process{
std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
auto samples_to_read{samples_to_process};
auto start_pos{req.start_offset + req.offset};
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
samples_remaining_in_frame};
if (samples_remaining_in_frame) {
position_in_frame += 2;
}
const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
std::vector<u8> wavebuffer(size);
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
wavebuffer.size());
auto context{req.adpcm_context};
auto header{context->header};
u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
u8 scale{static_cast<u8>(header & 0xFU)};
s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
auto yn0{context->yn0};
auto yn1{context->yn1};
static constexpr std::array<s32, 16> Steps{
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
};
const auto decode_sample = [&](const s32 code) -> s16 {
const auto xn = code * (1 << scale);
const auto prediction = coeff0 * yn0 + coeff1 * yn1;
const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
yn1 = yn0;
yn0 = static_cast<s16>(saturated);
return yn0;
};
u32 read_index{0};
u32 write_index{0};
while (samples_to_read > 0) {
// Are we at a new frame?
if ((position_in_frame % NibblesPerFrame) == 0) {
header = wavebuffer[read_index++];
coeff_index = (header >> 4) & 0xF;
scale = header & 0xF;
coeff0 = req.coefficients[coeff_index * 2 + 0];
coeff1 = req.coefficients[coeff_index * 2 + 1];
position_in_frame += 2;
// Can we consume all of this frame's samples?
if (samples_to_read >= SamplesPerFrame) {
// Can grab all samples until the next header
for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
auto code1{Steps[wavebuffer[read_index] & 0xF]};
read_index++;
out_buffer[write_index++] = decode_sample(code0);
out_buffer[write_index++] = decode_sample(code1);
}
position_in_frame += SamplesPerFrame;
samples_to_read -= SamplesPerFrame;
continue;
}
}
// Decode a single sample
auto code{wavebuffer[read_index]};
if (position_in_frame & 1) {
code &= 0xF;
read_index++;
} else {
code >>= 4;
}
out_buffer[write_index++] = decode_sample(Steps[code]);
position_in_frame++;
samples_to_read--;
}
context->header = header;
context->yn0 = yn0;
context->yn1 = yn1;
return samples_to_process;
}
/**
* Decode implementation.
* Decode wavebuffers according to the given args.
*
* @param memory - Core memory to read data from.
* @param args - The wavebuffer data, and information for how to decode it.
*/
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
auto& voice_state{*args.voice_state};
auto remaining_sample_count{args.sample_count};
auto fraction{voice_state.fraction};
const auto sample_rate_ratio{
(Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
args.pitch};
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
if (size_required < 0) {
return;
}
auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
return;
}
auto max_remaining_sample_count{
((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio)
.to_uint_floor()};
max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count);
auto wavebuffers_consumed{voice_state.wave_buffers_consumed};
auto wavebuffer_index{voice_state.wave_buffer_index};
auto played_sample_count{voice_state.played_sample_count};
bool is_buffer_starved{false};
u32 offset{voice_state.offset};
auto output_buffer{args.output};
std::vector<s16> temp_buffer(TempBufferSize, 0);
while (remaining_sample_count > 0) {
const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};
const auto samples_to_read{
(fraction + samples_to_write * sample_rate_ratio).to_uint_floor()};
u32 temp_buffer_pos{0};
if (!args.IsVoicePitchAndSrcSkippedSupported) {
for (u32 i = 0; i < pitch; i++) {
temp_buffer[i] = voice_state.sample_history[i];
}
temp_buffer_pos = pitch;
}
u32 samples_read{0};
while (samples_read < samples_to_read) {
if (wavebuffer_index >= MaxWaveBuffers) {
LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
wavebuffer_index = 0;
voice_state.wave_buffer_valid.fill(false);
wavebuffers_consumed = MaxWaveBuffers;
}
if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
is_buffer_starved = true;
break;
}
auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
wavebuffer.context != 0) {
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
wavebuffer.context_size);
}
auto start_offset{wavebuffer.start_offset};
auto end_offset{wavebuffer.end_offset};
if (wavebuffer.loop && voice_state.loop_count > 0 &&
wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
start_offset = wavebuffer.loop_start_offset;
end_offset = wavebuffer.loop_end_offset;
}
DecodeArg decode_arg{.buffer{wavebuffer.buffer},
.buffer_size{wavebuffer.buffer_size},
.start_offset{start_offset},
.end_offset{end_offset},
.channel_count{args.channel_count},
.coefficients{},
.adpcm_context{nullptr},
.target_channel{args.channel},
.offset{offset},
.samples_to_read{samples_to_read - samples_read}};
s32 samples_decoded{0};
switch (args.sample_format) {
case SampleFormat::PcmInt16:
samples_decoded = DecodePcm<s16>(
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
decode_arg);
break;
case SampleFormat::PcmFloat:
samples_decoded = DecodePcm<f32>(
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
decode_arg);
break;
case SampleFormat::Adpcm: {
decode_arg.adpcm_context = &voice_state.adpcm_context;
memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
samples_decoded = DecodeAdpcm(
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
decode_arg);
} break;
default:
LOG_ERROR(Service_Audio, "Invalid sample format to decode {}",
static_cast<u32>(args.sample_format));
samples_decoded = 0;
break;
}
played_sample_count += samples_decoded;
samples_read += samples_decoded;
temp_buffer_pos += samples_decoded;
offset += samples_decoded;
if (samples_decoded == 0 || offset >= end_offset - start_offset) {
offset = 0;
if (!wavebuffer.loop) {
voice_state.wave_buffer_valid[wavebuffer_index] = false;
voice_state.loop_count = 0;
if (wavebuffer.stream_ended) {
played_sample_count = 0;
}
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
wavebuffers_consumed++;
} else {
voice_state.loop_count++;
if (wavebuffer.loop_count > 0 &&
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
voice_state.wave_buffer_valid[wavebuffer_index] = false;
voice_state.loop_count = 0;
if (wavebuffer.stream_ended) {
played_sample_count = 0;
}
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
wavebuffers_consumed++;
}
if (samples_decoded == 0) {
is_buffer_starved = true;
break;
}
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
played_sample_count = 0;
}
}
}
}
if (args.IsVoicePitchAndSrcSkippedSupported) {
if (samples_read > output_buffer.size()) {
LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!");
}
for (u32 i = 0; i < samples_read; i++) {
output_buffer[i] = temp_buffer[i];
}
} else {
std::memset(&temp_buffer[temp_buffer_pos], 0,
(samples_to_read - samples_read) * sizeof(s16));
Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write,
args.src_quality);
std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read],
pitch * sizeof(s16));
}
remaining_sample_count -= samples_to_write;
if (remaining_sample_count != 0 && is_buffer_starved) {
LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??");
break;
}
output_buffer = output_buffer.subspan(samples_to_write);
}
voice_state.wave_buffers_consumed = wavebuffers_consumed;
voice_state.played_sample_count = played_sample_count;
voice_state.wave_buffer_index = wavebuffer_index;
voice_state.offset = offset;
voice_state.fraction = fraction;
}
} // namespace AudioCore::AudioRenderer

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