Compare commits
104 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf14790f08 | ||
|
|
1e67d2b59f | ||
|
|
a41baaa181 | ||
|
|
61ce57b524 | ||
|
|
6a2dcc8b3d | ||
|
|
6b5667dfa5 | ||
|
|
7d82e57b91 | ||
|
|
899c8bb330 | ||
|
|
4b404191cf | ||
|
|
6c8e456185 | ||
|
|
ec407bd3f1 | ||
|
|
ee5cb9c7b9 | ||
|
|
7fbd2916a1 | ||
|
|
7c3d241f0d | ||
|
|
1b36542be2 | ||
|
|
705f7db84d | ||
|
|
dcfe0a5feb | ||
|
|
1bcde9dd98 | ||
|
|
591d1f1b09 | ||
|
|
5af06d1433 | ||
|
|
403bdc4daf | ||
|
|
097785e19e | ||
|
|
4539700595 | ||
|
|
57311b2c8b | ||
|
|
cc83e0a600 | ||
|
|
f19e7be6e8 | ||
|
|
97729fd8e9 | ||
|
|
6c4e48dac4 | ||
|
|
7284adf4d9 | ||
|
|
072516dcae | ||
|
|
5878eb34f4 | ||
|
|
a47fc62cea | ||
|
|
5cda630417 | ||
|
|
1d700f1dfa | ||
|
|
da066e8ed8 | ||
|
|
dc45147736 | ||
|
|
d60b0e8655 | ||
|
|
58081a3664 | ||
|
|
5b4bef13e7 | ||
|
|
265d1d6979 | ||
|
|
4b93ea59db | ||
|
|
458da8a948 | ||
|
|
6e36f4d230 | ||
|
|
bcd0ca6076 | ||
|
|
a67776013d | ||
|
|
382b41b18f | ||
|
|
6f9bfb10d3 | ||
|
|
aab25d9b22 | ||
|
|
f5964fe2a9 | ||
|
|
caaf2368b6 | ||
|
|
742f67908c | ||
|
|
b4ee3fa39a | ||
|
|
09300abe92 | ||
|
|
ba8ea95624 | ||
|
|
a5bdf824e6 | ||
|
|
e4eb7acd49 | ||
|
|
8ca8281f4f | ||
|
|
6d160873c4 | ||
|
|
02282477e7 | ||
|
|
f8aaa59990 | ||
|
|
7d66f8339e | ||
|
|
b8813edc51 | ||
|
|
87bb44830b | ||
|
|
912cae21b0 | ||
|
|
93a4ca11fa | ||
|
|
a9a9999efd | ||
|
|
99fbdaf75b | ||
|
|
d2c0b45bca | ||
|
|
8266f63130 | ||
|
|
037ce7cb5f | ||
|
|
a1d2fb314e | ||
|
|
914ead075e | ||
|
|
30b23fb7b8 | ||
|
|
40e39ddd46 | ||
|
|
1002563776 | ||
|
|
fc503c3445 | ||
|
|
e991525d63 | ||
|
|
a9a83fa726 | ||
|
|
77137583cd | ||
|
|
da07e13e07 | ||
|
|
21945ae127 | ||
|
|
0624c880bd | ||
|
|
53fb4a78a3 | ||
|
|
1a85c18600 | ||
|
|
381381c7e0 | ||
|
|
2fed6dd7e1 | ||
|
|
9627c550a0 | ||
|
|
1584de951a | ||
|
|
2535e9d1ec | ||
|
|
8e0e2e95e6 | ||
|
|
32b522b1fd | ||
|
|
adc617ccc4 | ||
|
|
802bbb2263 | ||
|
|
a1c1ad096d | ||
|
|
1611c53c12 | ||
|
|
e71d457af9 | ||
|
|
b2ad4dd189 | ||
|
|
caef92a584 | ||
|
|
4fa625c4fa | ||
|
|
cbef6b1fca | ||
|
|
4f847621af | ||
|
|
b38509b030 | ||
|
|
c0264d2121 | ||
|
|
5e7e55b98a |
@@ -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
|
||||
|
||||
|
||||
@@ -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 ./
|
||||
|
||||
@@ -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 ..
|
||||
|
||||
|
||||
@@ -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/ ';'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
20
.github/workflows/verify.yml
vendored
@@ -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 }}
|
||||
|
||||
17
.gitmodules
vendored
@@ -1,9 +1,12 @@
|
||||
[submodule "enet"]
|
||||
path = externals/enet
|
||||
url = https://github.com/lsalzman/enet.git
|
||||
[submodule "inih"]
|
||||
path = externals/inih/inih
|
||||
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 +37,15 @@
|
||||
[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
|
||||
url = https://github.com/FFmpeg/FFmpeg.git
|
||||
[submodule "vcpkg"]
|
||||
path = externals/vcpkg
|
||||
url = https://github.com/Microsoft/vcpkg.git
|
||||
[submodule "cpp-jwt"]
|
||||
path = externals/cpp-jwt
|
||||
url = https://github.com/arun11299/cpp-jwt.git
|
||||
|
||||
178
CMakeLists.txt
@@ -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,24 +189,14 @@ 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")
|
||||
if (NOT YUZU_USE_BUNDLED_QT)
|
||||
find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets DBus)
|
||||
find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets DBus Multimedia)
|
||||
endif()
|
||||
if (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT)
|
||||
# Check for dependencies, then enable bundled Qt download
|
||||
@@ -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 Multimedia 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 Multimedia ${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
|
||||
|
||||
@@ -10,21 +10,22 @@ function(copy_yuzu_Qt5_deps target_dir)
|
||||
set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
|
||||
set(Qt5_PLATFORMTHEMES_DIR "${Qt5_DIR}/../../../plugins/platformthemes/")
|
||||
set(Qt5_PLATFORMINPUTCONTEXTS_DIR "${Qt5_DIR}/../../../plugins/platforminputcontexts/")
|
||||
set(Qt5_MEDIASERVICE_DIR "${Qt5_DIR}/../../../plugins/mediaservice/")
|
||||
set(Qt5_XCBGLINTEGRATIONS_DIR "${Qt5_DIR}/../../../plugins/xcbglintegrations/")
|
||||
set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
|
||||
set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
|
||||
set(Qt5_RESOURCES_DIR "${Qt5_DIR}/../../../resources/")
|
||||
set(PLATFORMS ${DLL_DEST}plugins/platforms/)
|
||||
set(MEDIASERVICE ${DLL_DEST}mediaservice/)
|
||||
set(STYLES ${DLL_DEST}plugins/styles/)
|
||||
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>.*
|
||||
Qt5Multimedia$<$<CONFIG:Debug>:d>.*
|
||||
Qt5Network$<$<CONFIG:Debug>:d>.*
|
||||
)
|
||||
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
@@ -37,18 +38,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 +56,11 @@ function(copy_yuzu_Qt5_deps target_dir)
|
||||
windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS}
|
||||
qjpeg$<$<CONFIG:Debug>:d>.*
|
||||
qgif$<$<CONFIG:Debug>:d>.*
|
||||
)
|
||||
)
|
||||
windows_copy_files(yuzu ${Qt5_MEDIASERVICE_DIR} ${MEDIASERVICE}
|
||||
dsengine$<$<CONFIG:Debug>:d>.*
|
||||
wmfengine$<$<CONFIG:Debug>:d>.*
|
||||
)
|
||||
else()
|
||||
set(Qt5_DLLS
|
||||
"${Qt5_DLL_DIR}libQt5Core.so.5"
|
||||
|
||||
9
dist/license.md
vendored
@@ -3,6 +3,9 @@ The icons in this folder and its subfolders have the following licenses:
|
||||
Icon Name | License | Origin/Author
|
||||
--- | --- | ---
|
||||
qt_themes/default/icons/16x16/checked.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/failed.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
|
||||
@@ -10,18 +13,24 @@ qt_themes/default/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.
|
||||
qt_themes/default/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
|
||||
qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
|
||||
qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
|
||||
BIN
dist/qt_themes/colorful/icons/16x16/connected.png
vendored
Normal file
|
After Width: | Height: | Size: 362 B |
BIN
dist/qt_themes/colorful/icons/16x16/connected_notification.png
vendored
Normal file
|
After Width: | Height: | Size: 607 B |
BIN
dist/qt_themes/colorful/icons/16x16/disconnected.png
vendored
Normal file
|
After Width: | Height: | Size: 784 B |
3
dist/qt_themes/colorful/style.qrc
vendored
@@ -1,6 +1,9 @@
|
||||
<RCC>
|
||||
<qresource prefix="icons/colorful">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
||||
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
|
||||
4
dist/qt_themes/colorful_dark/style.qrc
vendored
@@ -1,11 +1,15 @@
|
||||
<RCC>
|
||||
<qresource prefix="icons/colorful_dark">
|
||||
<file alias="16x16/connected.png">../colorful/icons/16x16/connected.png</file>
|
||||
<file alias="16x16/connected_notification.png">../colorful/icons/16x16/connected_notification.png</file>
|
||||
<file alias="16x16/disconnected.png">../colorful/icons/16x16/disconnected.png</file>
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
|
||||
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/plus.png">../colorful/icons/48x48/plus.png</file>
|
||||
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
|
||||
<file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
|
||||
|
||||
4
dist/qt_themes/default/default.qrc
vendored
@@ -4,10 +4,14 @@
|
||||
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
|
||||
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
<file alias="48x48/star.png">icons/48x48/star.png</file>
|
||||
|
||||
BIN
dist/qt_themes/default/icons/16x16/connected.png
vendored
Normal file
|
After Width: | Height: | Size: 269 B |
BIN
dist/qt_themes/default/icons/16x16/connected_notification.png
vendored
Normal file
|
After Width: | Height: | Size: 517 B |
BIN
dist/qt_themes/default/icons/16x16/disconnected.png
vendored
Normal file
|
After Width: | Height: | Size: 306 B |
BIN
dist/qt_themes/default/icons/48x48/no_avatar.png
vendored
Normal file
|
After Width: | Height: | Size: 588 B |
BIN
dist/qt_themes/qdarkstyle/icons/16x16/connected.png
vendored
Normal file
|
After Width: | Height: | Size: 397 B |
BIN
dist/qt_themes/qdarkstyle/icons/16x16/connected_notification.png
vendored
Normal file
|
After Width: | Height: | Size: 526 B |
BIN
dist/qt_themes/qdarkstyle/icons/16x16/disconnected.png
vendored
Normal file
|
After Width: | Height: | Size: 444 B |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/no_avatar.png
vendored
Normal file
|
After Width: | Height: | Size: 708 B |
4
dist/qt_themes/qdarkstyle/style.qrc
vendored
@@ -1,11 +1,15 @@
|
||||
<RCC>
|
||||
<qresource prefix="icons/qdarkstyle">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
<file alias="48x48/star.png">icons/48x48/star.png</file>
|
||||
|
||||
9
externals/CMakeLists.txt
vendored
@@ -73,6 +73,10 @@ if (YUZU_USE_EXTERNAL_SDL2)
|
||||
add_library(SDL2 ALIAS SDL2-static)
|
||||
endif()
|
||||
|
||||
# ENet
|
||||
add_subdirectory(enet)
|
||||
target_include_directories(enet INTERFACE ./enet/include)
|
||||
|
||||
# Cubeb
|
||||
if(ENABLE_CUBEB)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
@@ -112,6 +116,11 @@ if (ENABLE_WEB_SERVICE)
|
||||
if (WIN32)
|
||||
target_link_libraries(httplib INTERFACE crypt32 cryptui ws2_32)
|
||||
endif()
|
||||
|
||||
# cpp-jwt
|
||||
add_library(cpp-jwt INTERFACE)
|
||||
target_include_directories(cpp-jwt INTERFACE ./cpp-jwt/include)
|
||||
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
|
||||
endif()
|
||||
|
||||
# Opus
|
||||
|
||||
2
externals/SDL
vendored
1
externals/cpp-jwt
vendored
Submodule
2
externals/dynarmic
vendored
1
externals/enet
vendored
Submodule
2
externals/ffmpeg/ffmpeg
vendored
1
externals/vcpkg
vendored
Submodule
@@ -156,6 +156,7 @@ add_subdirectory(common)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(audio_core)
|
||||
add_subdirectory(video_core)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(input_common)
|
||||
add_subdirectory(shader_recompiler)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
68
src/audio_core/audio_core.cpp
Normal 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
@@ -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
|
||||
61
src/audio_core/audio_event.cpp
Normal 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
|
||||
92
src/audio_core/audio_event.h
Normal 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
|
||||
91
src/audio_core/audio_in_manager.cpp
Normal 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
|
||||
92
src/audio_core/audio_in_manager.h
Normal 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
|
||||
80
src/audio_core/audio_manager.cpp
Normal 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
|
||||
101
src/audio_core/audio_manager.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
81
src/audio_core/audio_out_manager.cpp
Normal 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
|
||||
89
src/audio_core/audio_out_manager.h
Normal 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
|
||||
70
src/audio_core/audio_render_manager.cpp
Normal 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
|
||||
103
src/audio_core/audio_render_manager.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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, ¶ms, 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
60
src/audio_core/common/audio_renderer_parameter.h
Normal 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
|
||||
138
src/audio_core/common/common.h
Normal 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
|
||||
105
src/audio_core/common/feature_support.h
Normal 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
|
||||
35
src/audio_core/common/wave_buffer.h
Normal 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
|
||||
100
src/audio_core/common/workbuffer_allocator.h
Normal 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
|
||||
@@ -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, ¶ms, &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,
|
||||
¶ms, 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
21
src/audio_core/device/audio_buffer.h
Normal 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
|
||||
304
src/audio_core/device/audio_buffers.h
Normal 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
|
||||
114
src/audio_core/device/device_session.cpp
Normal 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
|
||||
126
src/audio_core/device/device_session.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
100
src/audio_core/in/audio_in.cpp
Normal 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
|
||||
147
src/audio_core/in/audio_in.h
Normal 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
|
||||
213
src/audio_core/in/audio_in_system.cpp
Normal 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
|
||||
275
src/audio_core/in/audio_in_system.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
100
src/audio_core/out/audio_out.cpp
Normal 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
|
||||
147
src/audio_core/out/audio_out.h
Normal 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
|
||||
207
src/audio_core/out/audio_out_system.cpp
Normal 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
|
||||
257
src/audio_core/out/audio_out_system.h
Normal 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
|
||||
118
src/audio_core/renderer/adsp/adsp.cpp
Normal 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
|
||||
173
src/audio_core/renderer/adsp/adsp.h
Normal 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
|
||||
226
src/audio_core/renderer/adsp/audio_renderer.cpp
Normal 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
|
||||
203
src/audio_core/renderer/adsp/audio_renderer.h
Normal 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
|
||||
21
src/audio_core/renderer/adsp/command_buffer.h
Normal 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
|
||||
109
src/audio_core/renderer/adsp/command_list_processor.cpp
Normal 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
|
||||