diff options
383 files changed, 37283 insertions, 9870 deletions
diff --git a/.ci/scripts/clang/docker.sh b/.ci/scripts/clang/docker.sh index 94a9ca0ec..db736f72b 100755 --- a/.ci/scripts/clang/docker.sh +++ b/.ci/scripts/clang/docker.sh @@ -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 diff --git a/.ci/scripts/linux/exec.sh b/.ci/scripts/linux/exec.sh index fc4594d65..78e8aeabf 100644 --- a/.ci/scripts/linux/exec.sh +++ b/.ci/scripts/linux/exec.sh @@ -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 ./ diff --git a/.ci/scripts/transifex/docker.sh b/.ci/scripts/transifex/docker.sh index bafd326f9..05e1a98f5 100755 --- a/.ci/scripts/transifex/docker.sh +++ b/.ci/scripts/transifex/docker.sh @@ -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 .. diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh index d670fe47d..9f34530d6 100755 --- a/.ci/scripts/windows/docker.sh +++ b/.ci/scripts/windows/docker.sh @@ -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/ ';' diff --git a/.ci/scripts/windows/upload.ps1 b/.ci/scripts/windows/upload.ps1 index 62483607b..ac2a38f1d 100644 --- a/.ci/scripts/windows/upload.ps1 +++ b/.ci/scripts/windows/upload.ps1 @@ -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 diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml index cca3189fa..5d2e86179 100644 --- a/.ci/templates/build-msvc.yml +++ b/.ci/templates/build-msvc.yml @@ -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' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3400670a..8ec7e3c69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 7e39ef847..887220441 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -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 }} diff --git a/.gitmodules b/.gitmodules index dc92d0a4b..3c0d15951 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/benhoyt/inih.git [submodule "cubeb"] path = externals/cubeb - url = https://github.com/kinetiknz/cubeb.git + url = https://github.com/mozilla/cubeb.git [submodule "dynarmic"] path = externals/dynarmic url = https://github.com/MerryMage/dynarmic.git @@ -34,9 +34,12 @@ [submodule "SDL"] path = externals/SDL url = https://github.com/libsdl-org/SDL.git -[submodule "externals/cpp-httplib"] +[submodule "cpp-httplib"] path = externals/cpp-httplib url = https://github.com/yhirose/cpp-httplib.git -[submodule "externals/ffmpeg/ffmpeg"] +[submodule "ffmpeg"] path = externals/ffmpeg/ffmpeg url = https://git.ffmpeg.org/ffmpeg.git +[submodule "vcpkg"] + path = externals/vcpkg + url = https://github.com/Microsoft/vcpkg.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 80a8d4ed8..40ca8b149 100644 --- a/CMakeLists.txt +++ b/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") -endif() -macro(yuzu_find_packages) - set(options FORCE_REQUIRED) - cmake_parse_arguments(FN "${options}" "" "" ${ARGN}) - - # 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() +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) - 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() +# 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() + +if (YUZU_TESTS) + find_package(Catch2 2.13.7 REQUIRED CONFIG) +endif() -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 diff --git a/CMakeModules/CopyYuzuQt5Deps.cmake b/CMakeModules/CopyYuzuQt5Deps.cmake index dd97f5b2b..6c5044caa 100644 --- a/CMakeModules/CopyYuzuQt5Deps.cmake +++ b/CMakeModules/CopyYuzuQt5Deps.cmake @@ -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" diff --git a/externals/SDL b/externals/SDL -Subproject e2ade2bfc46d915cd306c63c830b81d800b2575 +Subproject b424665e0899769b200231ba943353a5fee1b6b diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 7f84870712ac2fe06aa62dc2bebbe46b51a2cc2 +Subproject 91d1f944e3870e0f3c505b48f5ec00ca9a82b95 diff --git a/externals/vcpkg b/externals/vcpkg new file mode 160000 +Subproject cef0b3ec767df6e83806899fe9525f6cf8d7bc9 diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 89575a53e..2971c42a2 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -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) diff --git a/src/audio_core/algorithm/filter.cpp b/src/audio_core/algorithm/filter.cpp deleted file mode 100644 index 96e37991f..000000000 --- a/src/audio_core/algorithm/filter.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/algorithm/filter.h b/src/audio_core/algorithm/filter.h deleted file mode 100644 index 2586f0079..000000000 --- a/src/audio_core/algorithm/filter.h +++ /dev/null @@ -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 diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp deleted file mode 100644 index d2a4cd53f..000000000 --- a/src/audio_core/algorithm/interpolate.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h deleted file mode 100644 index 5e59f4d70..000000000 --- a/src/audio_core/algorithm/interpolate.h +++ /dev/null @@ -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 diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp new file mode 100644 index 000000000..78e615a10 --- /dev/null +++ b/src/audio_core/audio_core.cpp @@ -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 diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h new file mode 100644 index 000000000..0f7d61ee4 --- /dev/null +++ b/src/audio_core/audio_core.h @@ -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 diff --git a/src/audio_core/audio_event.cpp b/src/audio_core/audio_event.cpp new file mode 100644 index 000000000..424049c7a --- /dev/null +++ b/src/audio_core/audio_event.cpp @@ -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 diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h new file mode 100644 index 000000000..82dd32dca --- /dev/null +++ b/src/audio_core/audio_event.h @@ -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 diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp new file mode 100644 index 000000000..4aadb7fd6 --- /dev/null +++ b/src/audio_core/audio_in_manager.cpp @@ -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 diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h new file mode 100644 index 000000000..75b73a0b6 --- /dev/null +++ b/src/audio_core/audio_in_manager.h @@ -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 diff --git a/src/audio_core/audio_manager.cpp b/src/audio_core/audio_manager.cpp new file mode 100644 index 000000000..2f1bba9c3 --- /dev/null +++ b/src/audio_core/audio_manager.cpp @@ -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 diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h new file mode 100644 index 000000000..70316e9cb --- /dev/null +++ b/src/audio_core/audio_manager.h @@ -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 diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp deleted file mode 100644 index 83ec0221f..000000000 --- a/src/audio_core/audio_out.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h deleted file mode 100644 index 6856373f1..000000000 --- a/src/audio_core/audio_out.h +++ /dev/null @@ -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 diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp new file mode 100644 index 000000000..71d67de64 --- /dev/null +++ b/src/audio_core/audio_out_manager.cpp @@ -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 diff --git a/src/audio_core/audio_out_manager.h b/src/audio_core/audio_out_manager.h new file mode 100644 index 000000000..24981e08f --- /dev/null +++ b/src/audio_core/audio_out_manager.h @@ -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 diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp new file mode 100644 index 000000000..7a846835b --- /dev/null +++ b/src/audio_core/audio_render_manager.cpp @@ -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 diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h new file mode 100644 index 000000000..6a508ec56 --- /dev/null +++ b/src/audio_core/audio_render_manager.h @@ -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 diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp deleted file mode 100644 index 9191ca093..000000000 --- a/src/audio_core/audio_renderer.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h deleted file mode 100644 index a67ffd592..000000000 --- a/src/audio_core/audio_renderer.h +++ /dev/null @@ -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 diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp deleted file mode 100644 index ea7e45617..000000000 --- a/src/audio_core/behavior_info.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h deleted file mode 100644 index b8c3159b9..000000000 --- a/src/audio_core/behavior_info.h +++ /dev/null @@ -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 diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h deleted file mode 100644 index ac001629f..000000000 --- a/src/audio_core/buffer.h +++ /dev/null @@ -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 diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp deleted file mode 100644 index 868b7a173..000000000 --- a/src/audio_core/codec.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h deleted file mode 100644 index 5a058b368..000000000 --- a/src/audio_core/codec.h +++ /dev/null @@ -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 diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp deleted file mode 100644 index f97520820..000000000 --- a/src/audio_core/command_generator.cpp +++ /dev/null @@ -1,1369 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <algorithm> -#include <cmath> -#include <numbers> - -#include "audio_core/algorithm/interpolate.h" -#include "audio_core/command_generator.h" -#include "audio_core/effect_context.h" -#include "audio_core/mix_context.h" -#include "audio_core/voice_context.h" -#include "common/common_types.h" -#include "core/memory.h" - -namespace AudioCore { -namespace { -constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; -constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; -using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>; - -constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f}; -constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f}; -constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f}; -constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f}; -constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{ - 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f, - 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f, - 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f}; -constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{ - 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, - 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, - 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; - -template <std::size_t N> -void ApplyMix(std::span<s32> output, std::span<const s32> input, s32 gain, s32 sample_count) { - for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) { - for (std::size_t j = 0; j < N; j++) { - output[i + j] += - static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15); - } - } -} - -s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, float gain, float delta, - s32 sample_count) { - // XC2 passes in NaN mix volumes, causing further issues as we handle everything as s32 rather - // than float, so the NaN propogation is lost. As the samples get further modified for - // volume etc, they can get out of NaN range, so a later heuristic for catching this is - // more difficult. Handle it here by setting these samples to silence. - if (std::isnan(gain)) { - gain = 0.0f; - delta = 0.0f; - } - - s32 x = 0; - for (s32 i = 0; i < sample_count; i++) { - x = static_cast<s32>(static_cast<float>(input[i]) * gain); - output[i] += x; - gain += delta; - } - return x; -} - -void ApplyGain(std::span<s32> output, std::span<const s32> input, s32 gain, s32 delta, - s32 sample_count) { - for (s32 i = 0; i < sample_count; i++) { - output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15); - gain += delta; - } -} - -void ApplyGainWithoutDelta(std::span<s32> output, std::span<const s32> input, s32 gain, - s32 sample_count) { - for (s32 i = 0; i < sample_count; i++) { - output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15); - } -} - -s32 ApplyMixDepop(std::span<s32> output, s32 first_sample, s32 delta, s32 sample_count) { - const bool positive = first_sample > 0; - auto final_sample = std::abs(first_sample); - for (s32 i = 0; i < sample_count; i++) { - final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15); - if (positive) { - output[i] += final_sample; - } else { - output[i] -= final_sample; - } - } - if (positive) { - return final_sample; - } else { - return -final_sample; - } -} - -float Pow10(float x) { - if (x >= 0.0f) { - return 1.0f; - } else if (x <= -5.3f) { - return 0.0f; - } - return std::pow(10.0f, x); -} - -float SinD(float degrees) { - return std::sin(degrees * std::numbers::pi_v<float> / 180.0f); -} - -float CosD(float degrees) { - return std::cos(degrees * std::numbers::pi_v<float> / 180.0f); -} - -float ToFloat(s32 sample) { - return static_cast<float>(sample) / 65536.f; -} - -s32 ToS32(float sample) { - constexpr auto min = -8388608.0f; - constexpr auto max = 8388607.f; - float rescaled_sample = sample * 65536.0f; - if (rescaled_sample < min) { - rescaled_sample = min; - } - if (rescaled_sample > max) { - rescaled_sample = max; - } - return static_cast<s32>(rescaled_sample); -} - -constexpr std::array<u8, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -constexpr std::array<u8, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0, - 1, 1, 1, 0, 0, 0, 0, 1, 1, 1}; - -constexpr std::array<u8, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, - 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; - -constexpr std::array<u8, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2, - 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; - -template <std::size_t CHANNEL_COUNT> -void ApplyReverbGeneric( - I3dl2ReverbState& state, - const std::array<std::span<const s32>, AudioCommon::MAX_CHANNEL_COUNT>& input, - const std::array<std::span<s32>, AudioCommon::MAX_CHANNEL_COUNT>& output, s32 sample_count) { - - auto GetTapLookup = []() { - if constexpr (CHANNEL_COUNT == 1) { - return REVERB_TAP_INDEX_1CH; - } else if constexpr (CHANNEL_COUNT == 2) { - return REVERB_TAP_INDEX_2CH; - } else if constexpr (CHANNEL_COUNT == 4) { - return REVERB_TAP_INDEX_4CH; - } else if constexpr (CHANNEL_COUNT == 6) { - return REVERB_TAP_INDEX_6CH; - } - }; - - const auto& tap_index_lut = GetTapLookup(); - for (s32 sample = 0; sample < sample_count; sample++) { - std::array<f32, CHANNEL_COUNT> out_samples{}; - std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{}; - std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{}; - std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{}; - - // Mix everything into a single sample - s32 temp_mixed_sample = 0; - for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { - temp_mixed_sample += input[i][sample]; - } - const auto current_sample = ToFloat(temp_mixed_sample); - const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps); - - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) { - const auto tapped_samp = - state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i]; - out_samples[tap_index_lut[i]] += tapped_samp; - - if constexpr (CHANNEL_COUNT == 6) { - // handle lfe - out_samples[5] += tapped_samp; - } - } - - state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1; - state.early_delay_line.Tick(state.lowpass_0); - - for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { - out_samples[i] *= state.early_gain; - } - - // Two channel seems to apply a latet gain, we require to save this - f32 filter{}; - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - filter = state.fdn_delay_line[i].GetOutputSample(); - const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i]; - state.shelf_filter[i] = - filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i]; - fsamp[i] = computed; - } - - // Mixing matrix - mixed[0] = fsamp[1] + fsamp[2]; - mixed[1] = -fsamp[0] - fsamp[3]; - mixed[2] = fsamp[0] - fsamp[3]; - mixed[3] = fsamp[1] - fsamp[2]; - - if constexpr (CHANNEL_COUNT == 2) { - for (auto& mix : mixed) { - mix *= (filter * state.late_gain); - } - } - - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - const auto late = early_tap * state.late_gain; - osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]); - osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]); - state.fdn_delay_line[i].Tick(osamp[i]); - } - - if constexpr (CHANNEL_COUNT == 1) { - output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) + - (out_samples[0] + osamp[0] + osamp[1])); - } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) { - for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { - output[i][sample] = - ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); - } - } else if constexpr (CHANNEL_COUNT == 6) { - const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3])); - for (std::size_t i = 0; i < 4; i++) { - output[i][sample] = - ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); - } - output[4][sample] = - ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center)); - output[5][sample] = - ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3])); - } - } -} - -} // namespace - -CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, - VoiceContext& voice_context_, MixContext& mix_context_, - SplitterContext& splitter_context_, - EffectContext& effect_context_, Core::Memory::Memory& memory_) - : worker_params(worker_params_), voice_context(voice_context_), mix_context(mix_context_), - splitter_context(splitter_context_), effect_context(effect_context_), memory(memory_), - mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * - worker_params.sample_count), - sample_buffer(MIX_BUFFER_SIZE), - depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * - worker_params.sample_count) {} -CommandGenerator::~CommandGenerator() = default; - -void CommandGenerator::ClearMixBuffers() { - std::fill(mix_buffer.begin(), mix_buffer.end(), 0); - std::fill(sample_buffer.begin(), sample_buffer.end(), 0); - // std::fill(depop_buffer.begin(), depop_buffer.end(), 0); -} - -void CommandGenerator::GenerateVoiceCommands() { - if (dumping_frame) { - LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands"); - } - // Grab all our voices - const auto voice_count = voice_context.GetVoiceCount(); - for (std::size_t i = 0; i < voice_count; i++) { - auto& voice_info = voice_context.GetSortedInfo(i); - // Update voices and check if we should queue them - if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) { - continue; - } - - // Queue our voice - GenerateVoiceCommand(voice_info); - } - // Update our splitters - splitter_context.UpdateInternalState(); -} - -void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) { - auto& in_params = voice_info.GetInParams(); - const auto channel_count = in_params.channel_count; - - for (s32 channel = 0; channel < channel_count; channel++) { - const auto resource_id = in_params.voice_channel_resource_id[channel]; - auto& dsp_state = voice_context.GetDspSharedState(resource_id); - auto& channel_resource = voice_context.GetChannelResource(resource_id); - - // Decode our samples for our channel - GenerateDataSourceCommand(voice_info, dsp_state, channel); - - if (in_params.should_depop) { - in_params.last_volume = 0.0f; - } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER || - in_params.mix_id != AudioCommon::NO_MIX) { - // Apply a biquad filter if needed - GenerateBiquadFilterCommandForVoice(voice_info, dsp_state, - worker_params.mix_buffer_count, channel); - // Base voice volume ramping - GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel, - in_params.node_id); - in_params.last_volume = in_params.volume; - - if (in_params.mix_id != AudioCommon::NO_MIX) { - // If we're using a mix id - auto& mix_info = mix_context.GetInfo(in_params.mix_id); - const auto& dest_mix_params = mix_info.GetInParams(); - - // Voice Mixing - GenerateVoiceMixCommand( - channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(), - dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, - worker_params.mix_buffer_count + channel, in_params.node_id); - - // Update last mix volumes - channel_resource.UpdateLastMixVolumes(); - } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { - s32 base = channel; - while (auto* destination_data = - GetDestinationData(in_params.splitter_info_id, base)) { - base += channel_count; - - if (!destination_data->IsConfigured()) { - continue; - } - if (destination_data->GetMixId() >= static_cast<int>(mix_context.GetCount())) { - continue; - } - - const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId()); - const auto& dest_mix_params = mix_info.GetInParams(); - GenerateVoiceMixCommand( - destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(), - dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, - worker_params.mix_buffer_count + channel, in_params.node_id); - destination_data->MarkDirty(); - } - } - // Update biquad filter enabled states - for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { - in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled; - } - } - } -} - -void CommandGenerator::GenerateSubMixCommands() { - const auto mix_count = mix_context.GetCount(); - for (std::size_t i = 0; i < mix_count; i++) { - auto& mix_info = mix_context.GetSortedInfo(i); - const auto& in_params = mix_info.GetInParams(); - if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) { - continue; - } - GenerateSubMixCommand(mix_info); - } -} - -void CommandGenerator::GenerateFinalMixCommands() { - GenerateFinalMixCommand(); -} - -void CommandGenerator::PreCommand() { - if (!dumping_frame) { - return; - } - for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) { - const auto& base = splitter_context.GetInfo(i); - std::string graph = fmt::format("b[{}]", i); - const auto* head = base.GetHead(); - while (head != nullptr) { - graph += fmt::format("->{}", head->GetMixId()); - head = head->GetNextDestination(); - } - LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph); - } -} - -void CommandGenerator::PostCommand() { - if (!dumping_frame) { - return; - } - dumping_frame = false; -} - -void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, - s32 channel) { - const auto& in_params = voice_info.GetInParams(); - const auto depop = in_params.should_depop; - - if (depop) { - if (in_params.mix_id != AudioCommon::NO_MIX) { - auto& mix_info = mix_context.GetInfo(in_params.mix_id); - const auto& mix_in = mix_info.GetInParams(); - GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset); - } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { - s32 index{}; - while (const auto* destination = - GetDestinationData(in_params.splitter_info_id, index++)) { - if (!destination->IsConfigured()) { - continue; - } - auto& mix_info = mix_context.GetInfo(destination->GetMixId()); - const auto& mix_in = mix_info.GetInParams(); - GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset); - } - } - } else { - switch (in_params.sample_format) { - case SampleFormat::Pcm8: - case SampleFormat::Pcm16: - case SampleFormat::Pcm32: - case SampleFormat::PcmFloat: - DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel, - worker_params.sample_rate, worker_params.sample_count, - in_params.node_id); - break; - case SampleFormat::Adpcm: - ASSERT(channel == 0 && in_params.channel_count == 1); - DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0, - worker_params.sample_rate, worker_params.sample_count, - in_params.node_id); - break; - default: - ASSERT_MSG(false, "Unimplemented sample format={}", in_params.sample_format); - } - } -} - -void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, - VoiceState& dsp_state, - [[maybe_unused]] s32 mix_buffer_count, - [[maybe_unused]] s32 channel) { - for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { - const auto& in_params = voice_info.GetInParams(); - auto& biquad_filter = in_params.biquad_filter[i]; - // Check if biquad filter is actually used - if (!biquad_filter.enabled) { - continue; - } - - // Reinitialize our biquad filter state if it was enabled previously - if (!in_params.was_biquad_filter_enabled[i]) { - dsp_state.biquad_filter_state.fill(0); - } - - // Generate biquad filter - // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, - // dsp_state.biquad_filter_state, - // mix_buffer_count + channel, mix_buffer_count + channel, - // worker_params.sample_count, voice_info.GetInParams().node_id); - } -} - -void CommandGenerator::GenerateBiquadFilterCommand([[maybe_unused]] s32 mix_buffer_id, - const BiquadFilterParameter& params, - std::array<s64, 2>& state, - std::size_t input_offset, - std::size_t output_offset, s32 sample_count, - s32 node_id) { - if (dumping_frame) { - LOG_DEBUG(Audio, - "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, " - "input_mix_buffer={}, output_mix_buffer={}", - node_id, input_offset, output_offset); - } - std::span<const s32> input = GetMixBuffer(input_offset); - std::span<s32> output = GetMixBuffer(output_offset); - - // Biquad filter parameters - const auto [n0, n1, n2] = params.numerator; - const auto [d0, d1] = params.denominator; - - // Biquad filter states - auto [s0, s1] = state; - - constexpr s64 int32_min = std::numeric_limits<s32>::min(); - constexpr s64 int32_max = std::numeric_limits<s32>::max(); - - for (int i = 0; i < sample_count; ++i) { - const auto sample = static_cast<s64>(input[i]); - const auto f = (sample * n0 + s0 + 0x4000) >> 15; - const auto y = std::clamp(f, int32_min, int32_max); - s0 = sample * n1 + y * d0 + s1; - s1 = sample * n2 + y * d1; - output[i] = static_cast<s32>(y); - } - - state = {s0, s1}; -} - -void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state, - std::size_t mix_buffer_count, - std::size_t mix_buffer_offset) { - for (std::size_t i = 0; i < mix_buffer_count; i++) { - auto& sample = dsp_state.previous_samples[i]; - if (sample != 0) { - depop_buffer[mix_buffer_offset + i] += sample; - sample = 0; - } - } -} - -void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, - std::size_t mix_buffer_offset, - s32 sample_rate) { - const std::size_t end_offset = - std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount()); - const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB; - for (std::size_t i = mix_buffer_offset; i < end_offset; i++) { - if (depop_buffer[i] == 0) { - continue; - } - - depop_buffer[i] = - ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count); - } -} - -void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) { - const std::size_t effect_count = effect_context.GetCount(); - const auto buffer_offset = mix_info.GetInParams().buffer_offset; - for (std::size_t i = 0; i < effect_count; i++) { - const auto index = mix_info.GetEffectOrder(i); - if (index == AudioCommon::NO_EFFECT_ORDER) { - break; - } - auto* info = effect_context.GetInfo(index); - const auto type = info->GetType(); - - // TODO(ogniK): Finish remaining effects - switch (type) { - case EffectType::Aux: - GenerateAuxCommand(buffer_offset, info, info->IsEnabled()); - break; - case EffectType::I3dl2Reverb: - GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled()); - break; - case EffectType::BiquadFilter: - GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled()); - break; - default: - break; - } - - info->UpdateForCommandGeneration(); - } -} - -void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, - bool enabled) { - auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info); - const auto& params = reverb->GetParams(); - auto& state = reverb->GetState(); - const auto channel_count = params.channel_count; - - if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) { - return; - } - - std::array<std::span<const s32>, AudioCommon::MAX_CHANNEL_COUNT> input{}; - std::array<std::span<s32>, AudioCommon::MAX_CHANNEL_COUNT> output{}; - - const auto status = params.status; - for (s32 i = 0; i < channel_count; i++) { - input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]); - output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]); - } - - if (enabled) { - if (status == ParameterStatus::Initialized) { - InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer()); - } else if (status == ParameterStatus::Updating) { - UpdateI3dl2Reverb(reverb->GetParams(), state, false); - } - } - - if (enabled) { - switch (channel_count) { - case 1: - ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count); - break; - case 2: - ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count); - break; - case 4: - ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count); - break; - case 6: - ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count); - break; - } - } else { - for (s32 i = 0; i < channel_count; i++) { - // Only copy if the buffer input and output do not match! - if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) { - std::memcpy(output[i].data(), input[i].data(), - worker_params.sample_count * sizeof(s32)); - } - } - } -} - -void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, - bool enabled) { - if (!enabled) { - return; - } - const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams(); - const auto channel_count = params.channel_count; - for (s32 i = 0; i < channel_count; i++) { - // TODO(ogniK): Actually implement biquad filter - if (params.input[i] != params.output[i]) { - std::span<const s32> input = GetMixBuffer(mix_buffer_offset + params.input[i]); - std::span<s32> output = GetMixBuffer(mix_buffer_offset + params.output[i]); - ApplyMix<1>(output, input, 32768, worker_params.sample_count); - } - } -} - -void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) { - auto* aux = dynamic_cast<EffectAuxInfo*>(info); - const auto& params = aux->GetParams(); - if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) { - const auto max_channels = params.count; - u32 offset{}; - for (u32 channel = 0; channel < max_channels; channel++) { - u32 write_count = 0; - if (channel == (max_channels - 1)) { - write_count = offset + worker_params.sample_count; - } - - const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset; - const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset; - - if (enabled) { - AuxInfoDSP send_info{}; - AuxInfoDSP recv_info{}; - memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP)); - memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); - - WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count, - GetMixBuffer(input_index), worker_params.sample_count, offset, - write_count); - memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP)); - - const auto samples_read = ReadAuxBuffer( - recv_info, aux->GetRecvBuffer(), params.sample_count, - GetMixBuffer(output_index), worker_params.sample_count, offset, write_count); - memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); - - if (samples_read != static_cast<int>(worker_params.sample_count) && - samples_read <= params.sample_count) { - std::memset(GetMixBuffer(output_index).data(), 0, - params.sample_count - samples_read); - } - } else { - AuxInfoDSP empty{}; - memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP)); - memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP)); - if (output_index != input_index) { - std::memcpy(GetMixBuffer(output_index).data(), GetMixBuffer(input_index).data(), - worker_params.sample_count * sizeof(s32)); - } - } - - offset += worker_params.sample_count; - } - } -} - -ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) { - if (splitter_id == AudioCommon::NO_SPLITTER) { - return nullptr; - } - return splitter_context.GetDestinationData(splitter_id, index); -} - -s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, - std::span<const s32> data, u32 sample_count, u32 write_offset, - u32 write_count) { - if (max_samples == 0) { - return 0; - } - u32 offset = dsp_info.write_offset + write_offset; - if (send_buffer == 0 || offset > max_samples) { - return 0; - } - - s32 data_offset{}; - u32 remaining = sample_count; - while (remaining > 0) { - // Get position in buffer - const auto base = send_buffer + (offset * sizeof(u32)); - const auto samples_to_grab = std::min(max_samples - offset, remaining); - // Write to output - memory.WriteBlock(base, (data.data() + data_offset), samples_to_grab * sizeof(u32)); - offset = (offset + samples_to_grab) % max_samples; - remaining -= samples_to_grab; - data_offset += samples_to_grab; - } - - if (write_count != 0) { - dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples; - } - return sample_count; -} - -s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, - std::span<s32> out_data, u32 sample_count, u32 read_offset, - u32 read_count) { - if (max_samples == 0) { - return 0; - } - - u32 offset = recv_info.read_offset + read_offset; - if (recv_buffer == 0 || offset > max_samples) { - return 0; - } - - u32 remaining = sample_count; - s32 data_offset{}; - while (remaining > 0) { - const auto base = recv_buffer + (offset * sizeof(u32)); - const auto samples_to_grab = std::min(max_samples - offset, remaining); - std::vector<s32> buffer(samples_to_grab); - memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32)); - std::memcpy(out_data.data() + data_offset, buffer.data(), buffer.size() * sizeof(u32)); - offset = (offset + samples_to_grab) % max_samples; - remaining -= samples_to_grab; - data_offset += samples_to_grab; - } - - if (read_count != 0) { - recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples; - } - return sample_count; -} - -void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, - std::vector<u8>& work_buffer) { - // Reset state - state.lowpass_0 = 0.0f; - state.lowpass_1 = 0.0f; - state.lowpass_2 = 0.0f; - - state.early_delay_line.Reset(); - state.early_tap_steps.fill(0); - state.early_gain = 0.0f; - state.late_gain = 0.0f; - state.early_to_late_taps = 0; - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - state.fdn_delay_line[i].Reset(); - state.decay_delay_line0[i].Reset(); - state.decay_delay_line1[i].Reset(); - } - state.last_reverb_echo = 0.0f; - state.center_delay_line.Reset(); - for (auto& coef : state.lpf_coefficients) { - coef.fill(0.0f); - } - state.shelf_filter.fill(0.0f); - state.dry_gain = 0.0f; - - const auto sample_rate = info.sample_rate / 1000; - f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data()); - - s32 delay_samples{}; - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - delay_samples = - AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]); - state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr); - work_buffer_ptr += delay_samples + 1; - - delay_samples = - AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]); - state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); - work_buffer_ptr += delay_samples + 1; - - delay_samples = - AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]); - state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); - work_buffer_ptr += delay_samples + 1; - } - delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f); - state.center_delay_line.Initialize(delay_samples, work_buffer_ptr); - work_buffer_ptr += delay_samples + 1; - - delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f); - state.early_delay_line.Initialize(delay_samples, work_buffer_ptr); - - UpdateI3dl2Reverb(info, state, true); -} - -void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, - bool should_clear) { - - state.dry_gain = info.dry_gain; - state.shelf_filter.fill(0.0f); - state.lowpass_0 = 0.0f; - state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f); - state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f); - - const auto sample_rate = info.sample_rate / 1000; - const f32 hf_gain = Pow10(info.room_hf / 2000.0f); - if (hf_gain >= 1.0f) { - state.lowpass_2 = 1.0f; - state.lowpass_1 = 0.0f; - } else { - const auto a = 1.0f - hf_gain; - const auto b = 2.0f * (2.0f - hf_gain * CosD(256.0f * info.hf_reference / - static_cast<f32>(info.sample_rate))); - const auto c = std::sqrt(b * b - 4.0f * a * a); - - state.lowpass_1 = (b - c) / (2.0f * a); - state.lowpass_2 = 1.0f - state.lowpass_1; - } - state.early_to_late_taps = AudioCommon::CalculateDelaySamples( - sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay)); - - state.last_reverb_echo = 0.6f * info.diffusion * 0.01f; - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - const auto length = - FDN_MIN_DELAY_LINE_TIMES[i] + - (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]); - state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length)); - - const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() + - state.decay_delay_line0[i].GetDelay() + - state.decay_delay_line1[i].GetDelay(); - - float a = (-60.0f * static_cast<f32>(delay_sample_counts)) / - (info.decay_time * static_cast<f32>(info.sample_rate)); - float b = a / info.hf_decay_ratio; - float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) / - SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)); - float d = Pow10((b - a) / 40.0f); - float e = Pow10((b + a) / 40.0f) * 0.7071f; - - state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d); - state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d); - state.lpf_coefficients[2][i] = (c - d) / (c + d); - - state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo); - state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo); - } - - if (should_clear) { - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - state.fdn_delay_line[i].Clear(); - state.decay_delay_line0[i].Clear(); - state.decay_delay_line1[i].Clear(); - } - state.early_delay_line.Clear(); - state.center_delay_line.Clear(); - } - - const auto max_early_delay = state.early_delay_line.GetMaxDelay(); - const auto reflection_time = 1000.0f * (0.9998f * info.reverb_delay + 0.02f); - for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { - const auto length = AudioCommon::CalculateDelaySamples( - sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); - state.early_tap_steps[tap] = std::min(length, max_early_delay); - } -} - -void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, - s32 channel, s32 node_id) { - const auto last = static_cast<s32>(last_volume * 32768.0f); - const auto current = static_cast<s32>(current_volume * 32768.0f); - const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) / - static_cast<float>(worker_params.sample_count)); - - if (dumping_frame) { - LOG_DEBUG(Audio, - "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, " - "last_volume={}, current_volume={}", - node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel), - last_volume, current_volume); - } - // Apply generic gain on samples - ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta, - worker_params.sample_count); -} - -void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, - const MixVolumeBuffer& last_mix_volumes, - VoiceState& dsp_state, s32 mix_buffer_offset, - s32 mix_buffer_count, s32 voice_index, s32 node_id) { - // Loop all our mix buffers - for (s32 i = 0; i < mix_buffer_count; i++) { - if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) { - const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) / - static_cast<float>(worker_params.sample_count); - - if (dumping_frame) { - LOG_DEBUG(Audio, - "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, " - "output={}, last_volume={}, current_volume={}", - node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i], - mix_volumes[i]); - } - - dsp_state.previous_samples[i] = - ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index), - last_mix_volumes[i], delta, worker_params.sample_count); - } else { - dsp_state.previous_samples[i] = 0; - } - } -} - -void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) { - if (dumping_frame) { - LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand"); - } - const auto& in_params = mix_info.GetInParams(); - GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, - in_params.sample_rate); - - GenerateEffectCommand(mix_info); - - GenerateMixCommands(mix_info); -} - -void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) { - if (!mix_info.HasAnyConnection()) { - return; - } - const auto& in_params = mix_info.GetInParams(); - if (in_params.dest_mix_id != AudioCommon::NO_MIX) { - const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id); - const auto& dest_in_params = dest_mix.GetInParams(); - - const auto buffer_count = in_params.buffer_count; - - for (s32 i = 0; i < buffer_count; i++) { - for (s32 j = 0; j < dest_in_params.buffer_count; j++) { - const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j]; - if (mixed_volume != 0.0f) { - GenerateMixCommand(dest_in_params.buffer_offset + j, - in_params.buffer_offset + i, mixed_volume, - in_params.node_id); - } - } - } - } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) { - s32 base{}; - while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) { - if (!destination_data->IsConfigured()) { - continue; - } - - const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId()); - const auto& dest_in_params = dest_mix.GetInParams(); - const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset; - for (std::size_t i = 0; i < static_cast<std::size_t>(dest_in_params.buffer_count); - i++) { - const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i); - if (mixed_volume != 0.0f) { - GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume, - in_params.node_id); - } - } - } - } -} - -void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, - float volume, s32 node_id) { - - if (dumping_frame) { - LOG_DEBUG(Audio, - "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}", - node_id, input_offset, output_offset, volume); - } - - std::span<s32> output = GetMixBuffer(output_offset); - std::span<const s32> input = GetMixBuffer(input_offset); - - const s32 gain = static_cast<s32>(volume * 32768.0f); - // Mix with loop unrolling - if (worker_params.sample_count % 4 == 0) { - ApplyMix<4>(output, input, gain, worker_params.sample_count); - } else if (worker_params.sample_count % 2 == 0) { - ApplyMix<2>(output, input, gain, worker_params.sample_count); - } else { - ApplyMix<1>(output, input, gain, worker_params.sample_count); - } -} - -void CommandGenerator::GenerateFinalMixCommand() { - if (dumping_frame) { - LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand"); - } - auto& mix_info = mix_context.GetFinalMixInfo(); - const auto& in_params = mix_info.GetInParams(); - - GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, - in_params.sample_rate); - - GenerateEffectCommand(mix_info); - - for (s32 i = 0; i < in_params.buffer_count; i++) { - const s32 gain = static_cast<s32>(in_params.volume * 32768.0f); - if (dumping_frame) { - LOG_DEBUG( - Audio, - "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}", - in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i, - in_params.volume); - } - ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i), - GetMixBuffer(in_params.buffer_offset + i), gain, - worker_params.sample_count); - } -} - -template <typename T> -s32 CommandGenerator::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) { - const auto& in_params = voice_info.GetInParams(); - const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; - if (wave_buffer.buffer_address == 0) { - return 0; - } - if (wave_buffer.buffer_size == 0) { - return 0; - } - if (sample_end_offset < sample_start_offset) { - return 0; - } - const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset; - const auto start_offset = - ((dsp_state.offset + sample_start_offset) * in_params.channel_count) * sizeof(T); - const auto buffer_pos = wave_buffer.buffer_address + start_offset; - const auto samples_processed = std::min(sample_count, samples_remaining); - - const auto channel_count = in_params.channel_count; - std::vector<T> buffer(samples_processed * channel_count); - memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(T)); - - if constexpr (std::is_floating_point_v<T>) { - for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) { - sample_buffer[mix_offset + i] = static_cast<s32>(buffer[i * channel_count + channel] * - std::numeric_limits<s16>::max()); - } - } else if constexpr (sizeof(T) == 1) { - for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) { - sample_buffer[mix_offset + i] = - static_cast<s32>(static_cast<f32>(buffer[i * channel_count + channel] / - std::numeric_limits<s8>::max()) * - std::numeric_limits<s16>::max()); - } - } else if constexpr (sizeof(T) == 2) { - for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) { - sample_buffer[mix_offset + i] = buffer[i * channel_count + channel]; - } - } else { - for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) { - sample_buffer[mix_offset + i] = - static_cast<s32>(static_cast<f32>(buffer[i * channel_count + channel] / - std::numeric_limits<s32>::max()) * - std::numeric_limits<s16>::max()); - } - } - - return samples_processed; -} - -s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, - s32 sample_start_offset, s32 sample_end_offset, s32 sample_count, - [[maybe_unused]] s32 channel, std::size_t mix_offset) { - const auto& in_params = voice_info.GetInParams(); - const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; - if (wave_buffer.buffer_address == 0) { - return 0; - } - if (wave_buffer.buffer_size == 0) { - return 0; - } - if (sample_end_offset < sample_start_offset) { - return 0; - } - - static constexpr std::array<int, 16> SIGNED_NIBBLES{ - 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, - }; - - constexpr std::size_t FRAME_LEN = 8; - constexpr std::size_t NIBBLES_PER_SAMPLE = 16; - constexpr std::size_t SAMPLES_PER_FRAME = 14; - - auto frame_header = dsp_state.context.header; - s32 idx = (frame_header >> 4) & 0xf; - s32 scale = frame_header & 0xf; - s16 yn1 = dsp_state.context.yn1; - s16 yn2 = dsp_state.context.yn2; - - Codec::ADPCM_Coeff coeffs; - memory.ReadBlock(in_params.additional_params_address, coeffs.data(), - sizeof(Codec::ADPCM_Coeff)); - - s32 coef1 = coeffs[idx * 2]; - s32 coef2 = coeffs[idx * 2 + 1]; - - const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset; - const auto samples_processed = std::min(sample_count, samples_remaining); - const auto sample_pos = dsp_state.offset + sample_start_offset; - - const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME; - auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) + - samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0); - - const auto decode_sample = [&](const int nibble) -> s16 { - const int xn = nibble * (1 << scale); - // We first transform everything into 11 bit fixed point, perform the second order - // digital filter, then transform back. - // 0x400 == 0.5 in 11 bit fixed point. - // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2] - int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11; - // Clamp to output range. - val = std::clamp<s32>(val, -32768, 32767); - // Advance output feedback. - yn2 = yn1; - yn1 = static_cast<s16>(val); - return yn1; - }; - - std::size_t buffer_offset{}; - std::vector<u8> buffer( - std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN)); - memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(), - buffer.size()); - std::size_t cur_mix_offset = mix_offset; - - auto remaining_samples = samples_processed; - while (remaining_samples > 0) { - if (position_in_frame % NIBBLES_PER_SAMPLE == 0) { - // Read header - frame_header = buffer[buffer_offset++]; - idx = (frame_header >> 4) & 0xf; - scale = frame_header & 0xf; - coef1 = coeffs[idx * 2]; - coef2 = coeffs[idx * 2 + 1]; - position_in_frame += 2; - - // Decode entire frame - if (remaining_samples >= static_cast<int>(SAMPLES_PER_FRAME)) { - for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) { - // Sample 1 - const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4]; - const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf]; - const s16 sample_1 = decode_sample(s0); - const s16 sample_2 = decode_sample(s1); - sample_buffer[cur_mix_offset++] = sample_1; - sample_buffer[cur_mix_offset++] = sample_2; - } - remaining_samples -= static_cast<int>(SAMPLES_PER_FRAME); - position_in_frame += SAMPLES_PER_FRAME; - continue; - } - } - // Decode mid frame - s32 current_nibble = buffer[buffer_offset]; - if (position_in_frame++ & 0x1) { - current_nibble &= 0xf; - buffer_offset++; - } else { - current_nibble >>= 4; - } - const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]); - sample_buffer[cur_mix_offset++] = sample; - remaining_samples--; - } - - dsp_state.context.header = frame_header; - dsp_state.context.yn1 = yn1; - dsp_state.context.yn2 = yn2; - - return samples_processed; -} - -std::span<s32> CommandGenerator::GetMixBuffer(std::size_t index) { - return std::span<s32>(mix_buffer.data() + (index * worker_params.sample_count), - worker_params.sample_count); -} - -std::span<const s32> CommandGenerator::GetMixBuffer(std::size_t index) const { - return std::span<const s32>(mix_buffer.data() + (index * worker_params.sample_count), - worker_params.sample_count); -} - -std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const { - return worker_params.mix_buffer_count + channel; -} - -std::size_t CommandGenerator::GetTotalMixBufferCount() const { - return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT; -} - -std::span<s32> CommandGenerator::GetChannelMixBuffer(s32 channel) { - return GetMixBuffer(worker_params.mix_buffer_count + channel); -} - -std::span<const s32> CommandGenerator::GetChannelMixBuffer(s32 channel) const { - return GetMixBuffer(worker_params.mix_buffer_count + channel); -} - -void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output, - VoiceState& dsp_state, s32 channel, - s32 target_sample_rate, s32 sample_count, - s32 node_id) { - const auto& in_params = voice_info.GetInParams(); - if (dumping_frame) { - LOG_DEBUG(Audio, - "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, " - "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}", - node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate, - in_params.mix_id, in_params.splitter_info_id); - } - ASSERT_OR_EXECUTE(output.data() != nullptr, { return; }); - - const auto resample_rate = static_cast<s32>( - static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) * - static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f))); - if (dsp_state.fraction + sample_count * resample_rate > - static_cast<s32>(SCALED_MIX_BUFFER_SIZE - 4ULL)) { - return; - } - - auto min_required_samples = - std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate); - if (min_required_samples >= sample_count) { - min_required_samples = sample_count; - } - - std::size_t temp_mix_offset{}; - s32 samples_output{}; - auto samples_remaining = sample_count; - while (samples_remaining > 0) { - const auto samples_to_output = std::min(samples_remaining, min_required_samples); - const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15; - - if (!in_params.behavior_flags.is_pitch_and_src_skipped) { - // Append sample histtory for resampler - for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { - sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i]; - } - temp_mix_offset += 4; - } - - s32 samples_read{}; - while (samples_read < samples_to_read) { - const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; - // No more data can be read - if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) { - break; - } - - if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 && - wave_buffer.context_address != 0 && wave_buffer.context_size != 0) { - memory.ReadBlock(wave_buffer.context_address, &dsp_state.context, - sizeof(ADPCMContext)); - } - - s32 samples_offset_start; - s32 samples_offset_end; - if (dsp_state.loop_count > 0 && wave_buffer.loop_start_sample != 0 && - wave_buffer.loop_end_sample != 0 && - wave_buffer.loop_start_sample <= wave_buffer.loop_end_sample) { - samples_offset_start = wave_buffer.loop_start_sample; - samples_offset_end = wave_buffer.loop_end_sample; - } else { - samples_offset_start = wave_buffer.start_sample_offset; - samples_offset_end = wave_buffer.end_sample_offset; - } - - s32 samples_decoded{0}; - switch (in_params.sample_format) { - case SampleFormat::Pcm8: - samples_decoded = - DecodePcm<s8>(voice_info, dsp_state, samples_offset_start, samples_offset_end, - samples_to_read - samples_read, channel, temp_mix_offset); - break; - case SampleFormat::Pcm16: - samples_decoded = - DecodePcm<s16>(voice_info, dsp_state, samples_offset_start, samples_offset_end, - samples_to_read - samples_read, channel, temp_mix_offset); - break; - case SampleFormat::Pcm32: - samples_decoded = - DecodePcm<s32>(voice_info, dsp_state, samples_offset_start, samples_offset_end, - samples_to_read - samples_read, channel, temp_mix_offset); - break; - case SampleFormat::PcmFloat: - samples_decoded = - DecodePcm<f32>(voice_info, dsp_state, samples_offset_start, samples_offset_end, - samples_to_read - samples_read, channel, temp_mix_offset); - break; - case SampleFormat::Adpcm: - samples_decoded = - DecodeAdpcm(voice_info, dsp_state, samples_offset_start, samples_offset_end, - samples_to_read - samples_read, channel, temp_mix_offset); - break; - default: - ASSERT_MSG(false, "Unimplemented sample format={}", in_params.sample_format); - } - - temp_mix_offset += samples_decoded; - samples_read += samples_decoded; - dsp_state.offset += samples_decoded; - dsp_state.played_sample_count += samples_decoded; - - if (dsp_state.offset >= (samples_offset_end - samples_offset_start) || - samples_decoded == 0) { - // Reset our sample offset - dsp_state.offset = 0; - if (wave_buffer.is_looping) { - dsp_state.loop_count++; - if (wave_buffer.loop_count > 0 && - (dsp_state.loop_count > wave_buffer.loop_count || samples_decoded == 0)) { - // End of our buffer - voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer); - } - - if (samples_decoded == 0) { - break; - } - - if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) { - dsp_state.played_sample_count = 0; - } - } else { - // Update our wave buffer states - voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer); - } - } - } - - if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) { - // No need to resample - std::memcpy(output.data() + samples_output, sample_buffer.data(), - samples_read * sizeof(s32)); - } else { - std::fill(sample_buffer.begin() + temp_mix_offset, - sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read), - 0); - AudioCore::Resample(output.data() + samples_output, sample_buffer.data(), resample_rate, - dsp_state.fraction, samples_to_output); - // Resample - for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { - dsp_state.sample_history[i] = sample_buffer[samples_to_read + i]; - } - } - samples_remaining -= samples_to_output; - samples_output += samples_to_output; - } -} - -} // namespace AudioCore diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h deleted file mode 100644 index 8077e7768..000000000 --- a/src/audio_core/command_generator.h +++ /dev/null @@ -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 diff --git a/src/audio_core/common.h b/src/audio_core/common.h deleted file mode 100644 index 056a0ac70..000000000 --- a/src/audio_core/common.h +++ /dev/null @@ -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 diff --git a/src/audio_core/common/audio_renderer_parameter.h b/src/audio_core/common/audio_renderer_parameter.h new file mode 100644 index 000000000..2f62c383b --- /dev/null +++ b/src/audio_core/common/audio_renderer_parameter.h @@ -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 diff --git a/src/audio_core/common/common.h b/src/audio_core/common/common.h new file mode 100644 index 000000000..6abd9be45 --- /dev/null +++ b/src/audio_core/common/common.h @@ -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 diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h new file mode 100644 index 000000000..55c9e690d --- /dev/null +++ b/src/audio_core/common/feature_support.h @@ -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 diff --git a/src/audio_core/common/wave_buffer.h b/src/audio_core/common/wave_buffer.h new file mode 100644 index 000000000..fc478ef79 --- /dev/null +++ b/src/audio_core/common/wave_buffer.h @@ -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 diff --git a/src/audio_core/common/workbuffer_allocator.h b/src/audio_core/common/workbuffer_allocator.h new file mode 100644 index 000000000..fb89f97fe --- /dev/null +++ b/src/audio_core/common/workbuffer_allocator.h @@ -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 diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp deleted file mode 100644 index e48c1ee8e..000000000 --- a/src/audio_core/cubeb_sink.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h deleted file mode 100644 index c124b7ee8..000000000 --- a/src/audio_core/cubeb_sink.h +++ /dev/null @@ -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 diff --git a/src/audio_core/delay_line.cpp b/src/audio_core/delay_line.cpp deleted file mode 100644 index b1626a71b..000000000 --- a/src/audio_core/delay_line.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/delay_line.h b/src/audio_core/delay_line.h deleted file mode 100644 index 05fda536f..000000000 --- a/src/audio_core/delay_line.h +++ /dev/null @@ -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 diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h new file mode 100644 index 000000000..cae7fa970 --- /dev/null +++ b/src/audio_core/device/audio_buffer.h @@ -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 diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h new file mode 100644 index 000000000..5d1979ea0 --- /dev/null +++ b/src/audio_core/device/audio_buffers.h @@ -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 diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp new file mode 100644 index 000000000..095fc96ce --- /dev/null +++ b/src/audio_core/device/device_session.cpp @@ -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 diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h new file mode 100644 index 000000000..4a031b765 --- /dev/null +++ b/src/audio_core/device/device_session.h @@ -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 diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp deleted file mode 100644 index 79bcd1192..000000000 --- a/src/audio_core/effect_context.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h deleted file mode 100644 index cb47df472..000000000 --- a/src/audio_core/effect_context.h +++ /dev/null @@ -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 diff --git a/src/audio_core/in/audio_in.cpp b/src/audio_core/in/audio_in.cpp new file mode 100644 index 000000000..c946895d6 --- /dev/null +++ b/src/audio_core/in/audio_in.cpp @@ -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 diff --git a/src/audio_core/in/audio_in.h b/src/audio_core/in/audio_in.h new file mode 100644 index 000000000..6253891d5 --- /dev/null +++ b/src/audio_core/in/audio_in.h @@ -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 diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp new file mode 100644 index 000000000..ec5d37ed4 --- /dev/null +++ b/src/audio_core/in/audio_in_system.cpp @@ -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 diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h new file mode 100644 index 000000000..165e35d83 --- /dev/null +++ b/src/audio_core/in/audio_in_system.h @@ -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 diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp deleted file mode 100644 index 0065e6e53..000000000 --- a/src/audio_core/info_updater.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h deleted file mode 100644 index 17e66b036..000000000 --- a/src/audio_core/info_updater.h +++ /dev/null @@ -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 diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp deleted file mode 100644 index 627e5f15e..000000000 --- a/src/audio_core/memory_pool.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h deleted file mode 100644 index e71bc025b..000000000 --- a/src/audio_core/memory_pool.h +++ /dev/null @@ -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 diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp deleted file mode 100644 index bcaa7afab..000000000 --- a/src/audio_core/mix_context.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h deleted file mode 100644 index 3939c77e9..000000000 --- a/src/audio_core/mix_context.h +++ /dev/null @@ -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 diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h deleted file mode 100644 index 37b2f7eff..000000000 --- a/src/audio_core/null_sink.h +++ /dev/null @@ -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 diff --git a/src/audio_core/out/audio_out.cpp b/src/audio_core/out/audio_out.cpp new file mode 100644 index 000000000..9a8d8a742 --- /dev/null +++ b/src/audio_core/out/audio_out.cpp @@ -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 diff --git a/src/audio_core/out/audio_out.h b/src/audio_core/out/audio_out.h new file mode 100644 index 000000000..f6b921645 --- /dev/null +++ b/src/audio_core/out/audio_out.h @@ -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 diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp new file mode 100644 index 000000000..35afddf06 --- /dev/null +++ b/src/audio_core/out/audio_out_system.cpp @@ -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 diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h new file mode 100644 index 000000000..4ca2f3417 --- /dev/null +++ b/src/audio_core/out/audio_out_system.h @@ -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 diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp new file mode 100644 index 000000000..e05a22d86 --- /dev/null +++ b/src/audio_core/renderer/adsp/adsp.cpp @@ -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 diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h new file mode 100644 index 000000000..4dfcef4a5 --- /dev/null +++ b/src/audio_core/renderer/adsp/adsp.h @@ -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 diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp new file mode 100644 index 000000000..3967ccfe6 --- /dev/null +++ b/src/audio_core/renderer/adsp/audio_renderer.cpp @@ -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 diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h new file mode 100644 index 000000000..b6ced9d2b --- /dev/null +++ b/src/audio_core/renderer/adsp/audio_renderer.h @@ -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 diff --git a/src/audio_core/renderer/adsp/command_buffer.h b/src/audio_core/renderer/adsp/command_buffer.h new file mode 100644 index 000000000..880b279d8 --- /dev/null +++ b/src/audio_core/renderer/adsp/command_buffer.h @@ -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 diff --git a/src/audio_core/renderer/adsp/command_list_processor.cpp b/src/audio_core/renderer/adsp/command_list_processor.cpp new file mode 100644 index 000000000..e3bf2d7ec --- /dev/null +++ b/src/audio_core/renderer/adsp/command_list_processor.cpp @@ -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 diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h new file mode 100644 index 000000000..3f99173e3 --- /dev/null +++ b/src/audio_core/renderer/adsp/command_list_processor.h @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace Core { +namespace Memory { +class Memory; +} +class System; +} // namespace Core + +namespace AudioCore { +namespace Sink { +class SinkStream; +} + +namespace AudioRenderer { +struct CommandListHeader; + +namespace ADSP { + +/** + * A processor for command lists given to the AudioRenderer. + */ +class CommandListProcessor { +public: + /** + * Initialize the processor. + * + * @param system_ - The core system. + * @param buffer - The command buffer to process. + * @param size - The size of the buffer. + * @param stream_ - The stream to be used for sending the samples. + */ + void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); + + /** + * Set the maximum processing time for this command list. + * + * @param time - The maximum process time. + */ + void SetProcessTimeMax(u64 time); + + /** + * Get the remaining command count for this list. + * + * @return The remaining command count. + */ + u32 GetRemainingCommandCount() const; + + /** + * Set the command buffer. + * + * @param buffer - The buffer to use. + * @param size - The size of the buffer. + */ + void SetBuffer(CpuAddr buffer, u64 size); + + /** + * Get the stream for this command list. + * + * @return The stream associated with this command list. + */ + Sink::SinkStream* GetOutputSinkStream() const; + + /** + * Process the command list. + * + * @param index - Index of the current command list. + * @return The time taken to process. + */ + u64 Process(u32 session_id); + + /// Core system + Core::System* system{}; + /// Core memory + Core::Memory::Memory* memory{}; + /// Stream for the processed samples + Sink::SinkStream* stream{}; + /// Header info for this command list + CommandListHeader* header{}; + /// The command buffer + u8* commands{}; + /// The command buffer size + u64 commands_buffer_size{}; + /// The maximum processing time alloted + u64 max_process_time{}; + /// The number of commands in the buffer + u32 command_count{}; + /// The target sample count for output + u32 sample_count{}; + /// The target sample rate for output + u32 target_sample_rate{}; + /// The mixing buffers used by the commands + std::span<s32> mix_buffers{}; + /// The number of mix buffers + u32 buffer_count{}; + /// The number of processed commands so far + u32 processed_command_count{}; + /// The processing start time of this list + u64 start_time{}; + /// The current processing time for this list + u64 current_processing_time{}; + /// The end processing time for this list + u64 end_time{}; + /// Last command list string generated, used for dumping audio commands to console + std::string last_dump{}; +}; + +} // namespace ADSP +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp new file mode 100644 index 000000000..d5886e55e --- /dev/null +++ b/src/audio_core/renderer/audio_device.cpp @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/audio_core.h" +#include "audio_core/common/feature_support.h" +#include "audio_core/renderer/audio_device.h" +#include "audio_core/sink/sink.h" +#include "core/core.h" + +namespace AudioCore::AudioRenderer { + +AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, + const u32 revision) + : output_sink{system.AudioCore().GetOutputSink()}, + applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} + +u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, + const size_t max_count) { + std::span<AudioDeviceName> names{}; + + if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { + names = usb_device_names; + } else { + names = device_names; + } + + u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; + for (u32 i = 0; i < out_count; i++) { + out_buffer.push_back(names[i]); + } + return out_count; +} + +u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, + const size_t max_count) { + u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; + + for (u32 i = 0; i < out_count; i++) { + out_buffer.push_back(output_device_names[i]); + } + return out_count; +} + +void AudioDevice::SetDeviceVolumes(const f32 volume) { + output_sink.SetDeviceVolume(volume); +} + +f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { + return output_sink.GetDeviceVolume(); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h new file mode 100644 index 000000000..1f449f261 --- /dev/null +++ b/src/audio_core/renderer/audio_device.h @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/audio_render_manager.h" + +namespace Core { +class System; +} + +namespace AudioCore { +namespace Sink { +class Sink; +} + +namespace AudioRenderer { +/** + * An interface to an output audio device available to the Switch. + */ +class AudioDevice { +public: + struct AudioDeviceName { + std::array<char, 0x100> name; + + AudioDeviceName(const char* name_) { + std::strncpy(name.data(), name_, name.size()); + } + }; + + std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput", + "AudioBuiltInSpeakerOutput", "AudioTvOutput", + "AudioUsbDeviceOutput"}; + std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput", + "AudioBuiltInSpeakerOutput", "AudioTvOutput"}; + std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput", + "AudioExternalOutput"}; + + explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); + + /** + * Get a list of the available output devices. + * + * @param out_buffer - Output buffer to write the available device names. + * @param max_count - Maximum number of devices to write (count of out_buffer). + * @return Number of device names written. + */ + u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); + + /** + * Get a list of the available output devices. + * Different to above somehow... + * + * @param out_buffer - Output buffer to write the available device names. + * @param max_count - Maximum number of devices to write (count of out_buffer). + * @return Number of device names written. + */ + u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); + + /** + * Set the volume of all streams in the backend sink. + * + * @param volume - Volume to set. + */ + void SetDeviceVolumes(f32 volume); + + /** + * Get the volume for a given device name. + * Note: This is not fully implemented, we only assume 1 device for all streams. + * + * @param name - Name of the device to check. Unused. + * @return Volume of the device. + */ + f32 GetDeviceVolume(std::string_view name); + +private: + /// Backend output sink for the device + Sink::Sink& output_sink; + /// Resource id this device is used for + const u64 applet_resource_user_id; + /// User audio renderer revision + const u32 user_revision; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/audio_renderer.cpp b/src/audio_core/renderer/audio_renderer.cpp new file mode 100644 index 000000000..51aa17599 --- /dev/null +++ b/src/audio_core/renderer/audio_renderer.cpp @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/audio_render_manager.h" +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/renderer/audio_renderer.h" +#include "audio_core/renderer/system_manager.h" +#include "core/core.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/service/audio/errors.h" + +namespace AudioCore::AudioRenderer { + +Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event) + : core{system_}, manager{manager_}, system{system_, rendered_event} {} + +Result Renderer::Initialize(const AudioRendererParameterInternal& params, + Kernel::KTransferMemory* transfer_memory, + const u64 transfer_memory_size, const u32 process_handle, + const u64 applet_resource_user_id, const s32 session_id) { + if (params.execution_mode == ExecutionMode::Auto) { + if (!manager.AddSystem(system)) { + LOG_ERROR(Service_Audio, + "Both Audio Render sessions are in use, cannot create any more"); + return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED; + } + system_registered = true; + } + + initialized = true; + system.Initialize(params, transfer_memory, transfer_memory_size, process_handle, + applet_resource_user_id, session_id); + + return ResultSuccess; +} + +void Renderer::Finalize() { + auto session_id{system.GetSessionId()}; + + system.Finalize(); + + if (system_registered) { + manager.RemoveSystem(system); + system_registered = false; + } + + manager.ReleaseSessionId(session_id); +} + +System& Renderer::GetSystem() { + return system; +} + +void Renderer::Start() { + system.Start(); +} + +void Renderer::Stop() { + system.Stop(); +} + +Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance, + std::span<u8> output) { + return system.Update(input, performance, output); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/audio_renderer.h b/src/audio_core/renderer/audio_renderer.h new file mode 100644 index 000000000..90c6f9727 --- /dev/null +++ b/src/audio_core/renderer/audio_renderer.h @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/system.h" +#include "core/hle/service/audio/errors.h" + +namespace Core { +class System; +} + +namespace Kernel { +class KTransferMemory; +} + +namespace AudioCore { +struct AudioRendererParameterInternal; + +namespace AudioRenderer { +class Manager; + +/** + * Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls. + */ +class Renderer { +public: + explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event); + + /** + * Initialize the renderer. + * Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes + * everything to a default state. + * + * @param params - Input parameters to initialize the system with. + * @param transfer_memory - Game-supplied memory for all workbuffers. Unused. + * @param transfer_memory_size - Size of the transfer memory. Unused. + * @param process_handle - Process handle, also used for memory. Unused. + * @param applet_resource_user_id - Applet id for this renderer. Unused. + * @param session_id - Session id of this renderer. + * @return Result code. + */ + Result Initialize(const AudioRendererParameterInternal& params, + Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, + u32 process_handle, u64 applet_resource_user_id, s32 session_id); + + /** + * Finalize the renderer for shutdown. + */ + void Finalize(); + + /** + * Get the renderer's system. + * + * @return Reference to the system. + */ + System& GetSystem(); + + /** + * Start the renderer. + */ + void Start(); + + /** + * Stop the renderer. + */ + void Stop(); + + /** + * Update the audio renderer with new information. + * Called via RequestUpdate from the AudRen:U service. + * + * @param input - Input buffer containing the new data. + * @param performance - Optional performance buffer for outputting performance metrics. + * @param output - Output data from the renderer. + * @return Result code. + */ + Result RequestUpdate(std::span<const u8> input, std::span<u8> performance, + std::span<u8> output); + +private: + /// System core + Core::System& core; + /// Manager this renderer is registered with + Manager& manager; + /// Is the audio renderer initialized? + bool initialized{}; + /// Is the system registered with the manager? + bool system_registered{}; + /// Audio render system, main driver of audio rendering + System system; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp new file mode 100644 index 000000000..c5d4d66d8 --- /dev/null +++ b/src/audio_core/renderer/behavior/behavior_info.cpp @@ -0,0 +1,191 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/common/feature_support.h" +#include "audio_core/renderer/behavior/behavior_info.h" + +namespace AudioCore::AudioRenderer { + +BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {} + +u32 BehaviorInfo::GetProcessRevisionNum() const { + return process_revision; +} + +u32 BehaviorInfo::GetProcessRevision() const { + return Common::MakeMagic('R', 'E', 'V', + static_cast<char>(static_cast<u8>('0') + process_revision)); +} + +u32 BehaviorInfo::GetUserRevisionNum() const { + return user_revision; +} + +u32 BehaviorInfo::GetUserRevision() const { + return Common::MakeMagic('R', 'E', 'V', + static_cast<char>(static_cast<u8>('0') + user_revision)); +} + +void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) { + user_revision = GetRevisionNum(user_revision_); +} + +void BehaviorInfo::ClearError() { + error_count = 0; +} + +void BehaviorInfo::AppendError(ErrorInfo& error) { + LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}", + error.error_code.raw, error.address); + if (error_count < MaxErrors) { + errors[error_count++] = error; + } +} + +void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { + auto error_count_{std::min(error_count, MaxErrors)}; + std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo)); + + for (size_t i = 0; i < error_count_; i++) { + out_errors[i] = errors[i]; + } + out_count = error_count_; +} + +void BehaviorInfo::UpdateFlags(const Flags flags_) { + flags = flags_; +} + +bool BehaviorInfo::IsMemoryForceMappingEnabled() const { + return flags.IsMemoryForceMappingEnabled; +} + +bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { + return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision); +} + +bool BehaviorInfo::IsSplitterSupported() const { + return CheckFeatureSupported(SupportTags::Splitter, user_revision); +} + +bool BehaviorInfo::IsSplitterBugFixed() const { + return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision); +} + +bool BehaviorInfo::IsEffectInfoVersion2Supported() const { + return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision); +} + +bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const { + return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize, + user_revision); +} + +bool BehaviorInfo::IsWaveBufferVer2Supported() const { + return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision); +} + +bool BehaviorInfo::IsLongSizePreDelaySupported() const { + return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision); +} + +bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const { + return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2, + user_revision); +} + +bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const { + return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3, + user_revision); +} + +bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const { + return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4, + user_revision); +} + +bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const { + return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4, + user_revision); +} + +bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const { + return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent, + user_revision); +} + +bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const { + return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent, + user_revision); +} + +bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const { + return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent, + user_revision); +} + +bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { + return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision); +} + +bool BehaviorInfo::IsElapsedFrameCountSupported() const { + return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision); +} + +bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const { + return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision); +} + +size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const { + if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) { + return 2; + } + return 1; +} + +bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { + return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision); +} + +bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { + return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint, + user_revision); +} + +bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const { + return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision); +} + +bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const { + return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision); +} + +bool BehaviorInfo::UseBiquadFilterFloatProcessing() const { + return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision); +} + +bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { + return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision); +} + +bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const { + return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision); +} + +bool BehaviorInfo::IsDeviceApiVersion2Supported() const { + return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision); +} + +bool BehaviorInfo::IsDelayChannelMappingChanged() const { + return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision); +} + +bool BehaviorInfo::IsReverbChannelMappingChanged() const { + return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision); +} + +bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const { + return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h new file mode 100644 index 000000000..7333c297f --- /dev/null +++ b/src/audio_core/renderer/behavior/behavior_info.h @@ -0,0 +1,376 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> + +#include "audio_core/common/common.h" +#include "common/common_types.h" +#include "core/hle/service/audio/errors.h" + +namespace AudioCore::AudioRenderer { +/** + * Holds host and user revisions, checks whether render features can be enabled, and reports errors. + */ +class BehaviorInfo { + static constexpr u32 MaxErrors = 10; + +public: + struct ErrorInfo { + /* 0x00 */ Result error_code{0}; + /* 0x04 */ u32 unk_04; + /* 0x08 */ CpuAddr address; + }; + static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!"); + + struct Flags { + u64 IsMemoryForceMappingEnabled : 1; + }; + + struct InParameter { + /* 0x00 */ u32 revision; + /* 0x08 */ Flags flags; + }; + static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!"); + + struct OutStatus { + /* 0x00 */ std::array<ErrorInfo, MaxErrors> errors; + /* 0xA0 */ u32 error_count; + /* 0xA4 */ char unkA4[0xC]; + }; + static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!"); + + BehaviorInfo(); + + /** + * Get the host revision as a number. + * + * @return The host revision. + */ + u32 GetProcessRevisionNum() const; + + /** + * Get the host revision in chars, e.g REV8. + * Rev 10 and higher use the ascii characters above 9. + * E.g: + * Rev 10 = REV: + * Rev 11 = REV; + * + * @return The host revision. + */ + u32 GetProcessRevision() const; + + /** + * Get the user revision as a number. + * + * @return The user revision. + */ + u32 GetUserRevisionNum() const; + + /** + * Get the user revision in chars, e.g REV8. + * Rev 10 and higher use the ascii characters above 9. REV: REV; etc. + * + * @return The user revision. + */ + u32 GetUserRevision() const; + + /** + * Set the user revision. + * + * @param user_revision - The user's revision. + */ + void SetUserLibRevision(u32 user_revision); + + /** + * Clear the current error count. + */ + void ClearError(); + + /** + * Append an error to the error list. + * + * @param error - The new error. + */ + void AppendError(ErrorInfo& error); + + /** + * Copy errors to the given output container. + * + * @param out_errors - Output container to receive the errors. + * @param out_count - The number of errors written. + */ + void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count); + + /** + * Update the behaviour flags. + * + * @param flags - New flags to use. + */ + void UpdateFlags(Flags flags); + + /** + * Check if memory pools can be forcibly mapped. + * + * @return True if enabled, otherwise false. + */ + bool IsMemoryForceMappingEnabled() const; + + /** + * Check if the ADPCM context bug is fixed. + * The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being + * used. + * + * @return True if fixed, otherwise false. + */ + bool IsAdpcmLoopContextBugFixed() const; + + /** + * Check if the splitter is supported. + * + * @return True if supported, otherwise false. + */ + bool IsSplitterSupported() const; + + /** + * Check if the splitter bug is fixed. + * Update is given the wrong number of splitter destinations, leading to invalid data + * being processed. + * + * @return True if supported, otherwise false. + */ + bool IsSplitterBugFixed() const; + + /** + * Check if effects version 2 are supported. + * This gives support for returning effect states from the AudioRenderer, currently only used + * for Limiter statistics. + * + * @return True if supported, otherwise false. + */ + bool IsEffectInfoVersion2Supported() const; + + /** + * Check if a variadic command buffer is supported. + * As of Rev 5 with the added optional performance metric logging, the command + * buffer can be a variable size, so take that into account for calcualting its size. + * + * @return True if supported, otherwise false. + */ + bool IsVariadicCommandBufferSizeSupported() const; + + /** + * Check if wave buffers version 2 are supported. + * See WaveBufferVersion1 and WaveBufferVersion2. + * + * @return True if supported, otherwise false. + */ + bool IsWaveBufferVer2Supported() const; + + /** + * Check if long size pre delay is supported. + * This allows a longer initial delay time for the Reverb command. + * + * @return True if supported, otherwise false. + */ + bool IsLongSizePreDelaySupported() const; + + /** + * Check if the command time estimator version 2 is supported. + * + * @return True if supported, otherwise false. + */ + bool IsCommandProcessingTimeEstimatorVersion2Supported() const; + + /** + * Check if the command time estimator version 3 is supported. + * + * @return True if supported, otherwise false. + */ + bool IsCommandProcessingTimeEstimatorVersion3Supported() const; + + /** + * Check if the command time estimator version 4 is supported. + * + * @return True if supported, otherwise false. + */ + bool IsCommandProcessingTimeEstimatorVersion4Supported() const; + + /** + * Check if the command time estimator version 5 is supported. + * + * @return True if supported, otherwise false. + */ + bool IsCommandProcessingTimeEstimatorVersion5Supported() const; + + /** + * Check if the AudioRenderer can use up to 70% of the allocated processing timeslice. + * + * @return True if supported, otherwise false. + */ + bool IsAudioRendererProcessingTimeLimit70PercentSupported() const; + + /** + * Check if the AudioRenderer can use up to 75% of the allocated processing timeslice. + * + * @return True if supported, otherwise false. + */ + bool IsAudioRendererProcessingTimeLimit75PercentSupported() const; + + /** + * Check if the AudioRenderer can use up to 80% of the allocated processing timeslice. + * + * @return True if supported, otherwise false. + */ + bool IsAudioRendererProcessingTimeLimit80PercentSupported() const; + + /** + * Check if voice flushing is supported + * This allowws low-priority voices to be dropped if the AudioRenderer is running behind. + * + * @return True if supported, otherwise false. + */ + bool IsFlushVoiceWaveBuffersSupported() const; + + /** + * Check if counting the number of elapsed frames is supported. + * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer + * processed a command list. + * + * @return True if supported, otherwise false. + */ + bool IsElapsedFrameCountSupported() const; + + /** + * Check if performance metrics version 2 are supported. + * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer + * (Unused?). + * + * @return True if supported, otherwise false. + */ + bool IsPerformanceMetricsDataFormatVersion2Supported() const; + + /** + * Get the supported performance metrics version. + * Version 2 logs some extra fields in output, such as number of voices dropped, + * processing start time, if the AudioRenderer exceeded its time, etc. + * + * @return Version supported, either 1 or 2. + */ + size_t GetPerformanceMetricsDataFormat() const; + + /** + * Check if skipping voice pitch and sample rate conversion is supported. + * This speeds up the data source commands by skipping resampling if unwanted. + * See AudioCore::AudioRenderer::DecodeFromWaveBuffers + * + * @return True if supported, otherwise false. + */ + bool IsVoicePitchAndSrcSkippedSupported() const; + + /** + * Check if resetting played sample count at loop points is supported. + * This resets the number of samples played in a voice state when a loop point is reached. + * See AudioCore::AudioRenderer::DecodeFromWaveBuffers + * + * @return True if supported, otherwise false. + */ + bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; + + /** + * Check if the clear state bug for biquad filters is fixed. + * The biquad state was not marked as needing re-initialisation when the effect was updated, it + * was only initialized once with a new effect. + * + * @return True if fixed, otherwise false. + */ + bool IsBiquadFilterEffectStateClearBugFixed() const; + + /** + * Check if Q23 precision is supported for fixed point. + * + * @return True if supported, otherwise false. + */ + bool IsVolumeMixParameterPrecisionQ23Supported() const; + + /** + * Check if float processing for biuad filters is supported. + * + * @return True if supported, otherwise false. + */ + bool UseBiquadFilterFloatProcessing() const; + + /** + * Check if dirty-only mix updates are supported. + * This saves a lot of buffer size as mixes can be large and not change much. + * + * @return True if supported, otherwise false. + */ + bool IsMixInParameterDirtyOnlyUpdateSupported() const; + + /** + * Check if multi-tap biquad filters are supported. + * + * @return True if supported, otherwise false. + */ + bool UseMultiTapBiquadFilterProcessing() const; + + /** + * Check if device api version 2 is supported. + * In the SDK but not in any sysmodule? Not sure, left here for completeness anyway. + * + * @return True if supported, otherwise false. + */ + bool IsDeviceApiVersion2Supported() const; + + /** + * Check if new channel mappings are used for Delay commands. + * Older commands used: + * front left/front right/back left/back right/center/lfe + * Whereas everywhere else in the code uses: + * front left/front right/center/lfe/back left/back right + * This corrects that and makes everything standardised. + * + * @return True if supported, otherwise false. + */ + bool IsDelayChannelMappingChanged() const; + + /** + * Check if new channel mappings are used for Reverb commands. + * Older commands used: + * front left/front right/back left/back right/center/lfe + * Whereas everywhere else in the code uses: + * front left/front right/center/lfe/back left/back right + * This corrects that and makes everything standardised. + * + * @return True if supported, otherwise false. + */ + bool IsReverbChannelMappingChanged() const; + + /** + * Check if new channel mappings are used for I3dl2Reverb commands. + * Older commands used: + * front left/front right/back left/back right/center/lfe + * Whereas everywhere else in the code uses: + * front left/front right/center/lfe/back left/back right + * This corrects that and makes everything standardised. + * + * @return True if supported, otherwise false. + */ + bool IsI3dl2ReverbChannelMappingChanged() const; + + /// Host version + u32 process_revision; + /// User version + u32 user_revision{}; + /// Behaviour flags + Flags flags{}; + /// Errors generated and reported during Update + std::array<ErrorInfo, MaxErrors> errors{}; + /// Error count + u32 error_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp new file mode 100644 index 000000000..06a37e1a6 --- /dev/null +++ b/src/audio_core/renderer/behavior/info_updater.cpp @@ -0,0 +1,539 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/common/feature_support.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/behavior/info_updater.h" +#include "audio_core/renderer/effect/effect_context.h" +#include "audio_core/renderer/effect/effect_reset.h" +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "audio_core/renderer/mix/mix_context.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "audio_core/renderer/sink/circular_buffer_sink_info.h" +#include "audio_core/renderer/sink/device_sink_info.h" +#include "audio_core/renderer/sink/sink_context.h" +#include "audio_core/renderer/splitter/splitter_context.h" +#include "audio_core/renderer/voice/voice_context.h" + +namespace AudioCore::AudioRenderer { + +InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_, + const u32 process_handle_, BehaviorInfo& behaviour_) + : input{input_.data() + sizeof(UpdateDataHeader)}, + input_origin{input_}, output{output_.data() + sizeof(UpdateDataHeader)}, + output_origin{output_}, in_header{reinterpret_cast<const UpdateDataHeader*>( + input_origin.data())}, + out_header{reinterpret_cast<UpdateDataHeader*>(output_origin.data())}, + expected_input_size{input_.size()}, expected_output_size{output_.size()}, + process_handle{process_handle_}, behaviour{behaviour_} { + std::construct_at<UpdateDataHeader>(out_header, behaviour.GetProcessRevision()); +} + +Result InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { + const auto voice_count{voice_context.GetCount()}; + std::span<const VoiceChannelResource::InParameter> in_params{ + reinterpret_cast<const VoiceChannelResource::InParameter*>(input), voice_count}; + + for (u32 i = 0; i < voice_count; i++) { + auto& resource{voice_context.GetChannelResource(i)}; + resource.in_use = in_params[i].in_use; + if (in_params[i].in_use) { + resource.mix_volumes = in_params[i].mix_volumes; + } + } + + const auto consumed_input_size{voice_count * + static_cast<u32>(sizeof(VoiceChannelResource::InParameter))}; + if (consumed_input_size != in_header->voice_resources_size) { + LOG_ERROR(Service_Audio, + "Consumed an incorrect voice resource size, header size={}, consumed={}", + in_header->voice_resources_size, consumed_input_size); + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + input += consumed_input_size; + return ResultSuccess; +} + +Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, + std::span<MemoryPoolInfo> memory_pools, + const u32 memory_pool_count) { + const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, + behaviour.IsMemoryForceMappingEnabled()); + const auto voice_count{voice_context.GetCount()}; + std::span<const VoiceInfo::InParameter> in_params{ + reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count}; + std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output), + voice_count}; + + for (u32 i = 0; i < voice_count; i++) { + auto& voice_info{voice_context.GetInfo(i)}; + voice_info.in_use = false; + } + + u32 new_voice_count{0}; + + for (u32 i = 0; i < voice_count; i++) { + const auto& in_param{in_params[i]}; + std::array<VoiceState*, MaxChannels> voice_states{}; + + if (!in_param.in_use) { + continue; + } + + auto& voice_info{voice_context.GetInfo(in_param.id)}; + + for (u32 channel = 0; channel < in_param.channel_count; channel++) { + voice_states[channel] = &voice_context.GetState(in_param.channel_resource_ids[channel]); + } + + if (in_param.is_new) { + voice_info.Initialize(); + + for (u32 channel = 0; channel < in_param.channel_count; channel++) { + std::memset(voice_states[channel], 0, sizeof(VoiceState)); + } + } + + BehaviorInfo::ErrorInfo update_error{}; + voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour); + + if (!update_error.error_code.IsSuccess()) { + behaviour.AppendError(update_error); + } + + std::array<std::array<BehaviorInfo::ErrorInfo, 2>, MaxWaveBuffers> wavebuffer_errors{}; + voice_info.UpdateWaveBuffers(wavebuffer_errors, MaxWaveBuffers * 2, in_param, voice_states, + pool_mapper, behaviour); + + for (auto& wavebuffer_error : wavebuffer_errors) { + for (auto& error : wavebuffer_error) { + if (error.error_code.IsError()) { + behaviour.AppendError(error); + } + } + } + + voice_info.WriteOutStatus(out_params[i], in_param, voice_states); + new_voice_count += in_param.channel_count; + } + + auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))}; + auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))}; + if (consumed_input_size != in_header->voices_size) { + LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}", + in_header->voices_size, consumed_input_size); + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + out_header->voices_size = consumed_output_size; + out_header->size += consumed_output_size; + input += consumed_input_size; + output += consumed_output_size; + + voice_context.SetActiveCount(new_voice_count); + + return ResultSuccess; +} + +Result InfoUpdater::UpdateEffects(EffectContext& effect_context, const bool renderer_active, + std::span<MemoryPoolInfo> memory_pools, + const u32 memory_pool_count) { + if (behaviour.IsEffectInfoVersion2Supported()) { + return UpdateEffectsVersion2(effect_context, renderer_active, memory_pools, + memory_pool_count); + } else { + return UpdateEffectsVersion1(effect_context, renderer_active, memory_pools, + memory_pool_count); + } +} + +Result InfoUpdater::UpdateEffectsVersion1(EffectContext& effect_context, const bool renderer_active, + std::span<MemoryPoolInfo> memory_pools, + const u32 memory_pool_count) { + PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, + behaviour.IsMemoryForceMappingEnabled()); + + const auto effect_count{effect_context.GetCount()}; + + std::span<const EffectInfoBase::InParameterVersion1> in_params{ + reinterpret_cast<const EffectInfoBase::InParameterVersion1*>(input), effect_count}; + std::span<EffectInfoBase::OutStatusVersion1> out_params{ + reinterpret_cast<EffectInfoBase::OutStatusVersion1*>(output), effect_count}; + + for (u32 i = 0; i < effect_count; i++) { + auto effect_info{&effect_context.GetInfo(i)}; + if (effect_info->GetType() != in_params[i].type) { + effect_info->ForceUnmapBuffers(pool_mapper); + ResetEffect(effect_info, in_params[i].type); + } + + BehaviorInfo::ErrorInfo error_info{}; + effect_info->Update(error_info, in_params[i], pool_mapper); + if (error_info.error_code.IsError()) { + behaviour.AppendError(error_info); + } + + effect_info->StoreStatus(out_params[i], renderer_active); + } + + auto consumed_input_size{effect_count * + static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion1))}; + auto consumed_output_size{effect_count * + static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion1))}; + if (consumed_input_size != in_header->effects_size) { + LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}", + in_header->effects_size, consumed_input_size); + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + out_header->effects_size = consumed_output_size; + out_header->size += consumed_output_size; + input += consumed_input_size; + output += consumed_output_size; + + return ResultSuccess; +} + +Result InfoUpdater::UpdateEffectsVersion2(EffectContext& effect_context, const bool renderer_active, + std::span<MemoryPoolInfo> memory_pools, + const u32 memory_pool_count) { + PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, + behaviour.IsMemoryForceMappingEnabled()); + + const auto effect_count{effect_context.GetCount()}; + + std::span<const EffectInfoBase::InParameterVersion2> in_params{ + reinterpret_cast<const EffectInfoBase::InParameterVersion2*>(input), effect_count}; + std::span<EffectInfoBase::OutStatusVersion2> out_params{ + reinterpret_cast<EffectInfoBase::OutStatusVersion2*>(output), effect_count}; + + for (u32 i = 0; i < effect_count; i++) { + auto effect_info{&effect_context.GetInfo(i)}; + if (effect_info->GetType() != in_params[i].type) { + effect_info->ForceUnmapBuffers(pool_mapper); + ResetEffect(effect_info, in_params[i].type); + } + + BehaviorInfo::ErrorInfo error_info{}; + effect_info->Update(error_info, in_params[i], pool_mapper); + + if (error_info.error_code.IsError()) { + behaviour.AppendError(error_info); + } + + effect_info->StoreStatus(out_params[i], renderer_active); + + if (in_params[i].is_new) { + effect_info->InitializeResultState(effect_context.GetDspSharedResultState(i)); + effect_info->InitializeResultState(effect_context.GetResultState(i)); + } + effect_info->UpdateResultState(out_params[i].result_state, + effect_context.GetResultState(i)); + } + + auto consumed_input_size{effect_count * + static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion2))}; + auto consumed_output_size{effect_count * + static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion2))}; + if (consumed_input_size != in_header->effects_size) { + LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}", + in_header->effects_size, consumed_input_size); + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + out_header->effects_size = consumed_output_size; + out_header->size += consumed_output_size; + input += consumed_input_size; + output += consumed_output_size; + + return ResultSuccess; +} + +Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_count, + EffectContext& effect_context, SplitterContext& splitter_context) { + s32 mix_count{0}; + u32 consumed_input_size{0}; + + if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { + auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)}; + mix_count = in_dirty_params->count; + input += sizeof(MixInfo::InDirtyParameter); + consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) + + mix_count * sizeof(MixInfo::InParameter)); + } else { + mix_count = mix_context.GetCount(); + consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter)); + } + + if (mix_buffer_count == 0) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + std::span<const MixInfo::InParameter> in_params{ + reinterpret_cast<const MixInfo::InParameter*>(input), static_cast<size_t>(mix_count)}; + + u32 total_buffer_count{0}; + for (s32 i = 0; i < mix_count; i++) { + const auto& params{in_params[i]}; + + if (params.in_use) { + total_buffer_count += params.buffer_count; + if (params.dest_mix_id > static_cast<s32>(mix_context.GetCount()) && + params.dest_mix_id != UnusedMixId && params.mix_id != FinalMixId) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + } + } + + if (total_buffer_count > mix_buffer_count) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + bool mix_dirty{false}; + for (s32 i = 0; i < mix_count; i++) { + const auto& params{in_params[i]}; + + s32 mix_id{i}; + if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { + mix_id = params.mix_id; + } + + auto mix_info{mix_context.GetInfo(mix_id)}; + if (mix_info->in_use != params.in_use) { + mix_info->in_use = params.in_use; + if (!params.in_use) { + mix_info->ClearEffectProcessingOrder(); + } + mix_dirty = true; + } + + if (params.in_use) { + mix_dirty |= mix_info->Update(mix_context.GetEdgeMatrix(), params, effect_context, + splitter_context, behaviour); + } + } + + if (mix_dirty) { + if (behaviour.IsSplitterSupported() && splitter_context.UsingSplitter()) { + if (!mix_context.TSortInfo(splitter_context)) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + } else { + mix_context.SortInfo(); + } + } + + if (consumed_input_size != in_header->mix_size) { + LOG_ERROR(Service_Audio, "Consumed an incorrect mixes size, header size={}, consumed={}", + in_header->mix_size, consumed_input_size); + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + input += mix_count * sizeof(MixInfo::InParameter); + + return ResultSuccess; +} + +Result InfoUpdater::UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools, + const u32 memory_pool_count) { + PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, + behaviour.IsMemoryForceMappingEnabled()); + + std::span<const SinkInfoBase::InParameter> in_params{ + reinterpret_cast<const SinkInfoBase::InParameter*>(input), memory_pool_count}; + std::span<SinkInfoBase::OutStatus> out_params{ + reinterpret_cast<SinkInfoBase::OutStatus*>(output), memory_pool_count}; + + const auto sink_count{sink_context.GetCount()}; + + for (u32 i = 0; i < sink_count; i++) { + const auto& params{in_params[i]}; + auto sink_info{sink_context.GetInfo(i)}; + + if (sink_info->GetType() != params.type) { + sink_info->CleanUp(); + switch (params.type) { + case SinkInfoBase::Type::Invalid: + std::construct_at<SinkInfoBase>(reinterpret_cast<SinkInfoBase*>(sink_info)); + break; + case SinkInfoBase::Type::DeviceSink: + std::construct_at<DeviceSinkInfo>(reinterpret_cast<DeviceSinkInfo*>(sink_info)); + break; + case SinkInfoBase::Type::CircularBufferSink: + std::construct_at<CircularBufferSinkInfo>( + reinterpret_cast<CircularBufferSinkInfo*>(sink_info)); + break; + default: + LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(params.type)); + break; + } + } + + BehaviorInfo::ErrorInfo error_info{}; + sink_info->Update(error_info, out_params[i], params, pool_mapper); + + if (error_info.error_code.IsError()) { + behaviour.AppendError(error_info); + } + } + + const auto consumed_input_size{sink_count * + static_cast<u32>(sizeof(SinkInfoBase::InParameter))}; + const auto consumed_output_size{sink_count * static_cast<u32>(sizeof(SinkInfoBase::OutStatus))}; + if (consumed_input_size != in_header->sinks_size) { + LOG_ERROR(Service_Audio, "Consumed an incorrect sinks size, header size={}, consumed={}", + in_header->sinks_size, consumed_input_size); + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + input += consumed_input_size; + output += consumed_output_size; + out_header->sinks_size = consumed_output_size; + out_header->size += consumed_output_size; + + return ResultSuccess; +} + +Result InfoUpdater::UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, + const u32 memory_pool_count) { + PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, + behaviour.IsMemoryForceMappingEnabled()); + std::span<const MemoryPoolInfo::InParameter> in_params{ + reinterpret_cast<const MemoryPoolInfo::InParameter*>(input), memory_pool_count}; + std::span<MemoryPoolInfo::OutStatus> out_params{ + reinterpret_cast<MemoryPoolInfo::OutStatus*>(output), memory_pool_count}; + + for (size_t i = 0; i < memory_pool_count; i++) { + auto state{pool_mapper.Update(memory_pools[i], in_params[i], out_params[i])}; + if (state != MemoryPoolInfo::ResultState::Success && + state != MemoryPoolInfo::ResultState::BadParam && + state != MemoryPoolInfo::ResultState::MapFailed && + state != MemoryPoolInfo::ResultState::InUse) { + LOG_WARNING(Service_Audio, "Invalid ResultState from updating memory pools"); + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + } + + const auto consumed_input_size{memory_pool_count * + static_cast<u32>(sizeof(MemoryPoolInfo::InParameter))}; + const auto consumed_output_size{memory_pool_count * + static_cast<u32>(sizeof(MemoryPoolInfo::OutStatus))}; + if (consumed_input_size != in_header->memory_pool_size) { + LOG_ERROR(Service_Audio, + "Consumed an incorrect memory pool size, header size={}, consumed={}", + in_header->memory_pool_size, consumed_input_size); + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + input += consumed_input_size; + output += consumed_output_size; + out_header->memory_pool_size = consumed_output_size; + out_header->size += consumed_output_size; + return ResultSuccess; +} + +Result InfoUpdater::UpdatePerformanceBuffer(std::span<u8> performance_output, + const u64 performance_output_size, + PerformanceManager* performance_manager) { + auto in_params{reinterpret_cast<const PerformanceManager::InParameter*>(input)}; + auto out_params{reinterpret_cast<PerformanceManager::OutStatus*>(output)}; + + if (performance_manager != nullptr) { + out_params->history_size = + performance_manager->CopyHistories(performance_output.data(), performance_output_size); + performance_manager->SetDetailTarget(in_params->target_node_id); + } else { + out_params->history_size = 0; + } + + const auto consumed_input_size{static_cast<u32>(sizeof(PerformanceManager::InParameter))}; + const auto consumed_output_size{static_cast<u32>(sizeof(PerformanceManager::OutStatus))}; + if (consumed_input_size != in_header->performance_buffer_size) { + LOG_ERROR(Service_Audio, + "Consumed an incorrect performance size, header size={}, consumed={}", + in_header->performance_buffer_size, consumed_input_size); + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + input += consumed_input_size; + output += consumed_output_size; + out_header->performance_buffer_size = consumed_output_size; + out_header->size += consumed_output_size; + return ResultSuccess; +} + +Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) { + const auto in_params{reinterpret_cast<const BehaviorInfo::InParameter*>(input)}; + + if (!CheckValidRevision(in_params->revision)) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + if (in_params->revision != behaviour_.GetUserRevision()) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + behaviour_.ClearError(); + behaviour_.UpdateFlags(in_params->flags); + + if (in_header->behaviour_size != sizeof(BehaviorInfo::InParameter)) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + input += sizeof(BehaviorInfo::InParameter); + return ResultSuccess; +} + +Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) { + auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)}; + behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); + + const auto consumed_output_size{static_cast<u32>(sizeof(BehaviorInfo::OutStatus))}; + + output += consumed_output_size; + out_header->behaviour_size = consumed_output_size; + out_header->size += consumed_output_size; + return ResultSuccess; +} + +Result InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) { + u32 consumed_size{0}; + if (!splitter_context.Update(input, consumed_size)) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + input += consumed_size; + + return ResultSuccess; +} + +Result InfoUpdater::UpdateRendererInfo(const u64 elapsed_frames) { + struct RenderInfo { + /* 0x00 */ u64 frames_elapsed; + /* 0x08 */ char unk08[0x8]; + }; + static_assert(sizeof(RenderInfo) == 0x10, "RenderInfo has the wrong size!"); + + auto out_params{reinterpret_cast<RenderInfo*>(output)}; + out_params->frames_elapsed = elapsed_frames; + + const auto consumed_output_size{static_cast<u32>(sizeof(RenderInfo))}; + + output += consumed_output_size; + out_header->render_info_size = consumed_output_size; + out_header->size += consumed_output_size; + + return ResultSuccess; +} + +Result InfoUpdater::CheckConsumedSize() { + if (CpuAddr(input) - CpuAddr(input_origin.data()) != expected_input_size) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } else if (CpuAddr(output) - CpuAddr(output_origin.data()) != expected_output_size) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + return ResultSuccess; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h new file mode 100644 index 000000000..f0b445d9c --- /dev/null +++ b/src/audio_core/renderer/behavior/info_updater.h @@ -0,0 +1,205 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "common/common_types.h" +#include "core/hle/service/audio/errors.h" + +namespace AudioCore::AudioRenderer { +class BehaviorInfo; +class VoiceContext; +class MixContext; +class SinkContext; +class SplitterContext; +class EffectContext; +class MemoryPoolInfo; +class PerformanceManager; + +class InfoUpdater { + struct UpdateDataHeader { + explicit UpdateDataHeader(u32 revision_) : revision{revision_} {} + + /* 0x00 */ u32 revision; + /* 0x04 */ u32 behaviour_size{}; + /* 0x08 */ u32 memory_pool_size{}; + /* 0x0C */ u32 voices_size{}; + /* 0x10 */ u32 voice_resources_size{}; + /* 0x14 */ u32 effects_size{}; + /* 0x18 */ u32 mix_size{}; + /* 0x1C */ u32 sinks_size{}; + /* 0x20 */ u32 performance_buffer_size{}; + /* 0x24 */ char unk24[4]; + /* 0x28 */ u32 render_info_size{}; + /* 0x2C */ char unk2C[0x10]; + /* 0x3C */ u32 size{sizeof(UpdateDataHeader)}; + }; + static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!"); + +public: + explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle, + BehaviorInfo& behaviour); + + /** + * Update the voice channel resources. + * + * @param voice_context - Voice context to update. + * @return Result code. + */ + Result UpdateVoiceChannelResources(VoiceContext& voice_context); + + /** + * Update voices. + * + * @param voice_context - Voice context to update. + * @param memory_pools - Memory pools to use for these voices. + * @param memory_pool_count - Number of memory pools. + * @return Result code. + */ + Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools, + u32 memory_pool_count); + + /** + * Update effects. + * + * @param effect_context - Effect context to update. + * @param renderer_active - Whether the AudioRenderer is active. + * @param memory_pools - Memory pools to use for these voices. + * @param memory_pool_count - Number of memory pools. + * @return Result code. + */ + Result UpdateEffects(EffectContext& effect_context, bool renderer_active, + std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); + + /** + * Update mixes. + * + * @param mix_context - Mix context to update. + * @param mix_buffer_count - Number of mix buffers. + * @param effect_context - Effect context to update effort order. + * @param splitter_context - Splitter context for the mixes. + * @return Result code. + */ + Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context, + SplitterContext& splitter_context); + + /** + * Update sinks. + * + * @param sink_context - Sink context to update. + * @param memory_pools - Memory pools to use for these voices. + * @param memory_pool_count - Number of memory pools. + * @return Result code. + */ + Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools, + u32 memory_pool_count); + + /** + * Update memory pools. + * + * @param memory_pools - Memory pools to use for these voices. + * @param memory_pool_count - Number of memory pools. + * @return Result code. + */ + Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); + + /** + * Update the performance buffer. + * + * @param output - Output buffer for performance metrics. + * @param output_size - Output buffer size. + * @param performance_manager - Performance manager.. + * @return Result code. + */ + Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size, + PerformanceManager* performance_manager); + + /** + * Update behaviour. + * + * @param behaviour - Behaviour to update. + * @return Result code. + */ + Result UpdateBehaviorInfo(BehaviorInfo& behaviour); + + /** + * Update errors. + * + * @param behaviour - Behaviour to update. + * @return Result code. + */ + Result UpdateErrorInfo(BehaviorInfo& behaviour); + + /** + * Update splitter. + * + * @param splitter_context - Splitter context to update. + * @return Result code. + */ + Result UpdateSplitterInfo(SplitterContext& splitter_context); + + /** + * Update renderer info. + * + * @param elapsed_frames - Number of elapsed frames. + * @return Result code. + */ + Result UpdateRendererInfo(u64 elapsed_frames); + + /** + * Check that the input.output sizes match their expected values. + * + * @return Result code. + */ + Result CheckConsumedSize(); + +private: + /** + * Update effects version 1. + * + * @param effect_context - Effect context to update. + * @param renderer_active - Is the AudioRenderer active? + * @param memory_pools - Memory pools to use for these voices. + * @param memory_pool_count - Number of memory pools. + * @return Result code. + */ + Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active, + std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); + + /** + * Update effects version 2. + * + * @param effect_context - Effect context to update. + * @param renderer_active - Is the AudioRenderer active? + * @param memory_pools - Memory pools to use for these voices. + * @param memory_pool_count - Number of memory pools. + * @return Result code. + */ + Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active, + std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); + + /// Input buffer + u8 const* input; + /// Input buffer start + std::span<const u8> input_origin; + /// Output buffer start + u8* output; + /// Output buffer start + std::span<u8> output_origin; + /// Input header + const UpdateDataHeader* in_header; + /// Output header + UpdateDataHeader* out_header; + /// Expected input size, see CheckConsumedSize + u64 expected_input_size; + /// Expected output size, see CheckConsumedSize + u64 expected_output_size; + /// Unused + u32 process_handle; + /// Behaviour + BehaviorInfo& behaviour; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp new file mode 100644 index 000000000..40074cf14 --- /dev/null +++ b/src/audio_core/renderer/command/command_buffer.cpp @@ -0,0 +1,714 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_list_header.h" +#include "audio_core/renderer/command/command_processing_time_estimator.h" +#include "audio_core/renderer/effect/biquad_filter.h" +#include "audio_core/renderer/effect/delay.h" +#include "audio_core/renderer/effect/reverb.h" +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/sink/circular_buffer_sink_info.h" +#include "audio_core/renderer/sink/device_sink_info.h" +#include "audio_core/renderer/sink/sink_info_base.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "audio_core/renderer/voice/voice_state.h" + +namespace AudioCore::AudioRenderer { + +template <typename T, CommandId Id> +T& CommandBuffer::GenerateStart(const s32 node_id) { + if (size + sizeof(T) >= command_list.size_bytes()) { + LOG_ERROR( + Service_Audio, + "Attempting to write commands beyond the end of allocated command buffer memory!"); + UNREACHABLE(); + } + + auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))}; + + cmd.magic = CommandMagic; + cmd.enabled = true; + cmd.type = Id; + cmd.size = sizeof(T); + cmd.node_id = node_id; + + return cmd; +} + +template <typename T> +void CommandBuffer::GenerateEnd(T& cmd) { + cmd.estimated_process_time = time_estimator->Estimate(cmd); + estimated_process_time += cmd.estimated_process_time; + size += sizeof(T); + count++; +} + +void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id, + const MemoryPoolInfo& memory_pool_, + VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>( + node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + cmd.channel_index = channel; + cmd.channel_count = voice_info.channel_count; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + + GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd); +} + +void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>( + node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + cmd.channel_index = channel; + cmd.channel_count = voice_info.channel_count; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + + GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd); +} + +void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id, + const MemoryPoolInfo& memory_pool_, + VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>( + node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + cmd.channel_index = channel; + cmd.channel_count = voice_info.channel_count; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + + GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd); +} + +void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>( + node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + cmd.channel_index = channel; + cmd.channel_count = voice_info.channel_count; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + + GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd); +} + +void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id, + const MemoryPoolInfo& memory_pool_, + VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + cmd.data_address = voice_info.data_address.GetReference(true); + cmd.data_size = voice_info.data_address.GetSize(); + + GenerateEnd<AdpcmDataSourceVersion1Command>(cmd); +} + +void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + cmd.channel_index = channel; + cmd.channel_count = voice_info.channel_count; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + cmd.data_address = voice_info.data_address.GetReference(true); + cmd.data_size = voice_info.data_address.GetSize(); + + GenerateEnd<AdpcmDataSourceVersion2Command>(cmd); +} + +void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset, + const s16 input_index, const f32 volume, + const u8 precision) { + auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)}; + + cmd.precision = precision; + cmd.input_index = buffer_offset + input_index; + cmd.output_index = buffer_offset + input_index; + cmd.volume = volume; + + GenerateEnd<VolumeCommand>(cmd); +} + +void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info, + const s16 buffer_count, const u8 precision) { + auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)}; + + cmd.input_index = buffer_count; + cmd.output_index = buffer_count; + cmd.prev_volume = voice_info.prev_volume; + cmd.volume = voice_info.volume; + cmd.precision = precision; + + GenerateEnd<VolumeRampCommand>(cmd); +} + +void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel, + const u32 biquad_index, + const bool use_float_processing) { + auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; + + cmd.input = buffer_count + channel; + cmd.output = buffer_count + channel; + + cmd.biquad = voice_info.biquads[biquad_index]; + + cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()), + MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); + + cmd.needs_init = !voice_info.biquad_initialized[biquad_index]; + cmd.use_float_processing = use_float_processing; + + GenerateEnd<BiquadFilterCommand>(cmd); +} + +void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset, const s8 channel, + const bool needs_init, + const bool use_float_processing) { + auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; + + const auto& parameter{ + *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; + const auto state{ + reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())}; + + cmd.input = buffer_offset + parameter.inputs[channel]; + cmd.output = buffer_offset + parameter.outputs[channel]; + + cmd.biquad.b = parameter.b; + cmd.biquad.a = parameter.a; + + cmd.state = memory_pool->Translate(CpuAddr(state), + MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); + + cmd.needs_init = needs_init; + cmd.use_float_processing = use_float_processing; + + GenerateEnd<BiquadFilterCommand>(cmd); +} + +void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index, + const s16 output_index, const s16 buffer_offset, + const f32 volume, const u8 precision) { + auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)}; + + cmd.input_index = input_index; + cmd.output_index = output_index; + cmd.volume = volume; + cmd.precision = precision; + + GenerateEnd<MixCommand>(cmd); +} + +void CommandBuffer::GenerateMixRampCommand(const s32 node_id, + [[maybe_unused]] const s16 buffer_count, + const s16 input_index, const s16 output_index, + const f32 volume, const f32 prev_volume, + const CpuAddr prev_samples, const u8 precision) { + if (volume == 0.0f && prev_volume == 0.0f) { + return; + } + + auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)}; + + cmd.input_index = input_index; + cmd.output_index = output_index; + cmd.prev_volume = prev_volume; + cmd.volume = volume; + cmd.previous_sample = prev_samples; + cmd.precision = precision; + + GenerateEnd<MixRampCommand>(cmd); +} + +void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count, + const s16 input_index, s16 output_index, + std::span<const f32> volumes, + std::span<const f32> prev_volumes, + const CpuAddr prev_samples, const u8 precision) { + auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)}; + + cmd.buffer_count = buffer_count; + + for (s32 i = 0; i < buffer_count; i++) { + cmd.inputs[i] = input_index; + cmd.outputs[i] = output_index++; + cmd.prev_volumes[i] = prev_volumes[i]; + cmd.volumes[i] = volumes[i]; + } + + cmd.previous_samples = prev_samples; + cmd.precision = precision; + + GenerateEnd<MixRampGroupedCommand>(cmd); +} + +void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state, + std::span<const s32> buffer, const s16 buffer_count, + s16 buffer_offset, const bool was_playing) { + auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)}; + + cmd.enabled = was_playing; + + for (u32 i = 0; i < MaxMixBuffers; i++) { + cmd.inputs[i] = buffer_offset++; + } + + cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()), + MaxMixBuffers * sizeof(s32)); + cmd.buffer_count = buffer_count; + cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32)); + + GenerateEnd<DepopPrepareCommand>(cmd); +} + +void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info, + std::span<const s32> depop_buffer) { + auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)}; + + cmd.input = mix_info.buffer_offset; + cmd.count = mix_info.buffer_count; + cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f; + cmd.depop_buffer = + memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32)); + + GenerateEnd<DepopForMixBuffersCommand>(cmd); +} + +void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset) { + auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)}; + + const auto& parameter{ + *reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())}; + const auto state{effect_info.GetStateBuffer()}; + + if (IsChannelCountValid(parameter.channel_count)) { + const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))}; + if (state_buffer) { + for (s16 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + + if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) { + UseOldChannelMapping(cmd.inputs, cmd.outputs); + } + + cmd.parameter = parameter; + cmd.effect_enabled = effect_info.IsEnabled(); + cmd.state = state_buffer; + cmd.workbuffer = effect_info.GetWorkbuffer(-1); + } + } + + GenerateEnd<DelayCommand>(cmd); +} + +void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset, + UpsamplerInfo& upsampler_info, const u32 input_count, + std::span<const s8> inputs, const s16 buffer_count, + const u32 sample_count_, const u32 sample_rate_) { + auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)}; + + cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos, + upsampler_info.sample_count * sizeof(s32)); + cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels); + cmd.buffer_count = buffer_count; + cmd.unk_20 = 0; + cmd.source_sample_count = sample_count_; + cmd.source_sample_rate = sample_rate_; + + upsampler_info.input_count = input_count; + for (u32 i = 0; i < input_count; i++) { + upsampler_info.inputs[i] = buffer_offset + inputs[i]; + } + + cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo)); + + GenerateEnd<UpsampleCommand>(cmd); +} + +void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs, + const s16 buffer_offset, + std::span<const f32> downmix_coeff) { + auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)}; + + for (u32 i = 0; i < MaxChannels; i++) { + cmd.inputs[i] = buffer_offset + inputs[i]; + cmd.outputs[i] = buffer_offset + inputs[i]; + } + + for (u32 i = 0; i < 4; i++) { + cmd.down_mix_coeff[i] = downmix_coeff[i]; + } + + GenerateEnd<DownMix6chTo2chCommand>(cmd); +} + +void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 input_index, const s16 output_index, + const s16 buffer_offset, const u32 update_count, + const u32 count_max, const u32 write_offset) { + auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)}; + + if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { + cmd.input = buffer_offset + input_index; + cmd.output = buffer_offset + output_index; + cmd.send_buffer_info = effect_info.GetSendBufferInfo(); + cmd.send_buffer = effect_info.GetSendBuffer(); + cmd.return_buffer_info = effect_info.GetReturnBufferInfo(); + cmd.return_buffer = effect_info.GetReturnBuffer(); + cmd.count_max = count_max; + cmd.write_offset = write_offset; + cmd.update_count = update_count; + cmd.effect_enabled = effect_info.IsEnabled(); + } + + GenerateEnd<AuxCommand>(cmd); +} + +void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset, + SinkInfoBase& sink_info, const u32 session_id, + std::span<s32> samples_buffer) { + auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)}; + const auto& parameter{ + *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())}; + auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())}; + + cmd.session_id = session_id; + + if (state.upsampler_info != nullptr) { + const auto size_{state.upsampler_info->sample_count * parameter.input_count}; + const auto size_bytes{size_ * sizeof(s32)}; + const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)}; + cmd.sample_buffer = {reinterpret_cast<s32*>(addr), + parameter.input_count * state.upsampler_info->sample_count}; + } else { + cmd.sample_buffer = samples_buffer; + } + + cmd.input_count = parameter.input_count; + for (u32 i = 0; i < parameter.input_count; i++) { + cmd.inputs[i] = buffer_offset + parameter.inputs[i]; + } + + GenerateEnd<DeviceSinkCommand>(cmd); +} + +void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info, + const s16 buffer_offset) { + auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)}; + const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>( + sink_info.GetParameter())}; + auto state{ + *reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())}; + + cmd.input_count = parameter.input_count; + for (u32 i = 0; i < parameter.input_count; i++) { + cmd.inputs[i] = buffer_offset + parameter.inputs[i]; + } + + cmd.address = state.address_info.GetReference(true); + cmd.size = parameter.size; + cmd.pos = state.current_pos; + + GenerateEnd<CircularBufferSinkCommand>(cmd); +} + +void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset, + const bool long_size_pre_delay_supported) { + auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)}; + + const auto& parameter{ + *reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())}; + const auto state{effect_info.GetStateBuffer()}; + + if (IsChannelCountValid(parameter.channel_count)) { + const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))}; + if (state_buffer) { + for (s16 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + + if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) { + UseOldChannelMapping(cmd.inputs, cmd.outputs); + } + + cmd.parameter = parameter; + cmd.effect_enabled = effect_info.IsEnabled(); + cmd.state = state_buffer; + cmd.workbuffer = effect_info.GetWorkbuffer(-1); + cmd.long_size_pre_delay_supported = long_size_pre_delay_supported; + } + } + + GenerateEnd<ReverbCommand>(cmd); +} + +void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset) { + auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)}; + + const auto& parameter{ + *reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())}; + const auto state{effect_info.GetStateBuffer()}; + + if (IsChannelCountValid(parameter.channel_count)) { + const auto state_buffer{ + memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))}; + if (state_buffer) { + for (s16 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + + if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) { + UseOldChannelMapping(cmd.inputs, cmd.outputs); + } + + cmd.parameter = parameter; + cmd.effect_enabled = effect_info.IsEnabled(); + cmd.state = state_buffer; + cmd.workbuffer = effect_info.GetWorkbuffer(-1); + } + } + + GenerateEnd<I3dl2ReverbCommand>(cmd); +} + +void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state, + const PerformanceEntryAddresses& entry_addresses) { + auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)}; + + cmd.state = state; + cmd.entry_address = entry_addresses; + + GenerateEnd<PerformanceCommand>(cmd); +} + +void CommandBuffer::GenerateClearMixCommand(const s32 node_id) { + auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)}; + GenerateEnd<ClearMixBufferCommand>(cmd); +} + +void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset, const s8 channel) { + auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)}; + + const auto& parameter{ + *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; + cmd.input_index = buffer_offset + parameter.inputs[channel]; + cmd.output_index = buffer_offset + parameter.outputs[channel]; + + GenerateEnd<CopyMixBufferCommand>(cmd); +} + +void CommandBuffer::GenerateLightLimiterCommand( + const s32 node_id, const s16 buffer_offset, + const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state, + const bool enabled, const CpuAddr workbuffer) { + auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)}; + + if (IsChannelCountValid(parameter.channel_count)) { + const auto state_buffer{ + memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; + if (state_buffer) { + for (s8 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + + std::memcpy(&cmd.parameter, ¶meter, sizeof(LightLimiterInfo::ParameterVersion1)); + cmd.effect_enabled = enabled; + cmd.state = state_buffer; + cmd.workbuffer = workbuffer; + } + } + + GenerateEnd<LightLimiterVersion1Command>(cmd); +} + +void CommandBuffer::GenerateLightLimiterCommand( + const s32 node_id, const s16 buffer_offset, + const LightLimiterInfo::ParameterVersion2& parameter, + const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state, + const bool enabled, const CpuAddr workbuffer) { + auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)}; + if (IsChannelCountValid(parameter.channel_count)) { + const auto state_buffer{ + memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; + if (state_buffer) { + for (s8 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + + cmd.parameter = parameter; + cmd.effect_enabled = enabled; + cmd.state = state_buffer; + if (cmd.parameter.statistics_enabled) { + cmd.result_state = memory_pool->Translate( + CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal)); + } else { + cmd.result_state = 0; + } + cmd.workbuffer = workbuffer; + } + } + + GenerateEnd<LightLimiterVersion2Command>(cmd); +} + +void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)}; + + cmd.input = buffer_count + channel; + cmd.output = buffer_count + channel; + cmd.biquads = voice_info.biquads; + + cmd.states[0] = + memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()), + MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); + cmd.states[1] = + memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()), + MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); + + cmd.needs_init[0] = !voice_info.biquad_initialized[0]; + cmd.needs_init[1] = !voice_info.biquad_initialized[1]; + cmd.filter_tap_count = MaxBiquadFilters; + + GenerateEnd<MultiTapBiquadFilterCommand>(cmd); +} + +void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 input_index, const s16 output_index, + const s16 buffer_offset, const u32 update_count, + const u32 count_max, const u32 write_offset) { + auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)}; + + if (effect_info.GetSendBuffer()) { + cmd.input = buffer_offset + input_index; + cmd.output = buffer_offset + output_index; + cmd.send_buffer_info = effect_info.GetSendBufferInfo(); + cmd.send_buffer = effect_info.GetSendBuffer(); + cmd.count_max = count_max; + cmd.write_offset = write_offset; + cmd.update_count = update_count; + cmd.effect_enabled = effect_info.IsEnabled(); + } + + GenerateEnd<CaptureCommand>(cmd); +} + +void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id) { + auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)}; + + auto& parameter{ + *reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())}; + auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())}; + + if (IsChannelCountValid(parameter.channel_count)) { + auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))}; + if (state_buffer) { + for (u16 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + cmd.parameter = parameter; + cmd.workbuffer = state_buffer; + cmd.enabled = effect_info.IsEnabled(); + } + } + + GenerateEnd<CompressorCommand>(cmd); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h new file mode 100644 index 000000000..496b0e50a --- /dev/null +++ b/src/audio_core/renderer/command/command_buffer.h @@ -0,0 +1,466 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/command/commands.h" +#include "audio_core/renderer/effect/light_limiter.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +struct UpsamplerInfo; +struct VoiceState; +class EffectInfoBase; +class ICommandProcessingTimeEstimator; +class MixInfo; +class MemoryPoolInfo; +class SinkInfoBase; +class VoiceInfo; + +/** + * Utility functions to generate and add commands into the current command list. + */ +class CommandBuffer { +public: + /** + * Generate a PCM s16 version 1 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param memory_pool - Memory pool for translating buffer addresses to the DSP. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool, + VoiceInfo& voice_info, const VoiceState& voice_state, + s16 buffer_count, s8 channel); + + /** + * Generate a PCM s16 version 2 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, s16 buffer_count, + s8 channel); + + /** + * Generate a PCM f32 version 1 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param memory_pool - Memory pool for translating buffer addresses to the DSP. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool, + VoiceInfo& voice_info, const VoiceState& voice_state, + s16 buffer_count, s8 channel); + + /** + * Generate a PCM f32 version 2 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, s16 buffer_count, + s8 channel); + + /** + * Generate an ADPCM version 1 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param memory_pool - Memory pool for translating buffer addresses to the DSP. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool, + VoiceInfo& voice_info, const VoiceState& voice_state, + s16 buffer_count, s8 channel); + + /** + * Generate an ADPCM version 2 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, s16 buffer_count, s8 channel); + + /** + * Generate a volume command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer index to generate this command at. + * @param input_index - Channel index and mix buffer offset for this command. + * @param volume - Mix volume added to the input samples. + * @param precision - Number of decimal bits for fixed point operations. + */ + void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume, + u8 precision); + + /** + * Generate a volume ramp command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command takes its volumes from. + * @param buffer_count - Number of active mix buffers, command will generate at this index. + * @param precision - Number of decimal bits for fixed point operations. + */ + void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count, + u8 precision); + + /** + * Generate a biquad filter command from a voice, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command takes biquad parameters from. + * @param voice_state - Used by the AudioRenderer to track previous samples. + * @param buffer_count - Number of active mix buffers, + * command will generate at this index + channel. + * @param channel - Channel index for this filter to work on. + * @param biquad_index - Which biquad filter to use for this command (0-1). + * @param use_float_processing - Should int or float processing be used? + */ + void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, s16 buffer_count, s8 channel, + u32 biquad_index, bool use_float_processing); + + /** + * Generate a biquad filter effect command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - The effect info this command takes biquad parameters from. + * @param buffer_offset - Mix buffer offset this command will use, + * command will generate at this index + channel. + * @param channel - Channel index for this filter to work on. + * @param needs_init - True if the biquad state needs initialisation. + * @param use_float_processing - Should int or float processing be used? + */ + void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, + s8 channel, bool needs_init, bool use_float_processing); + + /** + * Generate a mix command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param input_index - Input mix buffer index for this command. + * Added to the buffer offset. + * @param output_index - Output mix buffer index for this command. + * Added to the buffer offset. + * @param buffer_offset - Mix buffer offset this command will use. + * @param volume - Volume to be applied to the input. + * @param precision - Number of decimal bits for fixed point operations. + */ + void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset, + f32 volume, u8 precision); + + /** + * Generate a mix ramp command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_count - Number of active mix buffers. + * @param input_index - Input mix buffer index for this command. + * Added to buffer_count. + * @param output_index - Output mix buffer index for this command. + * Added to buffer_count. + * @param volume - Current mix volume used for calculating the ramp. + * @param prev_volume - Previous mix volume, used for calculating the ramp, + * also applied to the input. + * @param precision - Number of decimal bits for fixed point operations. + */ + void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, + f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision); + + /** + * Generate a mix ramp grouped command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_count - Number of active mix buffers. + * @param input_index - Input mix buffer index for this command. + * Added to buffer_count. + * @param output_index - Output mix buffer index for this command. + * Added to buffer_count. + * @param volumes - Current mix volumes used for calculating the ramp. + * @param prev_volumes - Previous mix volumes, used for calculating the ramp, + * also applied to the input. + * @param precision - Number of decimal bits for fixed point operations. + */ + void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, + s16 output_index, std::span<const f32> volumes, + std::span<const f32> prev_volumes, CpuAddr prev_samples, + u8 precision); + + /** + * Generate a depop prepare command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_state - State to track the previous depop samples for each mix buffer. + * @param buffer - State to track the current depop samples for each mix buffer. + * @param buffer_count - Number of active mix buffers. + * @param buffer_offset - Base mix buffer index to generate the channel depops at. + * @param was_playing - Command only needs to work if the voice was previously playing. + */ + void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state, + std::span<const s32> buffer, s16 buffer_count, + s16 buffer_offset, bool was_playing); + + /** + * Generate a depop command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param mix_info - Mix info to get the buffer count and base offsets from. + * @param depop_buffer - Buffer of current depop sample values to be added to the input + * channels. + */ + void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info, + std::span<const s32> depop_buffer); + + /** + * Generate a delay command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - Delay effect info to generate this command from. + * @param buffer_offset - Base mix buffer offset to apply the apply the delay. + */ + void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset); + + /** + * Generate an upsample command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer offset to upsample. + * @param upsampler_info - Upsampler info to control the upsampling. + * @param input_count - Number of input channels to upsample. + * @param inputs - Input mix buffer indexes. + * @param buffer_count - Number of active mix buffers. + * @param sample_count - Source sample count of the input. + * @param sample_rate - Source sample rate of the input. + */ + void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info, + u32 input_count, std::span<const s8> inputs, s16 buffer_count, + u32 sample_count, u32 sample_rate); + + /** + * Generate a downmix 6 -> 2 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param inputs - Input mix buffer indexes. + * @param buffer_offset - Base mix buffer offset of the channels to downmix. + * @param downmix_coeff - Downmixing coefficients. + */ + void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset, + std::span<const f32> downmix_coeff); + + /** + * Generate an aux buffer command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - Aux effect info to generate this command from. + * @param input_index - Input mix buffer index for this command. + * Added to buffer_offset. + * @param output_index - Output mix buffer index for this command. + * Added to buffer_offset. + * @param buffer_offset - Base mix buffer offset to use. + * @param update_count - Number of samples to write back to the game as updated, can be 0. + * @param count_max - Maximum number of samples to read or write. + * @param write_offset - Current read or write offset within the buffer. + */ + void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index, + s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max, + u32 write_offset); + + /** + * Generate a device sink command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - The sink_info to generate this command from. + * @session_id - System session id this command is generated from. + * @samples_buffer - The buffer to be sent to the sink if upsampling is not used. + */ + void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, + u32 session_id, std::span<s32> samples_buffer); + + /** + * Generate a circular buffer sink command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param sink_info - The sink_info to generate this command from. + * @param buffer_offset - Base mix buffer offset to use. + */ + void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset); + + /** + * Generate a reverb command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - Reverb effect info to generate this command from. + * @param buffer_offset - Base mix buffer offset to use. + * @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb + * begins? + */ + void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, + bool long_size_pre_delay_supported); + + /** + * Generate an I3DL2 reverb command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - I3DL2Reverb effect info to generate this command from. + * @param buffer_offset - Base mix buffer offset to use. + */ + void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset); + + /** + * Generate a performance command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param state - State of the performance. + * @param entry_addresses - The addresses to be filled in by the AudioRenderer. + */ + void GeneratePerformanceCommand(s32 node_id, PerformanceState state, + const PerformanceEntryAddresses& entry_addresses); + + /** + * Generate a clear mix command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + */ + void GenerateClearMixCommand(s32 node_id); + + /** + * Generate a copy mix command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - BiquadFilter effect info to generate this command from. + * @param buffer_offset - Base mix buffer offset to use. + * @param channel - Index to the effect's parameters input indexes for this command. + */ + void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, + s8 channel); + + /** + * Generate a light limiter version 1 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param parameter - Effect parameter to generate from. + * @param state - State used by the AudioRenderer between commands. + * @param enabled - Is this command enabled? + * @param workbuffer - Game-supplied memory for the state. + */ + void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset, + const LightLimiterInfo::ParameterVersion1& parameter, + const LightLimiterInfo::State& state, bool enabled, + CpuAddr workbuffer); + + /** + * Generate a light limiter version 2 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param parameter - Effect parameter to generate from. + * @param statistics - Statistics reported by the AudioRenderer on the limiter's state. + * @param state - State used by the AudioRenderer between commands. + * @param enabled - Is this command enabled? + * @param workbuffer - Game-supplied memory for the state. + */ + void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset, + const LightLimiterInfo::ParameterVersion2& parameter, + const LightLimiterInfo::StatisticsInternal& statistics, + const LightLimiterInfo::State& state, bool enabled, + CpuAddr workbuffer); + + /** + * Generate a multitap biquad filter command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command takes biquad parameters from. + * @param voice_state - Used by the AudioRenderer to track previous samples. + * @param buffer_count - Number of active mix buffers, + * command will generate at this index + channel. + * @param channel - Channel index for this filter to work on. + */ + void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, s16 buffer_count, + s8 channel); + + /** + * Generate a capture command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - Capture effect info to generate this command from. + * @param input_index - Input mix buffer index for this command. + * Added to buffer_offset. + * @param output_index - Output mix buffer index for this command (unused). + * Added to buffer_offset. + * @param buffer_offset - Base mix buffer offset to use. + * @param update_count - Number of samples to write back to the game as updated, can be 0. + * @param count_max - Maximum number of samples to read or write. + * @param write_offset - Current read or write offset within the buffer. + */ + void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index, + s16 output_index, s16 buffer_offset, u32 update_count, + u32 count_max, u32 write_offset); + + /** + * Generate a compressor command, adding it to the command list. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Capture effect info to generate this command from. + * @param node_id - Node id of the voice this command is generated for. + */ + void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); + + /// Command list buffer generated commands will be added to + std::span<u8> command_list{}; + /// Input sample count, unused + u32 sample_count{}; + /// Input sample rate, unused + u32 sample_rate{}; + /// Current size of the command buffer + u64 size{}; + /// Current number of commands added + u32 count{}; + /// Current estimated processing time for all commands + u32 estimated_process_time{}; + /// Used for mapping buffers for the AudioRenderer + MemoryPoolInfo* memory_pool{}; + /// Used for estimating command process times + ICommandProcessingTimeEstimator* time_estimator{}; + /// Used to check which rendering features are currently enabled + BehaviorInfo* behavior{}; + +private: + template <typename T, CommandId Id> + T& GenerateStart(const s32 node_id); + template <typename T> + void GenerateEnd(T& cmd); +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp new file mode 100644 index 000000000..2ea50d128 --- /dev/null +++ b/src/audio_core/renderer/command/command_generator.cpp @@ -0,0 +1,796 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_generator.h" +#include "audio_core/renderer/command/command_list_header.h" +#include "audio_core/renderer/effect/aux_.h" +#include "audio_core/renderer/effect/biquad_filter.h" +#include "audio_core/renderer/effect/buffer_mixer.h" +#include "audio_core/renderer/effect/capture.h" +#include "audio_core/renderer/effect/effect_context.h" +#include "audio_core/renderer/effect/light_limiter.h" +#include "audio_core/renderer/mix/mix_context.h" +#include "audio_core/renderer/performance/detail_aspect.h" +#include "audio_core/renderer/performance/entry_aspect.h" +#include "audio_core/renderer/sink/device_sink_info.h" +#include "audio_core/renderer/sink/sink_context.h" +#include "audio_core/renderer/splitter/splitter_context.h" +#include "audio_core/renderer/voice/voice_context.h" +#include "common/alignment.h" + +namespace AudioCore::AudioRenderer { + +CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_, + const CommandListHeader& command_list_header_, + const AudioRendererSystemContext& render_context_, + VoiceContext& voice_context_, MixContext& mix_context_, + EffectContext& effect_context_, SinkContext& sink_context_, + SplitterContext& splitter_context_, + PerformanceManager* performance_manager_) + : command_buffer{command_buffer_}, command_header{command_list_header_}, + render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_}, + effect_context{effect_context_}, sink_context{sink_context_}, + splitter_context{splitter_context_}, performance_manager{performance_manager_} { + command_buffer.GenerateClearMixCommand(InvalidNodeId); +} + +void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info, + const VoiceState& voice_state, const s8 channel) { + if (voice_info.mix_id == UnusedMixId) { + if (voice_info.splitter_id != UnusedSplitterId) { + auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)}; + u32 dest_id{0}; + while (destination != nullptr) { + if (destination->IsConfigured()) { + auto mix_id{destination->GetMixId()}; + if (mix_id < mix_context.GetCount()) { + auto mix_info{mix_context.GetInfo(mix_id)}; + command_buffer.GenerateDepopPrepareCommand( + voice_info.node_id, voice_state, render_context.depop_buffer, + mix_info->buffer_count, mix_info->buffer_offset, + voice_info.was_playing); + } + } + dest_id++; + destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id); + } + } + } else { + auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; + command_buffer.GenerateDepopPrepareCommand( + voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count, + mix_info->buffer_offset, voice_info.was_playing); + } + + if (voice_info.was_playing) { + return; + } + + if (render_context.behavior->IsWaveBufferVer2Supported()) { + switch (voice_info.sample_format) { + case SampleFormat::PcmInt16: + command_buffer.GeneratePcmInt16Version2Command( + voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, + channel); + break; + case SampleFormat::PcmFloat: + command_buffer.GeneratePcmFloatVersion2Command( + voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, + channel); + break; + case SampleFormat::Adpcm: + command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state, + render_context.mix_buffer_count, channel); + break; + default: + LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", + static_cast<u32>(voice_info.sample_format)); + break; + } + } else { + switch (voice_info.sample_format) { + case SampleFormat::PcmInt16: + command_buffer.GeneratePcmInt16Version1Command( + voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, + render_context.mix_buffer_count, channel); + break; + case SampleFormat::PcmFloat: + command_buffer.GeneratePcmFloatVersion1Command( + voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, + render_context.mix_buffer_count, channel); + break; + case SampleFormat::Adpcm: + command_buffer.GenerateAdpcmVersion1Command( + voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, + render_context.mix_buffer_count, channel); + break; + default: + LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", + static_cast<u32>(voice_info.sample_format)); + break; + } + } +} + +void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes, + std::span<const f32> prev_mix_volumes, + const VoiceState& voice_state, s16 output_index, + const s16 buffer_count, const s16 input_index, + const s32 node_id) { + u8 precision{15}; + if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { + precision = 23; + } + + if (buffer_count > 8) { + const auto prev_samples{render_context.memory_pool_info->Translate( + CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))}; + command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index, + output_index, mix_volumes, prev_mix_volumes, + prev_samples, precision); + } else { + for (s16 i = 0; i < buffer_count; i++, output_index++) { + const auto prev_samples{render_context.memory_pool_info->Translate( + CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))}; + + command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index, + mix_volumes[i], prev_mix_volumes[i], prev_samples, + precision); + } + } +} + +void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel, + const s32 node_id) { + const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled}; + const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()}; + + if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() && + use_float_processing) { + command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state, + buffer_count, channel); + } else { + for (u32 i = 0; i < MaxBiquadFilters; i++) { + if (voice_info.biquads[i].enabled) { + command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state, + buffer_count, channel, i, + use_float_processing); + } + } + } +} + +void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) { + u8 precision{15}; + if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { + precision = 23; + } + + for (s8 channel = 0; channel < voice_info.channel_count; channel++) { + const auto resource_id{voice_info.channel_resource_ids[channel]}; + auto& voice_state{voice_context.GetDspSharedState(resource_id)}; + auto& channel_resource{voice_context.GetChannelResource(resource_id)}; + + PerformanceDetailType detail_type{PerformanceDetailType::Invalid}; + switch (voice_info.sample_format) { + case SampleFormat::PcmInt16: + detail_type = PerformanceDetailType::Unk1; + break; + case SampleFormat::PcmFloat: + detail_type = PerformanceDetailType::Unk10; + break; + default: + detail_type = PerformanceDetailType::Unk2; + break; + } + + DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id, + detail_type); + GenerateDataSourceCommand(voice_info, voice_state, channel); + + if (data_source_detail.initialized) { + command_buffer.GeneratePerformanceCommand(data_source_detail.node_id, + PerformanceState::Stop, + data_source_detail.performance_entry_address); + } + + if (voice_info.was_playing) { + voice_info.prev_volume = 0.0f; + continue; + } + + if (!voice_info.HasAnyConnection()) { + continue; + } + + DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id, + PerformanceDetailType::Unk4); + GenerateBiquadFilterCommandForVoice( + voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id); + + if (biquad_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + biquad_detail_aspect.node_id, PerformanceState::Stop, + biquad_detail_aspect.performance_entry_address); + } + + DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice, + voice_info.node_id, PerformanceDetailType::Unk3); + command_buffer.GenerateVolumeRampCommand( + voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision); + if (volume_ramp_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + volume_ramp_detail_aspect.node_id, PerformanceState::Stop, + volume_ramp_detail_aspect.performance_entry_address); + } + + voice_info.prev_volume = voice_info.volume; + + if (voice_info.mix_id == UnusedMixId) { + if (voice_info.splitter_id != UnusedSplitterId) { + auto i{channel}; + auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)}; + while (destination != nullptr) { + if (destination->IsConfigured()) { + const auto mix_id{destination->GetMixId()}; + if (mix_id < mix_context.GetCount() && + static_cast<s32>(mix_id) != UnusedSplitterId) { + auto mix_info{mix_context.GetInfo(mix_id)}; + GenerateVoiceMixCommand( + destination->GetMixVolume(), destination->GetMixVolumePrev(), + voice_state, mix_info->buffer_offset, mix_info->buffer_count, + render_context.mix_buffer_count + channel, voice_info.node_id); + destination->MarkAsNeedToUpdateInternalState(); + } + } + i += voice_info.channel_count; + destination = splitter_context.GetDesintationData(voice_info.splitter_id, i); + } + } + } else { + DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice, + voice_info.node_id, PerformanceDetailType::Unk3); + auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; + GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes, + voice_state, mix_info->buffer_offset, mix_info->buffer_count, + render_context.mix_buffer_count + channel, voice_info.node_id); + if (volume_mix_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + volume_mix_detail_aspect.node_id, PerformanceState::Stop, + volume_mix_detail_aspect.performance_entry_address); + } + + channel_resource.prev_mix_volumes = channel_resource.mix_volumes; + } + voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled; + voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled; + } +} + +void CommandGenerator::GenerateVoiceCommands() { + const auto voice_count{voice_context.GetCount()}; + + for (u32 i = 0; i < voice_count; i++) { + auto sorted_info{voice_context.GetSortedInfo(i)}; + + if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) { + continue; + } + + EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id); + + GenerateVoiceCommand(*sorted_info); + + if (voice_entry_aspect.initialized) { + command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id, + PerformanceState::Stop, + voice_entry_aspect.performance_entry_address); + } + } + + splitter_context.UpdateInternalState(); +} + +void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset, + EffectInfoBase& effect_info, const s32 node_id) { + u8 precision{15}; + if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { + precision = 23; + } + + if (effect_info.IsEnabled()) { + const auto& parameter{ + *reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())}; + for (u32 i = 0; i < parameter.mix_count; i++) { + if (parameter.volumes[i] != 0.0f) { + command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i], + buffer_offset + parameter.outputs[i], + buffer_offset, parameter.volumes[i], precision); + } + } + } +} + +void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id) { + command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset); +} + +void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id, + const bool long_size_pre_delay_supported) { + command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset, + long_size_pre_delay_supported); +} + +void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset, + EffectInfoBase& effect_info, + const s32 node_id) { + command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset); +} + +void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id) { + + if (effect_info.IsEnabled()) { + effect_info.GetWorkbuffer(0); + effect_info.GetWorkbuffer(1); + } + + if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { + const auto& parameter{ + *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())}; + auto channel_index{parameter.mix_buffer_count - 1}; + u32 write_offset{0}; + for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) { + auto new_update_count{command_header.sample_count + write_offset}; + const auto update_count{channel_index > 0 ? 0 : new_update_count}; + command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i], + parameter.outputs[i], buffer_offset, update_count, + parameter.count_max, write_offset); + write_offset = new_update_count; + } + } +} + +void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset, + EffectInfoBase& effect_info, + const s32 node_id) { + const auto& parameter{ + *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; + if (effect_info.IsEnabled()) { + bool needs_init{false}; + + switch (parameter.state) { + case EffectInfoBase::ParameterState::Initialized: + needs_init = true; + break; + case EffectInfoBase::ParameterState::Updating: + case EffectInfoBase::ParameterState::Updated: + if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) { + needs_init = false; + } else { + needs_init = parameter.state == EffectInfoBase::ParameterState::Updating; + } + break; + default: + LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}", + static_cast<u32>(parameter.state)); + break; + } + + for (s8 channel = 0; channel < parameter.channel_count; channel++) { + command_buffer.GenerateBiquadFilterCommand( + node_id, effect_info, buffer_offset, channel, needs_init, + render_context.behavior->UseBiquadFilterFloatProcessing()); + } + } else { + for (s8 channel = 0; channel < parameter.channel_count; channel++) { + command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, + channel); + } + } +} + +void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset, + EffectInfoBase& effect_info, + const s32 node_id, + const u32 effect_index) { + + const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())}; + + if (render_context.behavior->IsEffectInfoVersion2Supported()) { + const auto& parameter{ + *reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())}; + const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>( + &effect_context.GetDspSharedResultState(effect_index))}; + command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state, + state, effect_info.IsEnabled(), + effect_info.GetWorkbuffer(-1)); + } else { + const auto& parameter{ + *reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())}; + command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state, + effect_info.IsEnabled(), + effect_info.GetWorkbuffer(-1)); + } +} + +void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id) { + if (effect_info.IsEnabled()) { + effect_info.GetWorkbuffer(0); + } + + if (effect_info.GetSendBuffer()) { + const auto& parameter{ + *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())}; + auto channel_index{parameter.mix_buffer_count - 1}; + u32 write_offset{0}; + for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) { + auto new_update_count{command_header.sample_count + write_offset}; + const auto update_count{channel_index > 0 ? 0 : new_update_count}; + command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i], + parameter.outputs[i], buffer_offset, update_count, + parameter.count_max, write_offset); + write_offset = new_update_count; + } + } +} + +void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset, + EffectInfoBase& effect_info, const s32 node_id) { + command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id); +} + +void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) { + const auto effect_count{effect_context.GetCount()}; + for (u32 i = 0; i < effect_count; i++) { + const auto effect_index{mix_info.effect_order_buffer[i]}; + if (effect_index == -1) { + break; + } + + auto& effect_info = effect_context.GetInfo(effect_index); + if (effect_info.ShouldSkip()) { + continue; + } + + const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix + : PerformanceEntryType::SubMix}; + + switch (effect_info.GetType()) { + case EffectInfoBase::Type::Mix: { + DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk5); + GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (mix_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + mix_detail_aspect.node_id, PerformanceState::Stop, + mix_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::Aux: { + DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk7); + GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (aux_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + aux_detail_aspect.node_id, PerformanceState::Stop, + aux_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::Delay: { + DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk6); + GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (delay_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + delay_detail_aspect.node_id, PerformanceState::Stop, + delay_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::Reverb: { + DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk8); + GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id, + render_context.behavior->IsLongSizePreDelaySupported()); + if (reverb_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + reverb_detail_aspect.node_id, PerformanceState::Stop, + reverb_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::I3dl2Reverb: { + DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk9); + GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (i3dl2_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + i3dl2_detail_aspect.node_id, PerformanceState::Stop, + i3dl2_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::BiquadFilter: { + DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk4); + GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info, + mix_info.node_id); + if (biquad_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + biquad_detail_aspect.node_id, PerformanceState::Stop, + biquad_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::LightLimiter: { + DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk11); + GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id, + effect_index); + if (light_limiter_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + light_limiter_detail_aspect.node_id, PerformanceState::Stop, + light_limiter_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::Capture: { + DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk12); + GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (capture_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + capture_detail_aspect.node_id, PerformanceState::Stop, + capture_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::Compressor: { + DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk13); + GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (capture_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + capture_detail_aspect.node_id, PerformanceState::Stop, + capture_detail_aspect.performance_entry_address); + } + } break; + + default: + LOG_ERROR(Service_Audio, "Invalid effect type {}", + static_cast<u32>(effect_info.GetType())); + break; + } + + effect_info.UpdateForCommandGeneration(); + } +} + +void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) { + u8 precision{15}; + if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { + precision = 23; + } + + if (!mix_info.HasAnyConnection()) { + return; + } + + if (mix_info.dst_mix_id == UnusedMixId) { + if (mix_info.dst_splitter_id != UnusedSplitterId) { + s16 dest_id{0}; + auto destination{ + splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)}; + while (destination != nullptr) { + if (destination->IsConfigured()) { + auto splitter_mix_id{destination->GetMixId()}; + if (splitter_mix_id < mix_context.GetCount()) { + auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)}; + const s16 input_index{static_cast<s16>(mix_info.buffer_offset + + (dest_id % mix_info.buffer_count))}; + for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) { + auto volume{mix_info.volume * destination->GetMixVolume(i)}; + if (volume != 0.0f) { + command_buffer.GenerateMixCommand( + mix_info.node_id, input_index, + splitter_mix_info->buffer_offset + i, mix_info.buffer_offset, + volume, precision); + } + } + } + } + dest_id++; + destination = + splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id); + } + } + } else { + auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)}; + for (s16 i = 0; i < mix_info.buffer_count; i++) { + for (s16 j = 0; j < dest_mix_info->buffer_count; j++) { + auto volume{mix_info.volume * mix_info.mix_volumes[i][j]}; + if (volume != 0.0f) { + command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i, + dest_mix_info->buffer_offset + j, + mix_info.buffer_offset, volume, precision); + } + } + } + } +} + +void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) { + command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info, + render_context.depop_buffer); + GenerateEffectCommand(mix_info); + + DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id, + PerformanceDetailType::Unk5); + + GenerateMixCommands(mix_info); + + if (mix_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop, + mix_detail_aspect.performance_entry_address); + } +} + +void CommandGenerator::GenerateSubMixCommands() { + const auto submix_count{mix_context.GetCount()}; + for (s32 i = 0; i < submix_count; i++) { + auto sorted_info{mix_context.GetSortedInfo(i)}; + if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) { + continue; + } + + EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id); + + GenerateSubMixCommand(*sorted_info); + + if (submix_entry_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + submix_entry_aspect.node_id, PerformanceState::Stop, + submix_entry_aspect.performance_entry_address); + } + } +} + +void CommandGenerator::GenerateFinalMixCommand() { + auto& final_mix_info{*mix_context.GetFinalMixInfo()}; + + command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info, + render_context.depop_buffer); + GenerateEffectCommand(final_mix_info); + + u8 precision{15}; + if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { + precision = 23; + } + + for (s16 i = 0; i < final_mix_info.buffer_count; i++) { + DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id, + PerformanceDetailType::Unk3); + command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset, + i, final_mix_info.volume, precision); + if (volume_aspect.initialized) { + command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop, + volume_aspect.performance_entry_address); + } + } +} + +void CommandGenerator::GenerateFinalMixCommands() { + auto final_mix_info{mix_context.GetFinalMixInfo()}; + EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id); + GenerateFinalMixCommand(); + if (final_mix_entry.initialized) { + command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop, + final_mix_entry.performance_entry_address); + } +} + +void CommandGenerator::GenerateSinkCommands() { + const auto sink_count{sink_context.GetCount()}; + + for (u32 i = 0; i < sink_count; i++) { + auto sink_info{sink_context.GetInfo(i)}; + if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) { + auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())}; + if (command_header.sample_rate != TargetSampleRate && + state->upsampler_info == nullptr) { + auto device_state{sink_info->GetDeviceState()}; + device_state->upsampler_info = render_context.upsampler_manager->Allocate(); + } + + EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink, + sink_info->GetNodeId()); + auto final_mix{mix_context.GetFinalMixInfo()}; + GenerateSinkCommand(final_mix->buffer_offset, *sink_info); + + if (device_sink_entry.initialized) { + command_buffer.GeneratePerformanceCommand( + device_sink_entry.node_id, PerformanceState::Stop, + device_sink_entry.performance_entry_address); + } + } + } + + for (u32 i = 0; i < sink_count; i++) { + auto sink_info{sink_context.GetInfo(i)}; + if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) { + EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink, + sink_info->GetNodeId()); + auto final_mix{mix_context.GetFinalMixInfo()}; + GenerateSinkCommand(final_mix->buffer_offset, *sink_info); + + if (circular_buffer_entry.initialized) { + command_buffer.GeneratePerformanceCommand( + circular_buffer_entry.node_id, PerformanceState::Stop, + circular_buffer_entry.performance_entry_address); + } + } + } +} + +void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { + if (sink_info.ShouldSkip()) { + return; + } + + switch (sink_info.GetType()) { + case SinkInfoBase::Type::DeviceSink: + GenerateDeviceSinkCommand(buffer_offset, sink_info); + break; + + case SinkInfoBase::Type::CircularBufferSink: + command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info, + buffer_offset); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType())); + break; + } + + sink_info.UpdateForCommandGeneration(); +} + +void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { + auto& parameter{ + *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())}; + auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())}; + + if (render_context.channels == 2 && parameter.downmix_enabled) { + command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs, + buffer_offset, parameter.downmix_coeff); + } + + if (state.upsampler_info != nullptr) { + command_buffer.GenerateUpsampleCommand( + InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count, + parameter.inputs, command_header.buffer_count, command_header.sample_count, + command_header.sample_rate); + } + + command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info, + render_context.session_id, + command_header.samples_buffer); +} + +void CommandGenerator::GeneratePerformanceCommand( + s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) { + command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h new file mode 100644 index 000000000..d80d9b0d8 --- /dev/null +++ b/src/audio_core/renderer/command/command_generator.h @@ -0,0 +1,349 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/command/commands.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore { +struct AudioRendererSystemContext; + +namespace AudioRenderer { +class CommandBuffer; +struct CommandListHeader; +class VoiceContext; +class MixContext; +class EffectContext; +class SplitterContext; +class SinkContext; +class BehaviorInfo; +class VoiceInfo; +struct VoiceState; +class MixInfo; +class SinkInfoBase; + +/** + * Generates all commands to build up a command list, which are sent to the AudioRender for + * processing. + */ +class CommandGenerator { +public: + explicit CommandGenerator(CommandBuffer& command_buffer, + const CommandListHeader& command_list_header, + const AudioRendererSystemContext& render_context, + VoiceContext& voice_context, MixContext& mix_context, + EffectContext& effect_context, SinkContext& sink_context, + SplitterContext& splitter_context, + PerformanceManager* performance_manager); + + /** + * Calculate the buffer size needed for commands. + * + * @param behavior - Used to check what features are enabled. + * @param params - Input rendering parameters for numbers of voices/mixes/sinks etc. + */ + static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params) { + u64 size{0}; + + // Effects + size += params.effects * sizeof(EffectInfoBase); + + // Voices + u64 voice_size{0}; + if (behavior.IsWaveBufferVer2Supported()) { + voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command), + sizeof(PcmInt16DataSourceVersion2Command)), + sizeof(PcmFloatDataSourceVersion2Command)); + } else { + voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command), + sizeof(PcmInt16DataSourceVersion1Command)), + sizeof(PcmFloatDataSourceVersion1Command)); + } + voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters; + voice_size += sizeof(VolumeRampCommand); + voice_size += sizeof(MixRampGroupedCommand); + + size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size); + + // Sub mixes + size += sizeof(DepopForMixBuffersCommand) + + (sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers; + + // Final mix + size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers; + + // Splitters + size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers; + + // Sinks + size += + params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand)); + + // Performance + size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 + + PerformanceManager::MaxDetailEntries) * + sizeof(PerformanceCommand); + return size; + } + + /** + * Get the current command buffer used to generate commands. + * + * @return The command buffer. + */ + CommandBuffer& GetCommandBuffer() { + return command_buffer; + } + + /** + * Get the current performance manager, + * + * @return The performance manager. May be nullptr. + */ + PerformanceManager* GetPerformanceManager() { + return performance_manager; + } + + /** + * Generate a data source command. + * These are the basis for all audio output. + * + * @param voice_info - Generate the command from this voice. + * @param voice_state - State used by the AudioRenderer across calls. + * @param channel - Channel index to generate the command into. + */ + void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state, + s8 channel); + + /** + * Generate voice mixing commands. + * These are used to mix buffers together, to mix one input to many outputs, + * and also used as copy commands to move data around and prevent it being accidentally + * overwritten, e.g by another data source command into the same channel. + * + * @param mix_volumes - Current volumes of the mix. + * @param prev_mix_volumes - Previous volumes of the mix. + * @param voice_state - State used by the AudioRenderer across calls. + * @param output_index - Output mix buffer index. + * @param buffer_count - Number of active mix buffers. + * @param input_index - Input mix buffer index. + * @param node_id - Node id of the voice this command is generated for. + */ + void GenerateVoiceMixCommand(std::span<const f32> mix_volumes, + std::span<const f32> prev_mix_volumes, + const VoiceState& voice_state, s16 output_index, s16 buffer_count, + s16 input_index, s32 node_id); + + /** + * Generate a biquad filter command for a voice. + * + * @param voice_info - Voice info this command is generated from. + * @param voice_state - State used by the AudioRenderer across calls. + * @param buffer_count - Number of active mix buffers. + * @param channel - Channel index of this command. + * @param node_id - Node id of the voice this command is generated for. + */ + void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state, + s16 buffer_count, s8 channel, s32 node_id); + + /** + * Generate commands for a voice. + * Includes a data source, biquad filter, volume and mixing. + * + * @param voice_info - Voice info these commands are generated from. + */ + void GenerateVoiceCommand(VoiceInfo& voice_info); + + /** + * Generate commands for all voices. + */ + void GenerateVoiceCommands(); + + /** + * Generate a mixing command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - BufferMixer effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, + s32 node_id); + + /** + * Generate a delay effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Delay effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id); + + /** + * Generate a reverb effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Reverb effect info. + * @param node_id - Node id of the mix this command is generated for. + * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts. + */ + void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id, + bool long_size_pre_delay_supported); + + /** + * Generate an I3DL2 reverb effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - I3DL2Reverb effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id); + + /** + * Generate an aux effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Aux effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); + + /** + * Generate a biquad filter effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Aux effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id); + + /** + * Generate a light limiter effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Limiter effect info. + * @param node_id - Node id of the mix this command is generated for. + * @param effect_index - Index for the statistics state. + */ + void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id, u32 effect_index); + + /** + * Generate a capture effect command. + * Writes a mix buffer back to game memory. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Capture effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); + + /** + * Generate a compressor effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Compressor effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id); + + /** + * Generate all effect commands for a mix. + * + * @param mix_info - Mix to generate effects from. + */ + void GenerateEffectCommand(MixInfo& mix_info); + + /** + * Generate all mix commands. + * + * @param mix_info - Mix to generate effects from. + */ + void GenerateMixCommands(MixInfo& mix_info); + + /** + * Generate a submix command. + * Generates all effects and all mixing commands. + * + * @param mix_info - Mix to generate effects from. + */ + void GenerateSubMixCommand(MixInfo& mix_info); + + /** + * Generate all submix command. + */ + void GenerateSubMixCommands(); + + /** + * Generate the final mix. + */ + void GenerateFinalMixCommand(); + + /** + * Generate the final mix commands. + */ + void GenerateFinalMixCommands(); + + /** + * Generate all sink commands. + */ + void GenerateSinkCommands(); + + /** + * Generate a sink command. + * Sends samples out to the backend, or a game-supplied circular buffer. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - Sink info to generate the commands from. + */ + void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); + + /** + * Generate a device sink command. + * Sends samples out to the backend. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - Sink info to generate the commands from. + */ + void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); + + /** + * Generate a performance command. + * Used to report performance metrics of the AudioRenderer back to the game. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - Sink info to generate the commands from. + */ + void GeneratePerformanceCommand(s32 node_id, PerformanceState state, + const PerformanceEntryAddresses& entry_addresses); + +private: + /// Commands will be written by this buffer + CommandBuffer& command_buffer; + /// Header information for the commands generated + const CommandListHeader& command_header; + /// Various things to control generation + const AudioRendererSystemContext& render_context; + /// Used for generating voices + VoiceContext& voice_context; + /// Used for generating mixes + MixContext& mix_context; + /// Used for generating effects + EffectContext& effect_context; + /// Used for generating sinks + SinkContext& sink_context; + /// Used for generating submixes + SplitterContext& splitter_context; + /// Used for generating performance + PerformanceManager* performance_manager; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/command/command_list_header.h b/src/audio_core/renderer/command/command_list_header.h new file mode 100644 index 000000000..988530b1f --- /dev/null +++ b/src/audio_core/renderer/command/command_list_header.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +struct CommandListHeader { + u64 buffer_size; + u32 command_count; + std::span<s32> samples_buffer; + s16 buffer_count; + u32 sample_count; + u32 sample_rate; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp new file mode 100644 index 000000000..3091f587a --- /dev/null +++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp @@ -0,0 +1,3620 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/command/command_processing_time_estimator.h" + +namespace AudioCore::AudioRenderer { + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + return static_cast<u32>(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + return static_cast<u32>(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const PcmFloatDataSourceVersion1Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const PcmFloatDataSourceVersion2Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + return static_cast<u32>(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + return static_cast<u32>(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 8.8f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 9.8f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 58.0f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const MixCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 10.0f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 14.4f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + return static_cast<u32>(((static_cast<f32>(sample_count) * 14.4f) * 1.2f) * + static_cast<f32>(count)); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 1080; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const DepopForMixBuffersCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 8.9f) * + static_cast<f32>(command.count)); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const DelayCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * command.parameter.channel_count) * + 202.5f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + return 357915; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + return 16108; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const AuxCommand& command) const { + if (command.enabled) { + return 15956; + } + return 3765; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const DeviceSinkCommand& command) const { + return 10042; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CircularBufferSinkCommand& command) const { + return 55; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const ReverbCommand& command) const { + if (command.enabled) { + return static_cast<u32>( + (command.parameter.channel_count * static_cast<f32>(sample_count) * 750) * 1.2f); + } + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const I3dl2ReverbCommand& command) const { + if (command.enabled) { + return static_cast<u32>( + (command.parameter.channel_count * static_cast<f32>(sample_count) * 530) * 1.2f); + } + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + return 1454; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + return static_cast<u32>( + ((static_cast<f32>(sample_count) * 0.83f) * static_cast<f32>(buffer_count)) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const LightLimiterVersion1Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const LightLimiterVersion2Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CaptureCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 2125.588f + + 9039.47f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 3564.088 + + 6225.471); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 2125.588f + + 9039.47f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 3564.088 + + 6225.471); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1280.3f); + case 240: + return static_cast<u32>(1737.8f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1403.9f); + case 240: + return static_cast<u32>(1884.3f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(4813.2f); + case 240: + return static_cast<u32>(6915.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1342.2f); + case 240: + return static_cast<u32>(1833.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1859.0f); + case 240: + return static_cast<u32>(2286.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) * + static_cast<f32>(count)); + case 240: + return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) * + static_cast<f32>(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(306.62f); + case 240: + return static_cast<u32>(293.22f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(762.96f); + case 240: + return static_cast<u32>(726.96f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(41635.555f); + case 2: + return static_cast<u32>(97861.211f); + case 4: + return static_cast<u32>(192515.516f); + case 6: + return static_cast<u32>(301755.969f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(578.529f); + case 2: + return static_cast<u32>(663.064f); + case 4: + return static_cast<u32>(703.983f); + case 6: + return static_cast<u32>(760.032f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(8770.345f); + case 2: + return static_cast<u32>(25741.18f); + case 4: + return static_cast<u32>(47551.168f); + case 6: + return static_cast<u32>(81629.219f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(521.283f); + case 2: + return static_cast<u32>(585.396f); + case 4: + return static_cast<u32>(629.884f); + case 6: + return static_cast<u32>(713.57f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(292000.0f); + case 240: + return static_cast<u32>(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(10009.0f); + case 240: + return static_cast<u32>(14577.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const AuxCommand& command) const { + // Is this function bugged, returning the wrong time? + // Surely the larger time should be returned when enabled... + // CMP W8, #0 + // MOV W8, #0x60; // 489.163f + // MOV W10, #0x64; // 7177.936f + // CSEL X8, X10, X8, EQ + + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(489.163f); + } + return static_cast<u32>(7177.936f); + case 240: + if (command.enabled) { + return static_cast<u32>(485.562f); + } + return static_cast<u32>(9499.822f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(9261.545f); + case 240: + return static_cast<u32>(9336.054f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(9336.054f); + case 240: + return static_cast<u32>(9566.728f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(command.input_count) * 853.629f + 1284.517f); + case 240: + return static_cast<u32>(static_cast<f32>(command.input_count) * 1726.021f + 1369.683f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(97192.227f); + case 2: + return static_cast<u32>(103278.555f); + case 4: + return static_cast<u32>(109579.039f); + case 6: + return static_cast<u32>(115065.438f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(492.009f); + case 2: + return static_cast<u32>(554.463f); + case 4: + return static_cast<u32>(595.864f); + case 6: + return static_cast<u32>(656.617f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(136463.641f); + case 2: + return static_cast<u32>(145749.047f); + case 4: + return static_cast<u32>(154796.938f); + case 6: + return static_cast<u32>(161968.406f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(495.789f); + case 2: + return static_cast<u32>(527.163f); + case 4: + return static_cast<u32>(598.752f); + case 6: + return static_cast<u32>(666.025f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(138836.484f); + case 2: + return static_cast<u32>(135428.172f); + case 4: + return static_cast<u32>(199181.844f); + case 6: + return static_cast<u32>(247345.906f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(718.704f); + case 2: + return static_cast<u32>(751.296f); + case 4: + return static_cast<u32>(797.464f); + case 6: + return static_cast<u32>(867.426f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(199952.734f); + case 2: + return static_cast<u32>(195199.5f); + case 4: + return static_cast<u32>(290575.875f); + case 6: + return static_cast<u32>(363494.531f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(534.24f); + case 2: + return static_cast<u32>(570.874f); + case 4: + return static_cast<u32>(660.933f); + case 6: + return static_cast<u32>(694.596f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(489.35f); + case 240: + return static_cast<u32>(491.18f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(buffer_count) * 260.4f + 139.65f); + case 240: + return static_cast<u32>(static_cast<f32>(buffer_count) * 668.85f + 193.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(836.32f); + case 240: + return static_cast<u32>(1000.9f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const LightLimiterVersion1Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const LightLimiterVersion2Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const CaptureCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 427.52f + + 6329.442f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 710.143f + + 7853.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 427.52f + + 6329.442f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 371.876f + + 8049.415f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 423.43f + + 5062.659f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 710.143f + + 7853.286f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 610.487f + + 10138.842f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 676.722f + + 5810.962f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1672.026f + + 7681.211f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2550.414f + + 9663.969f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.026f + + 7681.211f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.982f + + 9038.011f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1673.216f + + 6027.577f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2550.414f + + 9663.969f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2522.303f + + 11758.571f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2537.061f + + 7369.309f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1827.665f + + 7913.808f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2756.372f + + 9736.702f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1827.665f + + 7913.808f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1829.285f + + 9607.814f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1824.609f + + 6517.476f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2756.372f + + 9736.702f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2731.308f + + 12154.379f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2732.152f + + 7929.442f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1311.1f); + case 240: + return static_cast<u32>(1713.6f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1425.3f); + case 240: + return static_cast<u32>(1700.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(4173.2f); + case 240: + return static_cast<u32>(5585.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1402.8f); + case 240: + return static_cast<u32>(1853.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1968.7f); + case 240: + return static_cast<u32>(2459.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) * + static_cast<f32>(count)); + case 240: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) * + static_cast<f32>(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(739.64f); + case 240: + return static_cast<u32>(910.97f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(8929.042f); + case 2: + return static_cast<u32>(25500.75f); + case 4: + return static_cast<u32>(47759.617f); + case 6: + return static_cast<u32>(82203.07f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(1295.206f); + case 2: + return static_cast<u32>(1213.6f); + case 4: + return static_cast<u32>(942.028f); + case 6: + return static_cast<u32>(1001.553f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(11941.051f); + case 2: + return static_cast<u32>(37197.371f); + case 4: + return static_cast<u32>(69749.836f); + case 6: + return static_cast<u32>(120042.398f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(997.668f); + case 2: + return static_cast<u32>(977.634f); + case 4: + return static_cast<u32>(792.309f); + case 6: + return static_cast<u32>(875.427f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(312990.0f); + case 240: + return static_cast<u32>(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(9949.7f); + case 240: + return static_cast<u32>(14679.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const AuxCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(7182.136f); + } + return static_cast<u32>(472.111f); + case 240: + if (command.enabled) { + return static_cast<u32>(9435.961f); + } + return static_cast<u32>(462.619f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(8979.956f); + case 240: + return static_cast<u32>(9221.907f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(9177.903f); + case 240: + return static_cast<u32>(9725.897f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(81475.055f); + case 2: + return static_cast<u32>(84975.0f); + case 4: + return static_cast<u32>(91625.148f); + case 6: + return static_cast<u32>(95332.266f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(536.298f); + case 2: + return static_cast<u32>(588.798f); + case 4: + return static_cast<u32>(643.702f); + case 6: + return static_cast<u32>(705.999f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(120174.469f); + case 2: + return static_cast<u32>(125262.219f); + case 4: + return static_cast<u32>(135751.234f); + case 6: + return static_cast<u32>(141129.234f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(617.641f); + case 2: + return static_cast<u32>(659.536f); + case 4: + return static_cast<u32>(711.438f); + case 6: + return static_cast<u32>(778.071f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(116754.984f); + case 2: + return static_cast<u32>(125912.055f); + case 4: + return static_cast<u32>(146336.031f); + case 6: + return static_cast<u32>(165812.656f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(735.0f); + case 2: + return static_cast<u32>(766.615f); + case 4: + return static_cast<u32>(834.067f); + case 6: + return static_cast<u32>(875.437f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(170292.344f); + case 2: + return static_cast<u32>(183875.625f); + case 4: + return static_cast<u32>(214696.188f); + case 6: + return static_cast<u32>(243846.766f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(508.473f); + case 2: + return static_cast<u32>(582.445f); + case 4: + return static_cast<u32>(626.419f); + case 6: + return static_cast<u32>(682.468f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(498.17f); + case 240: + return static_cast<u32>(489.42f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(842.59f); + case 240: + return static_cast<u32>(986.72f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const LightLimiterVersion1Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21392.383f); + case 2: + return static_cast<u32>(26829.389f); + case 4: + return static_cast<u32>(32405.152f); + case 6: + return static_cast<u32>(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30555.504f); + case 2: + return static_cast<u32>(39010.785f); + case 4: + return static_cast<u32>(48270.18f); + case 6: + return static_cast<u32>(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const LightLimiterVersion2Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(23308.928f); + case 2: + return static_cast<u32>(29954.062f); + case 4: + return static_cast<u32>(35807.477f); + case 6: + return static_cast<u32>(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21392.383f); + case 2: + return static_cast<u32>(26829.389f); + case 4: + return static_cast<u32>(32405.152f); + case 6: + return static_cast<u32>(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(33526.121f); + case 2: + return static_cast<u32>(43549.355f); + case 4: + return static_cast<u32>(52190.281f); + case 6: + return static_cast<u32>(85526.516f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30555.504f); + case 2: + return static_cast<u32>(39010.785f); + case 4: + return static_cast<u32>(48270.18f); + case 6: + return static_cast<u32>(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const CaptureCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 427.52f + + 6329.442f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 710.143f + + 7853.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 427.52f + + 6329.442f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 371.876f + + 8049.415f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 423.43f + + 5062.659f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 710.143f + + 7853.286f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 610.487f + + 10138.842f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 676.722f + + 5810.962f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1672.026f + + 7681.211f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2550.414f + + 9663.969f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.026f + + 7681.211f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.982f + + 9038.011f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1673.216f + + 6027.577f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2550.414f + + 9663.969f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2522.303f + + 11758.571f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2537.061f + + 7369.309f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1827.665f + + 7913.808f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2756.372f + + 9736.702f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1827.665f + + 7913.808f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1829.285f + + 9607.814f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1824.609f + + 6517.476f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2756.372f + + 9736.702f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2731.308f + + 12154.379f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2732.152f + + 7929.442f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1311.1f); + case 240: + return static_cast<u32>(1713.6f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1425.3f); + case 240: + return static_cast<u32>(1700.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(4173.2f); + case 240: + return static_cast<u32>(5585.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1402.8f); + case 240: + return static_cast<u32>(1853.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1968.7f); + case 240: + return static_cast<u32>(2459.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) * + static_cast<f32>(count)); + case 240: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) * + static_cast<f32>(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(739.64f); + case 240: + return static_cast<u32>(910.97f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(8929.042f); + case 2: + return static_cast<u32>(25500.75f); + case 4: + return static_cast<u32>(47759.617f); + case 6: + return static_cast<u32>(82203.07f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(1295.206f); + case 2: + return static_cast<u32>(1213.6f); + case 4: + return static_cast<u32>(942.028f); + case 6: + return static_cast<u32>(1001.553f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(11941.051f); + case 2: + return static_cast<u32>(37197.371f); + case 4: + return static_cast<u32>(69749.836f); + case 6: + return static_cast<u32>(120042.398f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(997.668f); + case 2: + return static_cast<u32>(977.634f); + case 4: + return static_cast<u32>(792.309f); + case 6: + return static_cast<u32>(875.427f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(312990.0f); + case 240: + return static_cast<u32>(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(9949.7f); + case 240: + return static_cast<u32>(14679.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const AuxCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(7182.136f); + } + return static_cast<u32>(472.111f); + case 240: + if (command.enabled) { + return static_cast<u32>(9435.961f); + } + return static_cast<u32>(462.619f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(8979.956f); + case 240: + return static_cast<u32>(9221.907f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(9177.903f); + case 240: + return static_cast<u32>(9725.897f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(81475.055f); + case 2: + return static_cast<u32>(84975.0f); + case 4: + return static_cast<u32>(91625.148f); + case 6: + return static_cast<u32>(95332.266f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(536.298f); + case 2: + return static_cast<u32>(588.798f); + case 4: + return static_cast<u32>(643.702f); + case 6: + return static_cast<u32>(705.999f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(120174.469f); + case 2: + return static_cast<u32>(125262.219f); + case 4: + return static_cast<u32>(135751.234f); + case 6: + return static_cast<u32>(141129.234f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(617.641f); + case 2: + return static_cast<u32>(659.536f); + case 4: + return static_cast<u32>(711.438f); + case 6: + return static_cast<u32>(778.071f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(116754.984f); + case 2: + return static_cast<u32>(125912.055f); + case 4: + return static_cast<u32>(146336.031f); + case 6: + return static_cast<u32>(165812.656f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(735.0f); + case 2: + return static_cast<u32>(766.615f); + case 4: + return static_cast<u32>(834.067f); + case 6: + return static_cast<u32>(875.437f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(170292.344f); + case 2: + return static_cast<u32>(183875.625f); + case 4: + return static_cast<u32>(214696.188f); + case 6: + return static_cast<u32>(243846.766f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(508.473f); + case 2: + return static_cast<u32>(582.445f); + case 4: + return static_cast<u32>(626.419f); + case 6: + return static_cast<u32>(682.468f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(498.17f); + case 240: + return static_cast<u32>(489.42f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(842.59f); + case 240: + return static_cast<u32>(986.72f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const LightLimiterVersion1Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21392.383f); + case 2: + return static_cast<u32>(26829.389f); + case 4: + return static_cast<u32>(32405.152f); + case 6: + return static_cast<u32>(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30555.504f); + case 2: + return static_cast<u32>(39010.785f); + case 4: + return static_cast<u32>(48270.18f); + case 6: + return static_cast<u32>(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const LightLimiterVersion2Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(23308.928f); + case 2: + return static_cast<u32>(29954.062f); + case 4: + return static_cast<u32>(35807.477f); + case 6: + return static_cast<u32>(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21392.383f); + case 2: + return static_cast<u32>(26829.389f); + case 4: + return static_cast<u32>(32405.152f); + case 6: + return static_cast<u32>(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(33526.121f); + case 2: + return static_cast<u32>(43549.355f); + case 4: + return static_cast<u32>(52190.281f); + case 6: + return static_cast<u32>(85526.516f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30555.504f); + case 2: + return static_cast<u32>(39010.785f); + case 4: + return static_cast<u32>(48270.18f); + case 6: + return static_cast<u32>(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(7424.5f); + case 240: + return static_cast<u32>(9730.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const CaptureCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(426.982f); + } + return static_cast<u32>(4261.005f); + case 240: + if (command.enabled) { + return static_cast<u32>(435.204f); + } + return static_cast<u32>(5858.265f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 427.52f + + 6329.442f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 710.143f + + 7853.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 427.52f + + 6329.442f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 371.876f + + 8049.415f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 423.43f + + 5062.659f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 710.143f + + 7853.286f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 610.487f + + 10138.842f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 676.722f + + 5810.962f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1672.026f + + 7681.211f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2550.414f + + 9663.969f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.026f + + 7681.211f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.982f + + 9038.011f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1673.216f + + 6027.577f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2550.414f + + 9663.969f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2522.303f + + 11758.571f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2537.061f + + 7369.309f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1827.665f + + 7913.808f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2756.372f + + 9736.702f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1827.665f + + 7913.808f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1829.285f + + 9607.814f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1824.609f + + 6517.476f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2756.372f + + 9736.702f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2731.308f + + 12154.379f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2732.152f + + 7929.442f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1311.1f); + case 240: + return static_cast<u32>(1713.6f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1425.3f); + case 240: + return static_cast<u32>(1700.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(4173.2f); + case 240: + return static_cast<u32>(5585.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1402.8f); + case 240: + return static_cast<u32>(1853.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1968.7f); + case 240: + return static_cast<u32>(2459.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) * + static_cast<f32>(count)); + case 240: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) * + static_cast<f32>(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(739.64f); + case 240: + return static_cast<u32>(910.97f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(8929.042f); + case 2: + return static_cast<u32>(25500.75f); + case 4: + return static_cast<u32>(47759.617f); + case 6: + return static_cast<u32>(82203.07f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(1295.206f); + case 2: + return static_cast<u32>(1213.6f); + case 4: + return static_cast<u32>(942.028f); + case 6: + return static_cast<u32>(1001.553f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(11941.051f); + case 2: + return static_cast<u32>(37197.371f); + case 4: + return static_cast<u32>(69749.836f); + case 6: + return static_cast<u32>(120042.398f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(997.668f); + case 2: + return static_cast<u32>(977.634f); + case 4: + return static_cast<u32>(792.309f); + case 6: + return static_cast<u32>(875.427f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(312990.0f); + case 240: + return static_cast<u32>(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(9949.7f); + case 240: + return static_cast<u32>(14679.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const AuxCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(7182.136f); + } + return static_cast<u32>(472.111f); + case 240: + if (command.enabled) { + return static_cast<u32>(9435.961f); + } + return static_cast<u32>(462.619f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(8979.956f); + case 240: + return static_cast<u32>(9221.907f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(9177.903f); + case 240: + return static_cast<u32>(9725.897f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(81475.055f); + case 2: + return static_cast<u32>(84975.0f); + case 4: + return static_cast<u32>(91625.148f); + case 6: + return static_cast<u32>(95332.266f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(536.298f); + case 2: + return static_cast<u32>(588.798f); + case 4: + return static_cast<u32>(643.702f); + case 6: + return static_cast<u32>(705.999f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(120174.469f); + case 2: + return static_cast<u32>(125262.219f); + case 4: + return static_cast<u32>(135751.234f); + case 6: + return static_cast<u32>(141129.234f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(617.641f); + case 2: + return static_cast<u32>(659.536f); + case 4: + return static_cast<u32>(711.438f); + case 6: + return static_cast<u32>(778.071f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(116754.984f); + case 2: + return static_cast<u32>(125912.055f); + case 4: + return static_cast<u32>(146336.031f); + case 6: + return static_cast<u32>(165812.656f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(735.0f); + case 2: + return static_cast<u32>(766.615f); + case 4: + return static_cast<u32>(834.067f); + case 6: + return static_cast<u32>(875.437f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(170292.344f); + case 2: + return static_cast<u32>(183875.625f); + case 4: + return static_cast<u32>(214696.188f); + case 6: + return static_cast<u32>(243846.766f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(508.473f); + case 2: + return static_cast<u32>(582.445f); + case 4: + return static_cast<u32>(626.419f); + case 6: + return static_cast<u32>(682.468f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(498.17f); + case 240: + return static_cast<u32>(489.42f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(842.59f); + case 240: + return static_cast<u32>(986.72f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const LightLimiterVersion1Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21508.01f); + case 2: + return static_cast<u32>(23120.453f); + case 4: + return static_cast<u32>(26270.053f); + case 6: + return static_cast<u32>(40471.902f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30565.961f); + case 2: + return static_cast<u32>(32812.91f); + case 4: + return static_cast<u32>(37354.852f); + case 6: + return static_cast<u32>(58486.699f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const LightLimiterVersion2Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(23639.584f); + case 2: + return static_cast<u32>(24666.725f); + case 4: + return static_cast<u32>(28876.459f); + case 6: + return static_cast<u32>(47096.078f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21508.01f); + case 2: + return static_cast<u32>(23120.453f); + case 4: + return static_cast<u32>(26270.053f); + case 6: + return static_cast<u32>(40471.902f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } + } else if (command.parameter.processing_mode == + LightLimiterInfo::ProcessingMode::Mode1) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(23639.584f); + case 2: + return static_cast<u32>(29954.062f); + case 4: + return static_cast<u32>(35807.477f); + case 6: + return static_cast<u32>(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(23639.584f); + case 2: + return static_cast<u32>(29954.062f); + case 4: + return static_cast<u32>(35807.477f); + case 6: + return static_cast<u32>(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } + } else { + LOG_ERROR(Service_Audio, "Invalid processing mode {}", + command.parameter.processing_mode); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(33875.023f); + case 2: + return static_cast<u32>(35199.938f); + case 4: + return static_cast<u32>(41371.230f); + case 6: + return static_cast<u32>(68370.914f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30565.961f); + case 2: + return static_cast<u32>(32812.91f); + case 4: + return static_cast<u32>(37354.852f); + case 6: + return static_cast<u32>(58486.699f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } else if (command.parameter.processing_mode == + LightLimiterInfo::ProcessingMode::Mode1) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(33942.980f); + case 2: + return static_cast<u32>(28698.893f); + case 4: + return static_cast<u32>(34774.277f); + case 6: + return static_cast<u32>(61897.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30610.248f); + case 2: + return static_cast<u32>(26322.408f); + case 4: + return static_cast<u32>(30369.000f); + case 6: + return static_cast<u32>(51892.090f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } else { + LOG_ERROR(Service_Audio, "Invalid processing mode {}", + command.parameter.processing_mode); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(7424.5f); + case 240: + return static_cast<u32>(9730.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CaptureCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(426.982f); + } + return static_cast<u32>(4261.005f); + case 240: + if (command.enabled) { + return static_cast<u32>(435.204f); + } + return static_cast<u32>(5858.265f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& command) const { + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + switch (sample_count) { + case 160: + return static_cast<u32>(34430.570f); + case 240: + return static_cast<u32>(51095.348f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(44253.320f); + case 240: + return static_cast<u32>(65693.094f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 4: + switch (sample_count) { + case 160: + return static_cast<u32>(63827.457f); + case 240: + return static_cast<u32>(95382.852f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(83361.484f); + case 240: + return static_cast<u32>(124509.906f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + switch (sample_count) { + case 160: + return static_cast<u32>(630.115f); + case 240: + return static_cast<u32>(840.136f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(638.274f); + case 240: + return static_cast<u32>(826.098f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 4: + switch (sample_count) { + case 160: + return static_cast<u32>(705.862f); + case 240: + return static_cast<u32>(901.876f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(782.019f); + case 240: + return static_cast<u32>(965.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.h b/src/audio_core/renderer/command/command_processing_time_estimator.h new file mode 100644 index 000000000..452217196 --- /dev/null +++ b/src/audio_core/renderer/command/command_processing_time_estimator.h @@ -0,0 +1,254 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/command/commands.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Estimate the processing time required for all commands. + */ +class ICommandProcessingTimeEstimator { +public: + virtual ~ICommandProcessingTimeEstimator() = default; + + virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0; + virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0; + virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0; + virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0; + virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0; + virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0; + virtual u32 Estimate(const VolumeCommand& command) const = 0; + virtual u32 Estimate(const VolumeRampCommand& command) const = 0; + virtual u32 Estimate(const BiquadFilterCommand& command) const = 0; + virtual u32 Estimate(const MixCommand& command) const = 0; + virtual u32 Estimate(const MixRampCommand& command) const = 0; + virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0; + virtual u32 Estimate(const DepopPrepareCommand& command) const = 0; + virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0; + virtual u32 Estimate(const DelayCommand& command) const = 0; + virtual u32 Estimate(const UpsampleCommand& command) const = 0; + virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0; + virtual u32 Estimate(const AuxCommand& command) const = 0; + virtual u32 Estimate(const DeviceSinkCommand& command) const = 0; + virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0; + virtual u32 Estimate(const ReverbCommand& command) const = 0; + virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0; + virtual u32 Estimate(const PerformanceCommand& command) const = 0; + virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0; + virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0; + virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0; + virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0; + virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0; + virtual u32 Estimate(const CaptureCommand& command) const = 0; + virtual u32 Estimate(const CompressorCommand& command) const = 0; +}; + +class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/commands.h b/src/audio_core/renderer/command/commands.h new file mode 100644 index 000000000..6d8b8546d --- /dev/null +++ b/src/audio_core/renderer/command/commands.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/command/data_source/adpcm.h" +#include "audio_core/renderer/command/data_source/pcm_float.h" +#include "audio_core/renderer/command/data_source/pcm_int16.h" +#include "audio_core/renderer/command/effect/aux_.h" +#include "audio_core/renderer/command/effect/biquad_filter.h" +#include "audio_core/renderer/command/effect/capture.h" +#include "audio_core/renderer/command/effect/compressor.h" +#include "audio_core/renderer/command/effect/delay.h" +#include "audio_core/renderer/command/effect/i3dl2_reverb.h" +#include "audio_core/renderer/command/effect/light_limiter.h" +#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" +#include "audio_core/renderer/command/effect/reverb.h" +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/command/mix/clear_mix.h" +#include "audio_core/renderer/command/mix/copy_mix.h" +#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" +#include "audio_core/renderer/command/mix/depop_prepare.h" +#include "audio_core/renderer/command/mix/mix.h" +#include "audio_core/renderer/command/mix/mix_ramp.h" +#include "audio_core/renderer/command/mix/mix_ramp_grouped.h" +#include "audio_core/renderer/command/mix/volume.h" +#include "audio_core/renderer/command/mix/volume_ramp.h" +#include "audio_core/renderer/command/performance/performance.h" +#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" +#include "audio_core/renderer/command/resample/upsample.h" +#include "audio_core/renderer/command/sink/circular_buffer.h" +#include "audio_core/renderer/command/sink/device.h" diff --git a/src/audio_core/renderer/command/data_source/adpcm.cpp b/src/audio_core/renderer/command/data_source/adpcm.cpp new file mode 100644 index 000000000..e66ed2990 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/adpcm.cpp @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <span> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/data_source/adpcm.h" +#include "audio_core/renderer/command/data_source/decode.h" + +namespace AudioCore::AudioRenderer { + +void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample " + "rate {} target sample rate {} src quality {}\n", + output_index, sample_rate, processor.target_sample_rate, src_quality); +} + +void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::Adpcm}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{0}, + .channel_count{1}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{data_address}, + .data_size{data_size}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample " + "rate {} target sample rate {} src quality {}\n", + output_index, sample_rate, processor.target_sample_rate, src_quality); +} + +void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::Adpcm}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{0}, + .channel_count{1}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{data_address}, + .data_size{data_size}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/adpcm.h b/src/audio_core/renderer/command/data_source/adpcm.h new file mode 100644 index 000000000..a9cf9cee4 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/adpcm.h @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/common/common.h" +#include "audio_core/common/wave_buffer.h" +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers + * into the output_index mix buffer. + */ +struct AdpcmDataSourceVersion1Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; + /// Coefficients data address + CpuAddr data_address; + /// Coefficients data size + u64 data_size; +}; + +/** + * AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers + * into the output_index mix buffer. + */ +struct AdpcmDataSourceVersion2Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Target channel to read within the wavebuffer + s8 channel_index; + /// Number of channels within the wavebuffer + s8 channel_count; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; + /// Coefficients data address + CpuAddr data_address; + /// Coefficients data size + u64 data_size; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/decode.cpp b/src/audio_core/renderer/command/data_source/decode.cpp new file mode 100644 index 000000000..ff5d31bd6 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/decode.cpp @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <array> +#include <vector> + +#include "audio_core/renderer/command/data_source/decode.h" +#include "audio_core/renderer/command/resample/resample.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { + +constexpr u32 TempBufferSize = 0x3F00; +constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4}; + +/** + * Decode PCM data. Only s16 or f32 is supported. + * + * @tparam T - Type to decode. Only s16 and f32 are supported. + * @param memory - Core memory for reading samples. + * @param out_buffer - Output mix buffer to receive the samples. + * @param req - Information for how to decode. + * @return Number of samples decoded. + */ +template <typename T> +static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, + const DecodeArg& req) { + constexpr s32 min{std::numeric_limits<s16>::min()}; + constexpr s32 max{std::numeric_limits<s16>::max()}; + + if (req.buffer == 0 || req.buffer_size == 0) { + return 0; + } + + if (req.start_offset >= req.end_offset) { + return 0; + } + + auto samples_to_decode{ + std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)}; + u32 channel_count{static_cast<u32>(req.channel_count)}; + + switch (req.channel_count) { + default: { + const VAddr source{req.buffer + + (((req.start_offset + req.offset) * channel_count) * sizeof(T))}; + const u64 size{channel_count * samples_to_decode}; + const u64 size_bytes{size * sizeof(T)}; + + std::vector<T> samples(size); + memory.ReadBlockUnsafe(source, samples.data(), size_bytes); + + if constexpr (std::is_floating_point_v<T>) { + for (u32 i = 0; i < samples_to_decode; i++) { + auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * + std::numeric_limits<s16>::max())}; + out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); + } + } else { + for (u32 i = 0; i < samples_to_decode; i++) { + out_buffer[i] = samples[i * channel_count + req.target_channel]; + } + } + } break; + + case 1: + if (req.target_channel != 0) { + LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}", + req.target_channel); + return 0; + } + + const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))}; + std::vector<T> samples(samples_to_decode); + memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T)); + + if constexpr (std::is_floating_point_v<T>) { + for (u32 i = 0; i < samples_to_decode; i++) { + auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * + std::numeric_limits<s16>::max())}; + out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); + } + } else { + std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16)); + } + break; + } + + return samples_to_decode; +} + +/** + * Decode ADPCM data. + * + * @param memory - Core memory for reading samples. + * @param out_buffer - Output mix buffer to receive the samples. + * @param req - Information for how to decode. + * @return Number of samples decoded. + */ +static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, + const DecodeArg& req) { + constexpr u32 SamplesPerFrame{14}; + constexpr u32 NibblesPerFrame{16}; + + if (req.buffer == 0 || req.buffer_size == 0) { + return 0; + } + + if (req.end_offset < req.start_offset) { + return 0; + } + + auto end{(req.end_offset % SamplesPerFrame) + + NibblesPerFrame * (req.end_offset / SamplesPerFrame)}; + if (req.end_offset % SamplesPerFrame) { + end += 3; + } else { + end += 1; + } + + if (req.buffer_size < end / 2) { + return 0; + } + + auto samples_to_process{ + std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)}; + + auto samples_to_read{samples_to_process}; + auto start_pos{req.start_offset + req.offset}; + auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; + auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + + samples_remaining_in_frame}; + + if (samples_remaining_in_frame) { + position_in_frame += 2; + } + + const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)}; + std::vector<u8> wavebuffer(size); + memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(), + wavebuffer.size()); + + auto context{req.adpcm_context}; + auto header{context->header}; + u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)}; + u8 scale{static_cast<u8>(header & 0xFU)}; + s32 coeff0{req.coefficients[coeff_index * 2 + 0]}; + s32 coeff1{req.coefficients[coeff_index * 2 + 1]}; + + auto yn0{context->yn0}; + auto yn1{context->yn1}; + + static constexpr std::array<s32, 16> Steps{ + 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, + }; + + const auto decode_sample = [&](const s32 code) -> s16 { + const auto xn = code * (1 << scale); + const auto prediction = coeff0 * yn0 + coeff1 * yn1; + const auto sample = ((xn << 11) + 0x400 + prediction) >> 11; + const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF); + yn1 = yn0; + yn0 = static_cast<s16>(saturated); + return yn0; + }; + + u32 read_index{0}; + u32 write_index{0}; + + while (samples_to_read > 0) { + // Are we at a new frame? + if ((position_in_frame % NibblesPerFrame) == 0) { + header = wavebuffer[read_index++]; + coeff_index = (header >> 4) & 0xF; + scale = header & 0xF; + coeff0 = req.coefficients[coeff_index * 2 + 0]; + coeff1 = req.coefficients[coeff_index * 2 + 1]; + position_in_frame += 2; + + // Can we consume all of this frame's samples? + if (samples_to_read >= SamplesPerFrame) { + // Can grab all samples until the next header + for (u32 i = 0; i < SamplesPerFrame / 2; i++) { + auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]}; + auto code1{Steps[wavebuffer[read_index] & 0xF]}; + read_index++; + + out_buffer[write_index++] = decode_sample(code0); + out_buffer[write_index++] = decode_sample(code1); + } + + position_in_frame += SamplesPerFrame; + samples_to_read -= SamplesPerFrame; + continue; + } + } + + // Decode a single sample + auto code{wavebuffer[read_index]}; + if (position_in_frame & 1) { + code &= 0xF; + read_index++; + } else { + code >>= 4; + } + + out_buffer[write_index++] = decode_sample(Steps[code]); + + position_in_frame++; + samples_to_read--; + } + + context->header = header; + context->yn0 = yn0; + context->yn1 = yn1; + + return samples_to_process; +} + +/** + * Decode implementation. + * Decode wavebuffers according to the given args. + * + * @param memory - Core memory to read data from. + * @param args - The wavebuffer data, and information for how to decode it. + */ +void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { + auto& voice_state{*args.voice_state}; + auto remaining_sample_count{args.sample_count}; + auto fraction{voice_state.fraction}; + + const auto sample_rate_ratio{ + (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) * + args.pitch}; + const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; + + if (size_required < 0) { + return; + } + + auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]}; + if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) { + return; + } + + auto max_remaining_sample_count{ + ((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio) + .to_uint_floor()}; + max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count); + + auto wavebuffers_consumed{voice_state.wave_buffers_consumed}; + auto wavebuffer_index{voice_state.wave_buffer_index}; + auto played_sample_count{voice_state.played_sample_count}; + + bool is_buffer_starved{false}; + u32 offset{voice_state.offset}; + + auto output_buffer{args.output}; + std::vector<s16> temp_buffer(TempBufferSize, 0); + + while (remaining_sample_count > 0) { + const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)}; + const auto samples_to_read{ + (fraction + samples_to_write * sample_rate_ratio).to_uint_floor()}; + + u32 temp_buffer_pos{0}; + + if (!args.IsVoicePitchAndSrcSkippedSupported) { + for (u32 i = 0; i < pitch; i++) { + temp_buffer[i] = voice_state.sample_history[i]; + } + temp_buffer_pos = pitch; + } + + u32 samples_read{0}; + while (samples_read < samples_to_read) { + if (wavebuffer_index >= MaxWaveBuffers) { + LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index); + wavebuffer_index = 0; + voice_state.wave_buffer_valid.fill(false); + wavebuffers_consumed = MaxWaveBuffers; + } + + if (!voice_state.wave_buffer_valid[wavebuffer_index]) { + is_buffer_starved = true; + break; + } + + auto& wavebuffer{args.wave_buffers[wavebuffer_index]}; + + if (offset == 0 && args.sample_format == SampleFormat::Adpcm && + wavebuffer.context != 0) { + memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, + wavebuffer.context_size); + } + + auto start_offset{wavebuffer.start_offset}; + auto end_offset{wavebuffer.end_offset}; + + if (wavebuffer.loop && voice_state.loop_count > 0 && + wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 && + wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { + start_offset = wavebuffer.loop_start_offset; + end_offset = wavebuffer.loop_end_offset; + } + + DecodeArg decode_arg{.buffer{wavebuffer.buffer}, + .buffer_size{wavebuffer.buffer_size}, + .start_offset{start_offset}, + .end_offset{end_offset}, + .channel_count{args.channel_count}, + .coefficients{}, + .adpcm_context{nullptr}, + .target_channel{args.channel}, + .offset{offset}, + .samples_to_read{samples_to_read - samples_read}}; + + s32 samples_decoded{0}; + + switch (args.sample_format) { + case SampleFormat::PcmInt16: + samples_decoded = DecodePcm<s16>( + memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, + decode_arg); + break; + + case SampleFormat::PcmFloat: + samples_decoded = DecodePcm<f32>( + memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, + decode_arg); + break; + + case SampleFormat::Adpcm: { + decode_arg.adpcm_context = &voice_state.adpcm_context; + memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size); + samples_decoded = DecodeAdpcm( + memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, + decode_arg); + } break; + + default: + LOG_ERROR(Service_Audio, "Invalid sample format to decode {}", + static_cast<u32>(args.sample_format)); + samples_decoded = 0; + break; + } + + played_sample_count += samples_decoded; + samples_read += samples_decoded; + temp_buffer_pos += samples_decoded; + offset += samples_decoded; + + if (samples_decoded == 0 || offset >= end_offset - start_offset) { + offset = 0; + if (!wavebuffer.loop) { + voice_state.wave_buffer_valid[wavebuffer_index] = false; + voice_state.loop_count = 0; + + if (wavebuffer.stream_ended) { + played_sample_count = 0; + } + + wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; + wavebuffers_consumed++; + } else { + voice_state.loop_count++; + if (wavebuffer.loop_count > 0 && + (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { + voice_state.wave_buffer_valid[wavebuffer_index] = false; + voice_state.loop_count = 0; + + if (wavebuffer.stream_ended) { + played_sample_count = 0; + } + + wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; + wavebuffers_consumed++; + } + + if (samples_decoded == 0) { + is_buffer_starved = true; + break; + } + + if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { + played_sample_count = 0; + } + } + } + } + + if (args.IsVoicePitchAndSrcSkippedSupported) { + if (samples_read > output_buffer.size()) { + LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!"); + } + for (u32 i = 0; i < samples_read; i++) { + output_buffer[i] = temp_buffer[i]; + } + } else { + std::memset(&temp_buffer[temp_buffer_pos], 0, + (samples_to_read - samples_read) * sizeof(s16)); + + Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write, + args.src_quality); + + std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read], + pitch * sizeof(s16)); + } + + remaining_sample_count -= samples_to_write; + if (remaining_sample_count != 0 && is_buffer_starved) { + LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??"); + break; + } + + output_buffer = output_buffer.subspan(samples_to_write); + } + + voice_state.wave_buffers_consumed = wavebuffers_consumed; + voice_state.played_sample_count = played_sample_count; + voice_state.wave_buffer_index = wavebuffer_index; + voice_state.offset = offset; + voice_state.fraction = fraction; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/decode.h b/src/audio_core/renderer/command/data_source/decode.h new file mode 100644 index 000000000..4d63d6fa8 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/decode.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> + +#include "audio_core/common/common.h" +#include "audio_core/common/wave_buffer.h" +#include "audio_core/renderer/voice/voice_state.h" +#include "common/common_types.h" + +namespace Core::Memory { +class Memory; +} + +namespace AudioCore::AudioRenderer { + +struct DecodeFromWaveBuffersArgs { + SampleFormat sample_format; + std::span<s32> output; + VoiceState* voice_state; + std::span<WaveBufferVersion2> wave_buffers; + s8 channel; + s8 channel_count; + SrcQuality src_quality; + f32 pitch; + u32 source_sample_rate; + u32 target_sample_rate; + u32 sample_count; + CpuAddr data_address; + u64 data_size; + bool IsVoicePlayedSampleCountResetAtLoopPointSupported; + bool IsVoicePitchAndSrcSkippedSupported; +}; + +struct DecodeArg { + CpuAddr buffer; + u64 buffer_size; + u32 start_offset; + u32 end_offset; + s8 channel_count; + std::array<s16, 16> coefficients; + VoiceState::AdpcmContext* adpcm_context; + s8 target_channel; + u32 offset; + u32 samples_to_read; +}; + +/** + * Decode wavebuffers according to the given args. + * + * @param memory - Core memory to read data from. + * @param args - The wavebuffer data, and information for how to decode it. + */ +void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_float.cpp b/src/audio_core/renderer/command/data_source/pcm_float.cpp new file mode 100644 index 000000000..be77fab69 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_float.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/data_source/decode.h" +#include "audio_core/renderer/command/data_source/pcm_float.h" + +namespace AudioCore::AudioRenderer { + +void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += + fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} " + "channel count {} source sample rate {} target sample rate {} src quality {}\n", + output_index, channel_index, channel_count, sample_rate, + processor.target_sample_rate, src_quality); +} + +void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count); + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::PcmFloat}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{channel_index}, + .channel_count{channel_count}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{0}, + .data_size{0}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += + fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} " + "channel count {} source sample rate {} target sample rate {} src quality {}\n", + output_index, channel_index, channel_count, sample_rate, + processor.target_sample_rate, src_quality); +} + +void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count); + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::PcmFloat}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{channel_index}, + .channel_count{channel_count}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{0}, + .data_size{0}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_float.h b/src/audio_core/renderer/command/data_source/pcm_float.h new file mode 100644 index 000000000..e4af77c20 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_float.h @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/common/wave_buffer.h" +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers + * into the output_index mix buffer. + */ +struct PcmFloatDataSourceVersion1Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Target channel to read within the wavebuffer + s8 channel_index; + /// Number of channels within the wavebuffer + s8 channel_count; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; +}; + +/** + * AudioRenderer command to decode PCM float-encoded version 2 wavebuffers + * into the output_index mix buffer. + */ +struct PcmFloatDataSourceVersion2Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Target channel to read within the wavebuffer + s8 channel_index; + /// Number of channels within the wavebuffer + s8 channel_count; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.cpp b/src/audio_core/renderer/command/data_source/pcm_int16.cpp new file mode 100644 index 000000000..7a27463e4 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_int16.cpp @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <span> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/data_source/decode.h" +#include "audio_core/renderer/command/data_source/pcm_int16.h" + +namespace AudioCore::AudioRenderer { + +void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += + fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} " + "channel count {} source sample rate {} target sample rate {} src quality {}\n", + output_index, channel_index, channel_count, sample_rate, + processor.target_sample_rate, src_quality); +} + +void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count); + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::PcmInt16}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{channel_index}, + .channel_count{channel_count}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{0}, + .data_size{0}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += + fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} " + "channel count {} source sample rate {} target sample rate {} src quality {}\n", + output_index, channel_index, channel_count, sample_rate, + processor.target_sample_rate, src_quality); +} + +void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count); + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::PcmInt16}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{channel_index}, + .channel_count{channel_count}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{0}, + .data_size{0}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.h b/src/audio_core/renderer/command/data_source/pcm_int16.h new file mode 100644 index 000000000..5de1ad60d --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_int16.h @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/common/wave_buffer.h" +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers + * into the output_index mix buffer. + */ +struct PcmInt16DataSourceVersion1Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Target channel to read within the wavebuffer + s8 channel_index; + /// Number of channels within the wavebuffer + s8 channel_count; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; +}; + +/** + * AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers + * into the output_index mix buffer. + */ +struct PcmInt16DataSourceVersion2Command : ICommand { + /** + * Print this command's information to a string. + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Target channel to read within the wavebuffer + s8 channel_index; + /// Number of channels within the wavebuffer + s8 channel_count; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/aux_.cpp b/src/audio_core/renderer/command/effect/aux_.cpp new file mode 100644 index 000000000..e76db893f --- /dev/null +++ b/src/audio_core/renderer/command/effect/aux_.cpp @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/aux_.h" +#include "audio_core/renderer/effect/aux_.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { +/** + * Reset an AuxBuffer. + * + * @param memory - Core memory for writing. + * @param aux_info - Memory address pointing to the AuxInfo to reset. + */ +static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { + if (aux_info == 0) { + LOG_ERROR(Service_Audio, "Aux info is 0!"); + return; + } + + auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))}; + info->read_offset = 0; + info->write_offset = 0; + info->total_sample_count = 0; +} + +/** + * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if + * update_count is set, to notify the game that an update happened. + * + * @param memory - Core memory for writing. + * @param send_info_ - Meta information for where to write the mix buffer. + * @param sample_count - Unused. + * @param send_buffer - Memory address to write the mix buffer to. + * @param count_max - Maximum number of samples in the receiving buffer. + * @param input - Input mix buffer to write. + * @param write_count_ - Number of samples to write. + * @param write_offset - Current offset to begin writing the receiving buffer at. + * @param update_count - If non-zero, send_info_ will be updated. + * @return Number of samples written. + */ +static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, + [[maybe_unused]] u32 sample_count, const CpuAddr send_buffer, + const u32 count_max, std::span<const s32> input, + const u32 write_count_, const u32 write_offset, + const u32 update_count) { + if (write_count_ > count_max) { + LOG_ERROR(Service_Audio, + "write_count must be smaller than count_max! write_count {}, count_max {}", + write_count_, count_max); + return 0; + } + + if (input.empty()) { + LOG_ERROR(Service_Audio, "input buffer is empty!"); + return 0; + } + + if (send_buffer == 0) { + LOG_ERROR(Service_Audio, "send_buffer is 0!"); + return 0; + } + + if (count_max == 0) { + return 0; + } + + AuxInfo::AuxInfoDsp send_info{}; + memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp)); + + u32 target_write_offset{send_info.write_offset + write_offset}; + if (target_write_offset > count_max || write_count_ == 0) { + return 0; + } + + u32 write_count{write_count_}; + u32 write_pos{0}; + while (write_count > 0) { + u32 to_write{std::min(count_max - target_write_offset, write_count)}; + + if (to_write > 0) { + memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), + &input[write_pos], to_write * sizeof(s32)); + } + + target_write_offset = (target_write_offset + to_write) % count_max; + write_count -= to_write; + write_pos += to_write; + } + + if (update_count) { + send_info.write_offset = (send_info.write_offset + update_count) % count_max; + } + + memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp)); + + return write_count_; +} + +/** + * Read the given memory at return_buffer into the output mix buffer, and update return_info_ if + * update_count is set, to notify the game that an update happened. + * + * @param memory - Core memory for writing. + * @param return_info_ - Meta information for where to read the mix buffer. + * @param return_buffer - Memory address to read the samples from. + * @param count_max - Maximum number of samples in the receiving buffer. + * @param output - Output mix buffer which will receive the samples. + * @param count_ - Number of samples to read. + * @param read_offset - Current offset to begin reading the return_buffer at. + * @param update_count - If non-zero, send_info_ will be updated. + * @return Number of samples read. + */ +static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_, + const CpuAddr return_buffer, const u32 count_max, std::span<s32> output, + const u32 count_, const u32 read_offset, const u32 update_count) { + if (count_max == 0) { + return 0; + } + + if (count_ > count_max) { + LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}", + count_, count_max); + return 0; + } + + if (output.empty()) { + LOG_ERROR(Service_Audio, "output buffer is empty!"); + return 0; + } + + if (return_buffer == 0) { + LOG_ERROR(Service_Audio, "return_buffer is 0!"); + return 0; + } + + AuxInfo::AuxInfoDsp return_info{}; + memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); + + u32 target_read_offset{return_info.read_offset + read_offset}; + if (target_read_offset > count_max) { + return 0; + } + + u32 read_count{count_}; + u32 read_pos{0}; + while (read_count > 0) { + u32 to_read{std::min(count_max - target_read_offset, read_count)}; + + if (to_read > 0) { + memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32), + &output[read_pos], to_read * sizeof(s32)); + } + + target_read_offset = (target_read_offset + to_read) % count_max; + read_count -= to_read; + read_pos += to_read; + } + + if (update_count) { + return_info.read_offset = (return_info.read_offset + update_count) % count_max; + } + + memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); + + return count_; +} + +void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled, + input, output); +} + +void AuxCommand::Process(const ADSP::CommandListProcessor& processor) { + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + auto output_buffer{ + processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; + + if (effect_enabled) { + WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer, + count_max, input_buffer, processor.sample_count, write_offset, + update_count); + + auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max, + output_buffer, processor.sample_count, write_offset, + update_count)}; + + if (read != processor.sample_count) { + std::memset(&output_buffer[read], 0, processor.sample_count - read); + } + } else { + ResetAuxBufferDsp(*processor.memory, send_buffer_info); + ResetAuxBufferDsp(*processor.memory, return_buffer_info); + if (input != output) { + std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes()); + } + } +} + +bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/aux_.h b/src/audio_core/renderer/command/effect/aux_.h new file mode 100644 index 000000000..825c93732 --- /dev/null +++ b/src/audio_core/renderer/command/effect/aux_.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game + * memory, and reading into the output buffer from game memory. + */ +struct AuxCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Meta info for writing + CpuAddr send_buffer_info; + /// Meta info for reading + CpuAddr return_buffer_info; + /// Game memory write buffer + CpuAddr send_buffer; + /// Game memory read buffer + CpuAddr return_buffer; + /// Max samples to read/write + u32 count_max; + /// Current read/write offset + u32 write_offset; + /// Number of samples to update per call + u32 update_count; + /// is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp new file mode 100644 index 000000000..1baae74fd --- /dev/null +++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/biquad_filter.h" +#include "audio_core/renderer/voice/voice_state.h" + +namespace AudioCore::AudioRenderer { +/** + * Biquad filter float implementation. + * + * @param output - Output container for filtered samples. + * @param input - Input container for samples to be filtered. + * @param b - Feedforward coefficients. + * @param a - Feedback coefficients. + * @param state - State to track previous samples between calls. + * @param sample_count - Number of samples to process. + */ +void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, + std::array<s16, 3>& b_, std::array<s16, 2>& a_, + VoiceState::BiquadFilterState& state, const u32 sample_count) { + constexpr s64 min{std::numeric_limits<s32>::min()}; + constexpr s64 max{std::numeric_limits<s32>::max()}; + std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(), + Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(), + Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()}; + std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(), + Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()}; + std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(), + state.s3.to_double()}; + + for (u32 i = 0; i < sample_count; i++) { + f64 in_sample{static_cast<f64>(input[i])}; + auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]}; + + output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max)); + + s[1] = s[0]; + s[0] = in_sample; + s[3] = s[2]; + s[2] = sample; + } + + state.s0 = s[0]; + state.s1 = s[1]; + state.s2 = s[2]; + state.s3 = s[3]; +} + +/** + * Biquad filter s32 implementation. + * + * @param output - Output container for filtered samples. + * @param input - Input container for samples to be filtered. + * @param b - Feedforward coefficients. + * @param a - Feedback coefficients. + * @param state - State to track previous samples between calls. + * @param sample_count - Number of samples to process. + */ +static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input, + std::array<s16, 3>& b_, std::array<s16, 2>& a_, + VoiceState::BiquadFilterState& state, const u32 sample_count) { + constexpr s64 min{std::numeric_limits<s32>::min()}; + constexpr s64 max{std::numeric_limits<s32>::max()}; + std::array<Common::FixedPoint<50, 14>, 3> b{ + Common::FixedPoint<50, 14>::from_base(b_[0]), + Common::FixedPoint<50, 14>::from_base(b_[1]), + Common::FixedPoint<50, 14>::from_base(b_[2]), + }; + std::array<Common::FixedPoint<50, 14>, 3> a{ + Common::FixedPoint<50, 14>::from_base(a_[0]), + Common::FixedPoint<50, 14>::from_base(a_[1]), + }; + + for (u32 i = 0; i < sample_count; i++) { + s64 in_sample{input[i]}; + auto sample{in_sample * b[0] + state.s0}; + const auto out_sample{std::clamp(sample.to_long(), min, max)}; + + output[i] = static_cast<s32>(out_sample); + + state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample; + state.s1 = 0 + b[2] * in_sample + a[1] * out_sample; + } +} + +void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n", + input, output, needs_init, use_float_processing); +} + +void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { + auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)}; + if (needs_init) { + std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState)); + } + + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + auto output_buffer{ + processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; + + if (use_float_processing) { + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, + processor.sample_count); + } else { + ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_, + processor.sample_count); + } +} + +bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h new file mode 100644 index 000000000..4c9c42d29 --- /dev/null +++ b/src/audio_core/renderer/command/effect/biquad_filter.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "audio_core/renderer/voice/voice_state.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to + * the output mix buffer. + */ +struct BiquadFilterCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Input parameters for biquad + VoiceInfo::BiquadFilterParameter biquad; + /// Biquad state, updated each call + CpuAddr state; + /// If true, reset the state + bool needs_init; + /// If true, use float processing rather than int + bool use_float_processing; +}; + +/** + * Biquad filter float implementation. + * + * @param output - Output container for filtered samples. + * @param input - Input container for samples to be filtered. + * @param b - Feedforward coefficients. + * @param a - Feedback coefficients. + * @param state - State to track previous samples. + * @param sample_count - Number of samples to process. + */ +void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, + std::array<s16, 3>& b, std::array<s16, 2>& a, + VoiceState::BiquadFilterState& state, const u32 sample_count); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/capture.cpp b/src/audio_core/renderer/command/effect/capture.cpp new file mode 100644 index 000000000..042fd286e --- /dev/null +++ b/src/audio_core/renderer/command/effect/capture.cpp @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/capture.h" +#include "audio_core/renderer/effect/aux_.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { +/** + * Reset an AuxBuffer. + * + * @param memory - Core memory for writing. + * @param aux_info - Memory address pointing to the AuxInfo to reset. + */ +static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { + if (aux_info == 0) { + LOG_ERROR(Service_Audio, "Aux info is 0!"); + return; + } + + memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0); + memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0); + memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0); +} + +/** + * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if + * update_count is set, to notify the game that an update happened. + * + * @param memory - Core memory for writing. + * @param send_info_ - Header information for where to write the mix buffer. + * @param send_buffer - Memory address to write the mix buffer to. + * @param count_max - Maximum number of samples in the receiving buffer. + * @param input - Input mix buffer to write. + * @param write_count_ - Number of samples to write. + * @param write_offset - Current offset to begin writing the receiving buffer at. + * @param update_count - If non-zero, send_info_ will be updated. + * @return Number of samples written. + */ +static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, + const CpuAddr send_buffer, u32 count_max, std::span<const s32> input, + const u32 write_count_, const u32 write_offset, + const u32 update_count) { + if (write_count_ > count_max) { + LOG_ERROR(Service_Audio, + "write_count must be smaller than count_max! write_count {}, count_max {}", + write_count_, count_max); + return 0; + } + + if (send_info_ == 0) { + LOG_ERROR(Service_Audio, "send_info is 0!"); + return 0; + } + + if (input.empty()) { + LOG_ERROR(Service_Audio, "input buffer is empty!"); + return 0; + } + + if (send_buffer == 0) { + LOG_ERROR(Service_Audio, "send_buffer is 0!"); + return 0; + } + + if (count_max == 0) { + return 0; + } + + AuxInfo::AuxBufferInfo send_info{}; + memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); + + u32 target_write_offset{send_info.dsp_info.write_offset + write_offset}; + if (target_write_offset > count_max || write_count_ == 0) { + return 0; + } + + u32 write_count{write_count_}; + u32 write_pos{0}; + while (write_count > 0) { + u32 to_write{std::min(count_max - target_write_offset, write_count)}; + + if (to_write > 0) { + memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), + &input[write_pos], to_write * sizeof(s32)); + } + + target_write_offset = (target_write_offset + to_write) % count_max; + write_count -= to_write; + write_pos += to_write; + } + + if (update_count) { + const auto count_diff{send_info.dsp_info.total_sample_count - + send_info.cpu_info.total_sample_count}; + if (count_diff >= count_max) { + auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count}; + if (dsp_lost_count - send_info.cpu_info.lost_sample_count < + send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) { + dsp_lost_count = send_info.cpu_info.lost_sample_count - 1; + } + send_info.dsp_info.lost_sample_count = dsp_lost_count; + } + + send_info.dsp_info.write_offset = + (send_info.dsp_info.write_offset + update_count + count_max) % count_max; + + auto new_sample_count{send_info.dsp_info.total_sample_count + update_count}; + if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) { + new_sample_count = send_info.cpu_info.total_sample_count - 1; + } + send_info.dsp_info.total_sample_count = new_sample_count; + } + + memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); + + return write_count_; +} + +void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled, + input, output); +} + +void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) { + if (effect_enabled) { + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer, + processor.sample_count, write_offset, update_count); + } else { + ResetAuxBufferDsp(*processor.memory, send_buffer_info); + } +} + +bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/capture.h b/src/audio_core/renderer/command/effect/capture.h new file mode 100644 index 000000000..8670acb24 --- /dev/null +++ b/src/audio_core/renderer/command/effect/capture.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory + * address. + */ +struct CaptureCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Meta info for writing + CpuAddr send_buffer_info; + /// Game memory write buffer + CpuAddr send_buffer; + /// Max samples to read/write + u32 count_max; + /// Current read/write offset + u32 write_offset; + /// Number of samples to update per call + u32 update_count; + /// is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp new file mode 100644 index 000000000..2ebc140f1 --- /dev/null +++ b/src/audio_core/renderer/command/effect/compressor.cpp @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <cmath> +#include <span> +#include <vector> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/compressor.h" +#include "audio_core/renderer/effect/compressor.h" + +namespace AudioCore::AudioRenderer { + +static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, + CompressorInfo::State& state) { + const auto ratio{1.0f / params.compressor_ratio}; + auto makeup_gain{0.0f}; + if (params.makeup_gain_enabled) { + makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f; + } + state.makeup_gain = makeup_gain; + state.unk_18 = params.unk_28; + + const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f}; + const auto b{(a - std::trunc(a)) * 0.69315f}; + const auto c{std::pow(2.0f, b)}; + + state.unk_0C = (1.0f - ratio) / 6.0f; + state.unk_14 = params.threshold + 1.5f; + state.unk_10 = params.threshold - 1.5f; + state.unk_20 = c; +} + +static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, + CompressorInfo::State& state) { + std::memset(&state, 0, sizeof(CompressorInfo::State)); + + state.unk_00 = 0; + state.unk_04 = 1.0f; + state.unk_08 = 1.0f; + + SetCompressorEffectParameter(params, state); +} + +static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, + CompressorInfo::State& state, bool enabled, + std::vector<std::span<const s32>> input_buffers, + std::vector<std::span<s32>> output_buffers, u32 sample_count) { + if (enabled) { + auto state_00{state.unk_00}; + auto state_04{state.unk_04}; + auto state_08{state.unk_08}; + auto state_18{state.unk_18}; + + for (u32 i = 0; i < sample_count; i++) { + auto a{0.0f}; + for (s16 channel = 0; channel < params.channel_count; channel++) { + const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])}; + a += (input_sample * input_sample).to_float(); + } + + state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00); + + auto b{-100.0f}; + auto c{0.0f}; + if (state_00 >= 1.0e-10) { + b = std::log10(state_00) * 10.0f; + c = 1.0f; + } + + if (b >= state.unk_10) { + const auto d{b >= state.unk_14 + ? ((1.0f / params.compressor_ratio) - 1.0f) * + (b - params.threshold) + : (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C}; + const auto e{d / 20.0f * 3.3219f}; + const auto f{(e - std::trunc(e)) * 0.69315f}; + c = std::pow(2.0f, f); + } + + state_18 = params.unk_28; + auto tmp{c}; + if ((state_04 - c) <= 0.08f) { + state_18 = params.unk_2C; + if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) { + tmp = state_04; + } + } + + state_04 = tmp; + state_08 += (c - state_08) * state_18; + + for (s16 channel = 0; channel < params.channel_count; channel++) { + output_buffers[channel][i] = static_cast<s32>( + static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20); + } + } + + state.unk_00 = state_00; + state.unk_04 = state_04; + state.unk_08 = state_08; + state.unk_18 = state_18; + } else { + for (s16 channel = 0; channel < params.channel_count; channel++) { + if (params.inputs[channel] != params.outputs[channel]) { + std::memcpy((char*)output_buffers[channel].data(), + (char*)input_buffers[channel].data(), + output_buffers[channel].size_bytes()); + } + } + } +} + +void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled); + for (s16 i = 0; i < parameter.channel_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (s16 i = 0; i < parameter.channel_count; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (s16 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<CompressorInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == CompressorInfo::ParameterState::Updating) { + SetCompressorEffectParameter(parameter, *state_); + } else if (parameter.state == CompressorInfo::ParameterState::Initialized) { + InitializeCompressorEffect(parameter, *state_); + } + } + + ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/compressor.h b/src/audio_core/renderer/command/effect/compressor.h new file mode 100644 index 000000000..f8e96cb43 --- /dev/null +++ b/src/audio_core/renderer/command/effect/compressor.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/compressor.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for limiting volume between a high and low threshold. + * Version 1. + */ +struct CompressorCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + CompressorInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/delay.cpp b/src/audio_core/renderer/command/effect/delay.cpp new file mode 100644 index 000000000..a4e408d40 --- /dev/null +++ b/src/audio_core/renderer/command/effect/delay.cpp @@ -0,0 +1,238 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/delay.h" + +namespace AudioCore::AudioRenderer { +/** + * Update the DelayInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + */ +static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params, + DelayInfo::State& state) { + auto channel_spread{params.channel_spread}; + state.feedback_gain = params.feedback_gain * 0.97998046875f; + state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread); + if (params.channel_count == 4 || params.channel_count == 6) { + channel_spread >>= 1; + } + state.delay_feedback_cross_gain = channel_spread * state.feedback_gain; + state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f; + state.lowpass_gain = 1.0f - state.lowpass_feedback_gain; +} + +/** + * Initialize a new DelayInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + */ +static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params, + DelayInfo::State& state, + [[maybe_unused]] const CpuAddr workbuffer) { + state = {}; + + for (u32 channel = 0; channel < params.channel_count; channel++) { + Common::FixedPoint<32, 32> sample_count_max{0.064f}; + sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max; + + Common::FixedPoint<18, 14> delay_time{params.delay_time}; + delay_time *= params.sample_rate / 1000; + Common::FixedPoint<32, 32> sample_count{delay_time}; + + if (sample_count > sample_count_max) { + sample_count = sample_count_max; + } + + state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor(); + state.delay_lines[channel].sample_count = sample_count.to_int_floor(); + state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0); + if (state.delay_lines[channel].buffer.size() == 0) { + state.delay_lines[channel].buffer.push_back(0); + } + state.delay_lines[channel].buffer_pos = 0; + state.delay_lines[channel].decay_rate = 1.0f; + } + + SetDelayEffectParameter(params, state); +} + +/** + * Delay effect impl, according to the parameters and current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @tparam NumChannels - Number of channels to process. 1-6. + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeDelayEffect). + * @param inputs - Input mix buffers to performan the delay on. + * @param outputs - Output mix buffers to receive the delayed samples. + * @param sample_count - Number of samples to process. + */ +template <size_t NumChannels> +static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, + std::vector<std::span<const s32>>& inputs, + std::vector<std::span<s32>>& outputs, const u32 sample_count) { + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + input_samples[channel] = inputs[channel][sample_index] * 64; + } + + std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + delay_samples[channel] = state.delay_lines[channel].Read(); + } + + // clang-format off + std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{}; + if constexpr (NumChannels == 1) { + matrix = {{ + {state.feedback_gain}, + }}; + } else if constexpr (NumChannels == 2) { + matrix = {{ + {state.delay_feedback_gain, state.delay_feedback_cross_gain}, + {state.delay_feedback_cross_gain, state.delay_feedback_gain}, + }}; + } else if constexpr (NumChannels == 4) { + matrix = {{ + {state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f}, + {state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain}, + {state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain}, + {0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain}, + }}; + } else if constexpr (NumChannels == 6) { + matrix = {{ + {state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f}, + {0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain}, + {state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f}, + {state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain}, + {0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain}, + }}; + } + // clang-format on + + std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + Common::FixedPoint<50, 14> delay{}; + for (u32 j = 0; j < NumChannels; j++) { + delay += delay_samples[j] * matrix[j][channel]; + } + gained_samples[channel] = input_samples[channel] * params.in_gain + delay; + } + + for (u32 channel = 0; channel < NumChannels; channel++) { + state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain + + state.lowpass_z[channel] * state.lowpass_feedback_gain; + state.delay_lines[channel].Write(state.lowpass_z[channel]); + } + + for (u32 channel = 0; channel < NumChannels; channel++) { + outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain + + delay_samples[channel] * params.wet_gain) + .to_int_floor() / + 64; + } + } +} + +/** + * Apply a delay effect if enabled, according to the parameters and current state, on the input mix + * buffers, saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeDelayEffect). + * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to performan the delay on. + * @param outputs - Output mix buffers to receive the delayed samples. + * @param sample_count - Number of samples to process. + */ +static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, + const bool enabled, std::vector<std::span<const s32>>& inputs, + std::vector<std::span<s32>>& outputs, const u32 sample_count) { + + if (!IsChannelCountValid(params.channel_count)) { + LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count); + return; + } + + if (enabled) { + switch (params.channel_count) { + case 1: + ApplyDelay<1>(params, state, inputs, outputs, sample_count); + break; + case 2: + ApplyDelay<2>(params, state, inputs, outputs, sample_count); + break; + case 4: + ApplyDelay<4>(params, state, inputs, outputs, sample_count); + break; + case 6: + ApplyDelay<6>(params, state, inputs, outputs, sample_count); + break; + default: + for (u32 channel = 0; channel < params.channel_count; channel++) { + if (inputs[channel].data() != outputs[channel].data()) { + std::memcpy(outputs[channel].data(), inputs[channel].data(), + sample_count * sizeof(s32)); + } + } + break; + } + } else { + for (u32 channel = 0; channel < params.channel_count; channel++) { + if (inputs[channel].data() != outputs[channel].data()) { + std::memcpy(outputs[channel].data(), inputs[channel].data(), + sample_count * sizeof(s32)); + } + } + } +} + +void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void DelayCommand::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (s16 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<DelayInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == DelayInfo::ParameterState::Updating) { + SetDelayEffectParameter(parameter, *state_); + } else if (parameter.state == DelayInfo::ParameterState::Initialized) { + InitializeDelayEffect(parameter, *state_, workbuffer); + } + } + ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/delay.h b/src/audio_core/renderer/command/effect/delay.h new file mode 100644 index 000000000..b7a15ae6b --- /dev/null +++ b/src/audio_core/renderer/command/effect/delay.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/delay.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters + * and state, outputs receives the delayed samples. + */ +struct DelayCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + DelayInfo::ParameterVersion1 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp new file mode 100644 index 000000000..c4bf3943a --- /dev/null +++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp @@ -0,0 +1,437 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <numbers> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/i3dl2_reverb.h" + +namespace AudioCore::AudioRenderer { + +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{ + 5.0f, + 6.0f, + 13.0f, + 14.0f, +}; +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{ + 45.7042007446f, + 82.7817001343f, + 149.938293457f, + 271.575805664f, +}; +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f, + 9.0f, 7.0f}; +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f, + 10.0f, 6.0f}; +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyTapTimes{ + 0.0171360000968f, + 0.0591540001333f, + 0.161733001471f, + 0.390186011791f, + 0.425262004137f, + 0.455410987139f, + 0.689737021923f, + 0.74590998888f, + 0.833844006062f, + 0.859502017498f, + 0.0f, + 0.0750240013003f, + 0.168788000941f, + 0.299901008606f, + 0.337442994118f, + 0.371903002262f, + 0.599011003971f, + 0.716741025448f, + 0.817858994007f, + 0.85166400671f, +}; + +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyGains{ + 0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f, + 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f, + 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; + +/** + * Update the I3dl2ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param reset - If enabled, the state buffers will be reset. Only set this on initialize. + */ +static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params, + I3dl2ReverbInfo::State& state, const bool reset) { + const auto pow_10 = [](f32 val) -> f32 { + return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val); + }; + const auto sin = [](f32 degrees) -> f32 { + return std::sin(degrees * std::numbers::pi_v<f32> / 180.0f); + }; + const auto cos = [](f32 degrees) -> f32 { + return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f); + }; + + Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000.0f}; + + state.dry_gain = params.dry_gain; + Common::FixedPoint<50, 14> early_gain{ + std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f}; + state.early_gain = pow_10(early_gain.to_float()); + Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) / + 2000.0f}; + state.late_gain = pow_10(late_gain.to_float()); + + Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)}; + if (hf_gain >= 1.0f) { + state.lowpass_1 = 0.0f; + state.lowpass_2 = 1.0f; + } else { + const auto reference_hf{(params.reference_HF * 256.0f) / + static_cast<f32>(params.sample_rate)}; + const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()}; + const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))}; + const Common::FixedPoint<50, 14> c{ + std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))}; + + state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f); + state.lowpass_2 = 1.0f - state.lowpass_1; + } + + state.early_to_late_taps = + (((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int(); + state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f; + + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { + auto curr_delay{ + ((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) * + (MaxDelayLineTimes[i] - MinDelayLineTimes[i])) * + delay) + .to_int()}; + state.fdn_delay_lines[i].SetDelay(curr_delay); + + const auto a{ + (static_cast<f32>(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay + + state.decay_delay_lines1[i].delay) * + -60.0f) / + (params.late_reverb_decay_time * static_cast<f32>(params.sample_rate))}; + const auto b{a / params.late_reverb_HF_decay_ratio}; + const auto c{ + cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate)) / + sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate))}; + const auto d{pow_10((b - a) / 40.0f)}; + const auto e{pow_10((b + a) / 40.0f) * 0.7071f}; + + state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d); + state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d); + state.lowpass_coeff[i][2] = (c - d) / (c + d); + + state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo; + state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f; + } + + if (reset) { + state.shelf_filter.fill(0.0f); + state.lowpass_0 = 0.0f; + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { + std::ranges::fill(state.fdn_delay_lines[i].buffer, 0); + std::ranges::fill(state.decay_delay_lines0[i].buffer, 0); + std::ranges::fill(state.decay_delay_lines1[i].buffer, 0); + } + std::ranges::fill(state.center_delay_line.buffer, 0); + std::ranges::fill(state.early_delay_line.buffer, 0); + } + + const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f}; + const auto reflection_delay{params.reflection_delay * 1000.0f}; + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) { + auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()}; + if (length >= state.early_delay_line.max_delay) { + length = state.early_delay_line.max_delay; + } + state.early_tap_steps[i] = length; + } +} + +/** + * Initialize a new I3dl2ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + */ +static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params, + I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) { + state = {}; + Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000}; + + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { + auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.fdn_delay_lines[i].Initialize(fdn_delay_time); + + auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.decay_delay_lines0[i].Initialize(decay0_delay_time); + + auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.decay_delay_lines1[i].Initialize(decay1_delay_time); + } + + const auto center_delay_time{(5 * delay).to_uint_floor()}; + state.center_delay_line.Initialize(center_delay_time); + + const auto early_delay_time{(400 * delay).to_uint_floor()}; + state.early_delay_line.Initialize(early_delay_time); + + UpdateI3dl2ReverbEffectParameter(params, state, true); +} + +/** + * Pass-through the effect, copying input to output directly, with no reverb applied. + * + * @param inputs - Array of input mix buffers to copy. + * @param outputs - Array of output mix buffers to receive copy. + * @param channel_count - Number of channels in inputs and outputs. + * @param sample_count - Number of samples within each channel (unused). + */ +static void ApplyI3dl2ReverbEffectBypass(std::span<std::span<const s32>> inputs, + std::span<std::span<s32>> outputs, const u32 channel_count, + [[maybe_unused]] const u32 sample_count) { + for (u32 i = 0; i < channel_count; i++) { + if (inputs[i].data() != outputs[i].data()) { + std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); + } + } +} + +/** + * Tick the delay lines, reading and returning their current output, and writing a new decaying + * sample (mix). + * + * @param decay0 - The first decay line. + * @param decay1 - The second decay line. + * @param fdn - Feedback delay network. + * @param mix - The new calculated sample to be written and decayed. + * @return The next delayed and decayed sample. + */ +static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0, + I3dl2ReverbInfo::I3dl2DelayLine& decay1, + I3dl2ReverbInfo::I3dl2DelayLine& fdn, + const Common::FixedPoint<50, 14> mix) { + auto val{decay0.Read()}; + auto mixed{mix - (val * decay0.wet_gain)}; + auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)}; + + val = decay1.Read(); + mixed = out - (val * decay1.wet_gain); + out = decay1.Tick(mixed) + (mixed * decay1.wet_gain); + + fdn.Tick(out); + return out; +} + +/** + * Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @tparam NumChannels - Number of channels to process. 1-6. + Inputs/outputs should have this many buffers. + * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect). + * @param inputs - Input mix buffers to perform the reverb on. + * @param outputs - Output mix buffers to receive the reverbed samples. + * @param sample_count - Number of samples to process. + */ +template <size_t NumChannels> +static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state, + std::span<std::span<const s32>> inputs, + std::span<std::span<s32>> outputs, const u32 sample_count) { + constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{ + 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, + }; + constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{ + 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3, + }; + constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{ + 2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5, + }; + + std::span<const u8> tap_indexes{}; + if constexpr (NumChannels == 1) { + tap_indexes = OutTapIndexes1Ch; + } else if constexpr (NumChannels == 2) { + tap_indexes = OutTapIndexes2Ch; + } else if constexpr (NumChannels == 4) { + tap_indexes = OutTapIndexes4Ch; + } else if constexpr (NumChannels == 6) { + tap_indexes = OutTapIndexes6Ch; + } + + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + Common::FixedPoint<50, 14> early_to_late_tap{ + state.early_delay_line.TapOut(state.early_to_late_taps)}; + std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{}; + + for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) { + output_samples[tap_indexes[early_tap]] += + state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) * + EarlyGains[early_tap]; + if constexpr (NumChannels == 6) { + output_samples[static_cast<u32>(Channels::LFE)] += + state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) * + EarlyGains[early_tap]; + } + } + + Common::FixedPoint<50, 14> current_sample{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + current_sample += inputs[channel][sample_index]; + } + + state.lowpass_0 = + (current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float(); + state.early_delay_line.Tick(state.lowpass_0); + + for (u32 channel = 0; channel < NumChannels; channel++) { + output_samples[channel] *= state.early_gain; + } + + std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{}; + for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) { + filtered_samples[delay_line] = + state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] + + state.shelf_filter[delay_line]; + state.shelf_filter[delay_line] = + (filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] + + state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1]) + .to_float(); + } + + const std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{ + filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain, + -filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain, + filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain, + filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain, + }; + + std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{}; + for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) { + allpass_samples[delay_line] = Axfx2AllPassTick( + state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line], + state.fdn_delay_lines[delay_line], mix_matrix[delay_line]); + } + + if constexpr (NumChannels == 6) { + const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{ + allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3], + allpass_samples[3], allpass_samples[2], allpass_samples[3], + }; + + for (u32 channel = 0; channel < NumChannels; channel++) { + Common::FixedPoint<50, 14> allpass{}; + + if (channel == static_cast<u32>(Channels::Center)) { + allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f); + } else { + allpass = allpass_outputs[channel]; + } + + auto out_sample{output_samples[channel] + allpass + + state.dry_gain * static_cast<f32>(inputs[channel][sample_index])}; + + outputs[channel][sample_index] = + static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f)); + } + } else { + for (u32 channel = 0; channel < NumChannels; channel++) { + auto out_sample{output_samples[channel] + allpass_samples[channel] + + state.dry_gain * static_cast<f32>(inputs[channel][sample_index])}; + outputs[channel][sample_index] = + static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f)); + } + } + } +} + +/** + * Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect). + * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to performan the delay on. + * @param outputs - Output mix buffers to receive the delayed samples. + * @param sample_count - Number of samples to process. + */ +static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params, + I3dl2ReverbInfo::State& state, const bool enabled, + std::span<std::span<const s32>> inputs, + std::span<std::span<s32>> outputs, const u32 sample_count) { + if (enabled) { + switch (params.channel_count) { + case 0: + return; + case 1: + ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count); + break; + case 2: + ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count); + break; + case 4: + ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count); + break; + case 6: + ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count); + break; + default: + ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + break; + } + } else { + ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + } +} + +void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled); + for (u32 i = 0; i < parameter.channel_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < parameter.channel_count; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<I3dl2ReverbInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) { + UpdateI3dl2ReverbEffectParameter(parameter, *state_, false); + } else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) { + InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer); + } + } + ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.h b/src/audio_core/renderer/command/effect/i3dl2_reverb.h new file mode 100644 index 000000000..243877056 --- /dev/null +++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/i3dl2.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to + * the I3DL2 spec, outputs receives the results. + */ +struct I3dl2ReverbCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + I3dl2ReverbInfo::ParameterVersion1 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/light_limiter.cpp b/src/audio_core/renderer/command/effect/light_limiter.cpp new file mode 100644 index 000000000..e8fb0e2fc --- /dev/null +++ b/src/audio_core/renderer/command/effect/light_limiter.cpp @@ -0,0 +1,222 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/light_limiter.h" + +namespace AudioCore::AudioRenderer { +/** + * Update the LightLimiterInfo state according to the given parameters. + * A no-op. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + */ +static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params, + LightLimiterInfo::State& state) {} + +/** + * Initialize a new LightLimiterInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + */ +static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params, + LightLimiterInfo::State& state, const CpuAddr workbuffer) { + state = {}; + state.samples_average.fill(0.0f); + state.compression_gain.fill(1.0f); + state.look_ahead_sample_offsets.fill(0); + for (u32 i = 0; i < params.channel_count; i++) { + state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f); + } +} + +/** + * Apply a light limiter effect if enabled, according to the current state, on the input mix + * buffers, saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeLightLimiterEffect). + * @param enabled - If enabled, limiter will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to perform the limiter on. + * @param outputs - Output mix buffers to receive the limited samples. + * @param sample_count - Number of samples to process. + * @params statistics - Optional output statistics, only used with version 2. + */ +static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params, + LightLimiterInfo::State& state, const bool enabled, + std::vector<std::span<const s32>>& inputs, + std::vector<std::span<s32>>& outputs, const u32 sample_count, + LightLimiterInfo::StatisticsInternal* statistics) { + constexpr s64 min{std::numeric_limits<s32>::min()}; + constexpr s64 max{std::numeric_limits<s32>::max()}; + + const auto recip_estimate = [](f64 a) -> f64 { + s32 q, s; + f64 r; + q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */ + r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */ + s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */ + return ((f64)s / 256.0); + }; + + if (enabled) { + if (statistics && params.statistics_reset_required) { + for (u32 i = 0; i < params.channel_count; i++) { + statistics->channel_compression_gain_min[i] = 1.0f; + statistics->channel_max_sample[i] = 0; + } + } + + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + for (u32 channel = 0; channel < params.channel_count; channel++) { + auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) / + Common::FixedPoint<49, 15>::one) * + params.input_gain}; + auto abs_sample{sample}; + if (sample < 0.0f) { + abs_sample = -sample; + } + auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff + : params.release_coeff}; + state.samples_average[channel] += + ((abs_sample - state.samples_average[channel]) * coeff).to_float(); + + // Reciprocal estimate + auto new_average_sample{Common::FixedPoint<49, 15>( + recip_estimate(state.samples_average[channel].to_double()))}; + if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) { + // Two Newton-Raphson steps + auto temp{2.0 - (state.samples_average[channel] * new_average_sample)}; + new_average_sample = 2.0 - (state.samples_average[channel] * temp); + } + + auto above_threshold{state.samples_average[channel] > params.threshold}; + auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f}; + coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff + : params.release_coeff; + state.compression_gain[channel] += + (attenuation - state.compression_gain[channel]) * coeff; + + auto lookahead_sample{ + state.look_ahead_sample_buffers[channel] + [state.look_ahead_sample_offsets[channel]]}; + + state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] = + sample; + state.look_ahead_sample_offsets[channel] = + (state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min; + + outputs[channel][sample_index] = static_cast<s32>( + std::clamp((lookahead_sample * state.compression_gain[channel] * + params.output_gain * Common::FixedPoint<49, 15>::one) + .to_long(), + min, max)); + + if (statistics) { + statistics->channel_max_sample[channel] = + std::max(statistics->channel_max_sample[channel], abs_sample.to_float()); + statistics->channel_compression_gain_min[channel] = + std::min(statistics->channel_compression_gain_min[channel], + state.compression_gain[channel].to_float()); + } + } + } + } else { + for (u32 i = 0; i < params.channel_count; i++) { + if (params.inputs[i] != params.outputs[i]) { + std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); + } + } + } +} + +void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("LightLimiterVersion1Command\n\tinputs: "); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == LightLimiterInfo::ParameterState::Updating) { + UpdateLightLimiterEffectParameter(parameter, *state_); + } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) { + InitializeLightLimiterEffect(parameter, *state_, workbuffer); + } + } + + LightLimiterInfo::StatisticsInternal* statistics{nullptr}; + ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count, statistics); +} + +bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n"); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == LightLimiterInfo::ParameterState::Updating) { + UpdateLightLimiterEffectParameter(parameter, *state_); + } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) { + InitializeLightLimiterEffect(parameter, *state_, workbuffer); + } + } + + auto statistics{reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(result_state)}; + ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count, statistics); +} + +bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/light_limiter.h b/src/audio_core/renderer/command/effect/light_limiter.h new file mode 100644 index 000000000..5d98272c7 --- /dev/null +++ b/src/audio_core/renderer/command/effect/light_limiter.h @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/light_limiter.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for limiting volume between a high and low threshold. + * Version 1. + */ +struct LightLimiterVersion1Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + LightLimiterInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +/** + * AudioRenderer command for limiting volume between a high and low threshold. + * Version 2 with output statistics. + */ +struct LightLimiterVersion2Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + LightLimiterInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Optional statistics, sent back to the sysmodule + CpuAddr result_state; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp new file mode 100644 index 000000000..b3c3ba4ba --- /dev/null +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/biquad_filter.h" +#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" + +namespace AudioCore::AudioRenderer { + +void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n", + input, output, needs_init[0], needs_init[1]); +} + +void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { + if (filter_tap_count > MaxBiquadFilters) { + LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count); + filter_tap_count = MaxBiquadFilters; + } + + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + auto output_buffer{ + processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; + + // TODO: Fix this, currently just applies the filter to the input twice, + // and doesn't chain the biquads together at all. + for (u32 i = 0; i < filter_tap_count; i++) { + auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(states[i])}; + if (needs_init[i]) { + std::memset(state, 0, sizeof(VoiceState::BiquadFilterState)); + } + + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, + processor.sample_count); + } +} + +bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h new file mode 100644 index 000000000..99c2c0830 --- /dev/null +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying multiple biquads at once. + */ +struct MultiTapBiquadFilterCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Biquad parameters + std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads; + /// Biquad states, updated each call + std::array<CpuAddr, MaxBiquadFilters> states; + /// If each biquad needs initialisation + std::array<bool, MaxBiquadFilters> needs_init; + /// Number of active biquads + u8 filter_tap_count; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/reverb.cpp b/src/audio_core/renderer/command/effect/reverb.cpp new file mode 100644 index 000000000..fe2b1eb43 --- /dev/null +++ b/src/audio_core/renderer/command/effect/reverb.cpp @@ -0,0 +1,440 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <numbers> +#include <ranges> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/reverb.h" + +namespace AudioCore::AudioRenderer { + +constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = { + 53.9532470703125f, + 79.19256591796875f, + 116.23876953125f, + 170.61529541015625f, +}; + +constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = { + 7.0f, + 9.0f, + 13.0f, + 17.0f, +}; + +constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps + 1>, ReverbInfo::NumEarlyModes> + EarlyDelayTimes = { + {{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f, + 9.899963f, 12.000000f, 12.500000f}, + {0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f, + 29.599976f, 21.199951f, 24.799988f, 40.000000f}, + {0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f, + 45.199951f, 46.799988f, 0.000000f, 50.000000f}, + {33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f, + 34.199951f, 0.000000f, 43.299988f, 50.000000f}, + {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, + 0.000000f, 0.000000f, 0.000000f}}, +}; + +constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps>, ReverbInfo::NumEarlyModes> + EarlyDelayGains = {{ + {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, + 0.679993f, 0.679993f}, + {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f, + 0.679993f, 0.679993f}, + {0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f, + 0.679993f, 0.000000f}, + {0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f, + 0.759949f, 0.649963f}, + {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, + 0.000000f, 0.000000f}, + }}; + +constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes> + FdnDelayTimes = {{ + {53.953247f, 79.192566f, 116.238770f, 130.615295f}, + {53.953247f, 79.192566f, 116.238770f, 170.615295f}, + {5.000000f, 10.000000f, 5.000000f, 10.000000f}, + {47.029968f, 71.000000f, 103.000000f, 170.000000f}, + {53.953247f, 79.192566f, 116.238770f, 170.615295f}, + }}; + +constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes> + DecayDelayTimes = {{ + {7.000000f, 9.000000f, 13.000000f, 17.000000f}, + {7.000000f, 9.000000f, 13.000000f, 17.000000f}, + {1.000000f, 1.000000f, 1.000000f, 1.000000f}, + {7.000000f, 7.000000f, 13.000000f, 9.000000f}, + {7.000000f, 9.000000f, 13.000000f, 17.000000f}, + }}; + +/** + * Update the ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + */ +static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params, + ReverbInfo::State& state) { + const auto pow_10 = [](f32 val) -> f32 { + return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val); + }; + const auto cos = [](f32 degrees) -> f32 { + return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f); + }; + + static bool unk_initialized{false}; + static Common::FixedPoint<50, 14> unk_value{}; + + const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)}; + const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)}; + + for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) { + auto early_delay{ + ((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()}; + early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max); + state.early_delay_times[i] = early_delay + 1; + state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) * + EarlyDelayGains[params.early_mode][i]; + } + + if (params.channel_count == 2) { + state.early_gains[4] * 0.5f; + state.early_gains[5] * 0.5f; + } + + auto pre_time{ + ((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()}; + state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max); + + if (!unk_initialized) { + unk_value = cos((1280.0f / sample_rate).to_float()); + unk_initialized = true; + } + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()}; + state.fdn_delay_lines[i].sample_count = + std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max); + state.fdn_delay_lines[i].buffer_end = + &state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1]; + + const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()}; + state.decay_delay_lines[i].sample_count = + std::min(decay_delay, state.decay_delay_lines[i].sample_count_max); + state.decay_delay_lines[i].buffer_end = + &state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1]; + + state.decay_delay_lines[i].decay = + 0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration)); + + auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) + + state.decay_delay_lines[i].sample_count_max) * + -3}; + auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)}; + Common::FixedPoint<50, 14> c{0.0f}; + Common::FixedPoint<50, 14> d{0.0f}; + auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)}; + + if (hf_decay_ratio > 0.99493408203125f) { + c = 0.0f; + d = 1.0f; + } else { + const auto e{ + pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())}; + const auto f{1.0f - e}; + const auto g{2.0f - (unk_value * e * 2)}; + const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))}; + + c = (g - h) / (f * 2.0f); + d = 1.0f - c; + } + + state.hf_decay_prev_gain[i] = c; + state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f; + state.prev_feedback_output[i] = 0; + } +} + +/** + * Initialize a new ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins. + */ +static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params, + ReverbInfo::State& state, const CpuAddr workbuffer, + const bool long_size_pre_delay_supported) { + state = {}; + + auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)}; + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f); + + auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f); + } + + const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f}; + const auto pre_delay_line{(pre_delay * delay).to_uint_floor()}; + state.pre_delay_line.Initialize(pre_delay_line, 1.0f); + + const auto center_delay_time{(5 * delay).to_uint_floor()}; + state.center_delay_line.Initialize(center_delay_time, 1.0f); + + UpdateReverbEffectParameter(params, state); + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + std::ranges::fill(state.fdn_delay_lines[i].buffer, 0); + std::ranges::fill(state.decay_delay_lines[i].buffer, 0); + } + std::ranges::fill(state.center_delay_line.buffer, 0); + std::ranges::fill(state.pre_delay_line.buffer, 0); +} + +/** + * Pass-through the effect, copying input to output directly, with no reverb applied. + * + * @param inputs - Array of input mix buffers to copy. + * @param outputs - Array of output mix buffers to receive copy. + * @param channel_count - Number of channels in inputs and outputs. + * @param sample_count - Number of samples within each channel. + */ +static void ApplyReverbEffectBypass(std::span<std::span<const s32>> inputs, + std::span<std::span<s32>> outputs, const u32 channel_count, + const u32 sample_count) { + for (u32 i = 0; i < channel_count; i++) { + if (inputs[i].data() != outputs[i].data()) { + std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); + } + } +} + +/** + * Tick the delay lines, reading and returning their current output, and writing a new decaying + * sample (mix). + * + * @param decay - The decay line. + * @param fdn - Feedback delay network. + * @param mix - The new calculated sample to be written and decayed. + * @return The next delayed and decayed sample. + */ +static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay, + ReverbInfo::ReverbDelayLine& fdn, + const Common::FixedPoint<50, 14> mix) { + const auto val{decay.Read()}; + const auto mixed{mix - (val * decay.decay)}; + const auto out{decay.Tick(mixed) + (mixed * decay.decay)}; + + fdn.Tick(out); + return out; +} + +/** + * Impl. Apply a Reverb according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @tparam NumChannels - Number of channels to process. 1-6. + Inputs/outputs should have this many buffers. + * @param params - Input parameters to update the state. + * @param state - State to use, must be initialized (see InitializeReverbEffect). + * @param inputs - Input mix buffers to perform the reverb on. + * @param outputs - Output mix buffers to receive the reverbed samples. + * @param sample_count - Number of samples to process. + */ +template <size_t NumChannels> +static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state, + std::vector<std::span<const s32>>& inputs, + std::vector<std::span<s32>>& outputs, const u32 sample_count) { + constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{ + 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, + }; + constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{ + 0, 0, 1, 1, 0, 1, 2, 2, 3, 3, + }; + constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{ + 0, 0, 1, 1, 2, 2, 4, 4, 5, 5, + }; + + std::span<const u8> tap_indexes{}; + if constexpr (NumChannels == 1) { + tap_indexes = OutTapIndexes1Ch; + } else if constexpr (NumChannels == 2) { + tap_indexes = OutTapIndexes2Ch; + } else if constexpr (NumChannels == 4) { + tap_indexes = OutTapIndexes4Ch; + } else if constexpr (NumChannels == 6) { + tap_indexes = OutTapIndexes6Ch; + } + + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{}; + + for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) { + const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) * + state.early_gains[early_tap]}; + output_samples[tap_indexes[early_tap]] += sample; + if constexpr (NumChannels == 6) { + output_samples[static_cast<u32>(Channels::LFE)] += sample; + } + } + + if constexpr (NumChannels == 6) { + output_samples[static_cast<u32>(Channels::LFE)] *= 0.2f; + } + + Common::FixedPoint<50, 14> input_sample{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + input_sample += inputs[channel][sample_index]; + } + + input_sample *= 64; + input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain); + state.pre_delay_line.Write(input_sample); + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + state.prev_feedback_output[i] = + state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] + + state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i]; + } + + Common::FixedPoint<50, 14> pre_delay_sample{ + state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)}; + + std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> mix_matrix{ + state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample, + -state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample, + state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample, + state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample, + }; + + std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> allpass_samples{}; + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i], + state.fdn_delay_lines[i], mix_matrix[i]); + } + + const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)}; + const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)}; + + if constexpr (NumChannels == 6) { + const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{ + allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3], + allpass_samples[3], allpass_samples[2], allpass_samples[3], + }; + + for (u32 channel = 0; channel < NumChannels; channel++) { + auto in_sample{inputs[channel][sample_index] * dry_gain}; + + Common::FixedPoint<50, 14> allpass{}; + if (channel == static_cast<u32>(Channels::Center)) { + allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f); + } else { + allpass = allpass_outputs[channel]; + } + + auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64}; + outputs[channel][sample_index] = (in_sample + out_sample).to_int(); + } + } else { + for (u32 channel = 0; channel < NumChannels; channel++) { + auto in_sample{inputs[channel][sample_index] * dry_gain}; + auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) / + 64}; + outputs[channel][sample_index] = (in_sample + out_sample).to_int(); + } + } + } +} + +/** + * Apply a Reverb if enabled, according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeReverbEffect). + * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to performan the reverb on. + * @param outputs - Output mix buffers to receive the reverbed samples. + * @param sample_count - Number of samples to process. + */ +static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state, + const bool enabled, std::vector<std::span<const s32>>& inputs, + std::vector<std::span<s32>>& outputs, const u32 sample_count) { + if (enabled) { + switch (params.channel_count) { + case 0: + return; + case 1: + ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count); + break; + case 2: + ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count); + break; + case 4: + ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count); + break; + case 6: + ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count); + break; + default: + ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + break; + } + } else { + ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + } +} + +void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled, + long_size_pre_delay_supported); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<ReverbInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == ReverbInfo::ParameterState::Updating) { + UpdateReverbEffectParameter(parameter, *state_); + } else if (parameter.state == ReverbInfo::ParameterState::Initialized) { + InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported); + } + } + ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/reverb.h b/src/audio_core/renderer/command/effect/reverb.h new file mode 100644 index 000000000..328756150 --- /dev/null +++ b/src/audio_core/renderer/command/effect/reverb.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/reverb.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives + * the results. + */ +struct ReverbCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + ReverbInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; + /// Is a longer pre-delay time supported? + bool long_size_pre_delay_supported; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/icommand.h b/src/audio_core/renderer/command/icommand.h new file mode 100644 index 000000000..f2dd41254 --- /dev/null +++ b/src/audio_core/renderer/command/icommand.h @@ -0,0 +1,93 @@ +// 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 { +namespace ADSP { +class CommandListProcessor; +} + +enum class CommandId : u8 { + /* 0x00 */ Invalid, + /* 0x01 */ DataSourcePcmInt16Version1, + /* 0x02 */ DataSourcePcmInt16Version2, + /* 0x03 */ DataSourcePcmFloatVersion1, + /* 0x04 */ DataSourcePcmFloatVersion2, + /* 0x05 */ DataSourceAdpcmVersion1, + /* 0x06 */ DataSourceAdpcmVersion2, + /* 0x07 */ Volume, + /* 0x08 */ VolumeRamp, + /* 0x09 */ BiquadFilter, + /* 0x0A */ Mix, + /* 0x0B */ MixRamp, + /* 0x0C */ MixRampGrouped, + /* 0x0D */ DepopPrepare, + /* 0x0E */ DepopForMixBuffers, + /* 0x0F */ Delay, + /* 0x10 */ Upsample, + /* 0x11 */ DownMix6chTo2ch, + /* 0x12 */ Aux, + /* 0x13 */ DeviceSink, + /* 0x14 */ CircularBufferSink, + /* 0x15 */ Reverb, + /* 0x16 */ I3dl2Reverb, + /* 0x17 */ Performance, + /* 0x18 */ ClearMixBuffer, + /* 0x19 */ CopyMixBuffer, + /* 0x1A */ LightLimiterVersion1, + /* 0x1B */ LightLimiterVersion2, + /* 0x1C */ MultiTapBiquadFilter, + /* 0x1D */ Capture, + /* 0x1E */ Compressor, +}; + +constexpr u32 CommandMagic{0xCAFEBABE}; + +/** + * A command, generated by the host, and processed by the ADSP's AudioRenderer. + */ +struct ICommand { + virtual ~ICommand() = default; + + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + virtual void Process(const ADSP::CommandListProcessor& processor) = 0; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0; + + /// Command magic 0xCAFEBABE + u32 magic{}; + /// Command enabled + bool enabled{}; + /// Type of this command (see CommandId) + CommandId type{}; + /// Size of this command + s16 size{}; + /// Estimated processing time for this command + u32 estimated_process_time{}; + /// Node id of the voice or mix this command was generated from + u32 node_id{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/clear_mix.cpp b/src/audio_core/renderer/command/mix/clear_mix.cpp new file mode 100644 index 000000000..4f649d6a8 --- /dev/null +++ b/src/audio_core/renderer/command/mix/clear_mix.cpp @@ -0,0 +1,24 @@ +// 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/mix/clear_mix.h" + +namespace AudioCore::AudioRenderer { + +void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("ClearMixBufferCommand\n"); +} + +void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { + memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes()); +} + +bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/clear_mix.h b/src/audio_core/renderer/command/mix/clear_mix.h new file mode 100644 index 000000000..956ec0b65 --- /dev/null +++ b/src/audio_core/renderer/command/mix/clear_mix.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a clearing the mix buffers. + * Used at the start of each command list. + */ +struct ClearMixBufferCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/copy_mix.cpp b/src/audio_core/renderer/command/mix/copy_mix.cpp new file mode 100644 index 000000000..1d49f1644 --- /dev/null +++ b/src/audio_core/renderer/command/mix/copy_mix.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/copy_mix.h" + +namespace AudioCore::AudioRenderer { + +void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index, + output_index); +} + +void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32)); +} + +bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/copy_mix.h b/src/audio_core/renderer/command/mix/copy_mix.h new file mode 100644 index 000000000..a59007fb6 --- /dev/null +++ b/src/audio_core/renderer/command/mix/copy_mix.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a copying a mix buffer from input to output. + */ +struct CopyMixBufferCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp new file mode 100644 index 000000000..c2bc10061 --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/common/common.h" +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" + +namespace AudioCore::AudioRenderer { +/** + * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time + * according to decay. + * + * @param output - Output buffer to be depopped. + * @param depop_sample - Depopped sample to apply to output samples. + * @param decay_ - Amount to decay the depopped sample for every output sample. + * @param sample_count - Samples to process. + * @return Final decayed depop sample. + */ +static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample, + Common::FixedPoint<49, 15>& decay_, const u32 sample_count) { + auto sample{std::abs(depop_sample)}; + auto decay{decay_.to_raw()}; + + if (depop_sample <= 0) { + for (u32 i = 0; i < sample_count; i++) { + sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15); + output[i] -= sample; + } + return -sample; + } else { + for (u32 i = 0; i < sample_count; i++) { + sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15); + output[i] += sample; + } + return sample; + } +} + +void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input, + count, decay.to_float()); +} + +void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) { + auto end_index{std::min(processor.buffer_count, input + count)}; + std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index}; + + for (u32 index = input; index < end_index; index++) { + const auto depop_sample{depop_buff[index]}; + if (depop_sample != 0) { + auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count, + processor.sample_count)}; + depop_buff[index] = + ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count); + } + } +} + +bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h new file mode 100644 index 000000000..e7268ff27 --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for depopping a mix buffer. + * Adds a cumulation of previous samples to the current mix buffer with a decay. + */ +struct DepopForMixBuffersCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Starting input mix buffer index + u32 input; + /// Number of mix buffers to depop + u32 count; + /// Amount to decay the depop sample for each new sample + Common::FixedPoint<49, 15> decay; + /// Address of the depop buffer, holding the last sample for every mix buffer + CpuAddr depop_buffer; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_prepare.cpp b/src/audio_core/renderer/command/mix/depop_prepare.cpp new file mode 100644 index 000000000..2ee076ef6 --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_prepare.cpp @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/depop_prepare.h" +#include "audio_core/renderer/voice/voice_state.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DepopPrepareCommand\n\tinputs: "); + for (u32 i = 0; i < buffer_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n"; +} + +void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) { + auto samples{reinterpret_cast<s32*>(previous_samples)}; + auto buffer{std::span(reinterpret_cast<s32*>(depop_buffer), buffer_count)}; + + for (u32 i = 0; i < buffer_count; i++) { + if (samples[i]) { + buffer[inputs[i]] += samples[i]; + samples[i] = 0; + } + } +} + +bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_prepare.h b/src/audio_core/renderer/command/mix/depop_prepare.h new file mode 100644 index 000000000..a5465da9a --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_prepare.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for preparing depop. + * Adds the previusly output last samples to the depop buffer. + */ +struct DepopPrepareCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Depop buffer offset for each mix buffer + std::array<s16, MaxMixBuffers> inputs; + /// Pointer to the previous mix buffer samples + CpuAddr previous_samples; + /// Number of mix buffers to use + u32 buffer_count; + /// Pointer to the current depop values + CpuAddr depop_buffer; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix.cpp b/src/audio_core/renderer/command/mix/mix.cpp new file mode 100644 index 000000000..8ecf9b05a --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix.cpp @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <limits> +#include <span> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/mix.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Mix input mix buffer into output mix buffer, with volume applied to the input. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param sample_count - Number of samples to process. + */ +template <size_t Q> +static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f32 volume_, + const u32 sample_count) { + const Common::FixedPoint<64 - Q, Q> volume{volume_}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (output[i] + input[i] * volume).to_int(); + } +} + +void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("MixCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += "\n"; +} + +void MixCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + + // If volume is 0, nothing will be added to the output, so just skip. + if (volume == 0.0f) { + return; + } + + switch (precision) { + case 15: + ApplyMix<15>(output, input, volume, processor.sample_count); + break; + + case 23: + ApplyMix<23>(output, input, volume, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix.h b/src/audio_core/renderer/command/mix/mix.h new file mode 100644 index 000000000..0201cf171 --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume + * applied to the input. + */ +struct MixCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Mix volume applied to the input + f32 volume; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp new file mode 100644 index 000000000..ffdafa1c8 --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/mix_ramp.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" + +namespace AudioCore::AudioRenderer { +/** + * Mix input mix buffer into output mix buffer, with volume applied to the input. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param ramp - Ramp applied to volume every sample. + * @param sample_count - Number of samples to process. + * @return The final gained input sample, used for depopping. + */ +template <size_t Q> +s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, + const f32 ramp_, const u32 sample_count) { + Common::FixedPoint<64 - Q, Q> volume{volume_}; + Common::FixedPoint<64 - Q, Q> sample{0}; + + if (ramp_ == 0.0f) { + for (u32 i = 0; i < sample_count; i++) { + sample = input[i] * volume; + output[i] = (output[i] + sample).to_int(); + } + } else { + Common::FixedPoint<64 - Q, Q> ramp{ramp_}; + for (u32 i = 0; i < sample_count; i++) { + sample = input[i] * volume; + output[i] = (output[i] + sample).to_int(); + volume += ramp; + } + } + return sample.to_int(); +} + +template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, + const u32); +template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32, + const u32); + +void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { + const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; + string += fmt::format("MixRampCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += fmt::format("\n\tprev_volume {:.8f}", prev_volume); + string += fmt::format("\n\tramp {:.8f}", ramp); + string += "\n"; +} + +void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; + auto prev_sample_ptr{reinterpret_cast<s32*>(previous_sample)}; + + // If previous volume and ramp are both 0, nothing will be added to the output, so just skip. + if (prev_volume == 0.0f && ramp == 0.0f) { + *prev_sample_ptr = 0; + return; + } + + switch (precision) { + case 15: + *prev_sample_ptr = + ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count); + break; + + case 23: + *prev_sample_ptr = + ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h new file mode 100644 index 000000000..770f57e80 --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume + * applied to the input, and volume ramping to smooth out the transition. + */ +struct MixRampCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Previous mix volume + f32 prev_volume; + /// Current mix volume + f32 volume; + /// Pointer to the previous sample buffer, used for depopping + CpuAddr previous_sample; +}; + +/** + * Mix input mix buffer into output mix buffer, with volume applied to the input. + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param ramp - Ramp applied to volume every sample. + * @param sample_count - Number of samples to process. + * @return The final gained input sample, used for depopping. + */ +template <size_t Q> +s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, + const f32 ramp_, const u32 sample_count); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp new file mode 100644 index 000000000..43dbef9fc --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/mix_ramp.h" +#include "audio_core/renderer/command/mix/mix_ramp_grouped.h" + +namespace AudioCore::AudioRenderer { + +void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { + string += "MixRampGroupedCommand"; + for (u32 i = 0; i < buffer_count; i++) { + string += fmt::format("\n\t{}", i); + const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast<f32>(processor.sample_count)}; + string += fmt::format("\n\t\tinput {:02X}", inputs[i]); + string += fmt::format("\n\t\toutput {:02X}", outputs[i]); + string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]); + string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]); + string += fmt::format("\n\t\tramp {:.8f}", ramp); + string += "\n"; + } +} + +void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) { + std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers}; + + for (u32 i = 0; i < buffer_count; i++) { + auto last_sample{0}; + if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) { + const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count)}; + const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count)}; + const auto ramp{(volumes[i] - prev_volumes[i]) / + static_cast<f32>(processor.sample_count)}; + + if (prev_volumes[i] == 0.0f && ramp == 0.0f) { + prev_samples[i] = 0; + continue; + } + + switch (precision) { + case 15: + last_sample = + ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count); + break; + case 23: + last_sample = + ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count); + break; + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } + } + + prev_samples[i] = last_sample; + } +} + +bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h new file mode 100644 index 000000000..027276e5a --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with + * a volume applied to the input, and volume ramping to smooth out the transition. + */ +struct MixRampGroupedCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Number of mix buffers to mix + u32 buffer_count; + /// Input mix buffer indexes for each mix buffer + std::array<s16, MaxMixBuffers> inputs; + /// Output mix buffer indexes for each mix buffer + std::array<s16, MaxMixBuffers> outputs; + /// Previous mix vloumes for each mix buffer + std::array<f32, MaxMixBuffers> prev_volumes; + /// Current mix vloumes for each mix buffer + std::array<f32, MaxMixBuffers> volumes; + /// Pointer to the previous sample buffer, used for depop + CpuAddr previous_samples; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume.cpp b/src/audio_core/renderer/command/mix/volume.cpp new file mode 100644 index 000000000..b045fb062 --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume.cpp @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/volume.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" + +namespace AudioCore::AudioRenderer { +/** + * Apply volume to the input mix buffer, saving to the output buffer. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param sample_count - Number of samples to process. + */ +template <size_t Q> +static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, const f32 volume, + const u32 sample_count) { + if (volume == 1.0f) { + std::memcpy(output.data(), input.data(), input.size_bytes()); + } else { + const Common::FixedPoint<64 - Q, Q> gain{volume}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (input[i] * gain).to_int(); + } + } +} + +void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("VolumeCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += "\n"; +} + +void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) { + // If input and output buffers are the same, and the volume is 1.0f, this won't do + // anything, so just skip. + if (input_index == output_index && volume == 1.0f) { + return; + } + + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + + switch (precision) { + case 15: + ApplyUniformGain<15>(output, input, volume, processor.sample_count); + break; + + case 23: + ApplyUniformGain<23>(output, input, volume, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume.h b/src/audio_core/renderer/command/mix/volume.h new file mode 100644 index 000000000..6ae9fb794 --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying volume to a mix buffer. + */ +struct VolumeCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Mix volume applied to the input + f32 volume; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume_ramp.cpp b/src/audio_core/renderer/command/mix/volume_ramp.cpp new file mode 100644 index 000000000..424307148 --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume_ramp.cpp @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/volume_ramp.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Apply volume with ramping to the input mix buffer, saving to the output buffer. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffers. + * @param input - Input mix buffers. + * @param volume - Volume applied to the input. + * @param ramp - Ramp applied to volume every sample. + * @param sample_count - Number of samples to process. + */ +template <size_t Q> +static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> input, + const f32 volume, const f32 ramp_, const u32 sample_count) { + if (volume == 0.0f && ramp_ == 0.0f) { + std::memset(output.data(), 0, output.size_bytes()); + } else if (volume == 1.0f && ramp_ == 0.0f) { + std::memcpy(output.data(), input.data(), output.size_bytes()); + } else if (ramp_ == 0.0f) { + const Common::FixedPoint<64 - Q, Q> gain{volume}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (input[i] * gain).to_int(); + } + } else { + Common::FixedPoint<64 - Q, Q> gain{volume}; + const Common::FixedPoint<64 - Q, Q> ramp{ramp_}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (input[i] * gain).to_int(); + gain += ramp; + } + } +} + +void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { + const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; + string += fmt::format("VolumeRampCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += fmt::format("\n\tprev_volume {:.8f}", prev_volume); + string += fmt::format("\n\tramp {:.8f}", ramp); + string += "\n"; +} + +void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; + + // If input and output buffers are the same, and the volume is 1.0f, and there's no ramping, + // this won't do anything, so just skip. + if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) { + return; + } + + switch (precision) { + case 15: + ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count); + break; + + case 23: + ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume_ramp.h b/src/audio_core/renderer/command/mix/volume_ramp.h new file mode 100644 index 000000000..77b61547e --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume_ramp.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth + * out the transition. + */ +struct VolumeRampCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Previous mix volume applied to the input + f32 prev_volume; + /// Current mix volume applied to the input + f32 volume; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/performance/performance.cpp b/src/audio_core/renderer/command/performance/performance.cpp new file mode 100644 index 000000000..985958b03 --- /dev/null +++ b/src/audio_core/renderer/command/performance/performance.cpp @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/performance/performance.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" + +namespace AudioCore::AudioRenderer { + +void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state)); +} + +void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) { + auto base{entry_address.translated_address}; + if (state == PerformanceState::Start) { + auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)}; + *start_time_ptr = static_cast<u32>( + Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() - + processor.start_time - processor.current_processing_time) + .count()); + } else if (state == PerformanceState::Stop) { + auto processed_time_ptr{ + reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)}; + auto entry_count_ptr{ + reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)}; + + *processed_time_ptr = static_cast<u32>( + Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() - + processor.start_time - processor.current_processing_time) + .count()); + (*entry_count_ptr)++; + } +} + +bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/performance/performance.h b/src/audio_core/renderer/command/performance/performance.h new file mode 100644 index 000000000..11a7d6c08 --- /dev/null +++ b/src/audio_core/renderer/command/performance/performance.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/performance/performance_entry_addresses.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule. + */ +struct PerformanceCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// State of the performance + PerformanceState state; + /// Pointers to be written + PerformanceEntryAddresses entry_address; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp new file mode 100644 index 000000000..1fd90308a --- /dev/null +++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" + +namespace AudioCore::AudioRenderer { + +void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DownMix6chTo2chCommand\n\tinputs: "); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) { + auto in_front_left{ + processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)}; + auto in_front_right{ + processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)}; + auto in_center{ + processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)}; + auto in_lfe{ + processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)}; + auto in_back_left{ + processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)}; + auto in_back_right{ + processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)}; + + auto out_front_left{ + processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)}; + auto out_front_right{ + processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)}; + auto out_center{ + processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)}; + auto out_lfe{ + processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)}; + auto out_back_left{ + processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)}; + auto out_back_right{ + processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)}; + + for (u32 i = 0; i < processor.sample_count; i++) { + const auto left_sample{(in_front_left[i] * down_mix_coeff[0] + + in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] + + in_back_left[i] * down_mix_coeff[3]) + .to_int()}; + + const auto right_sample{(in_front_right[i] * down_mix_coeff[0] + + in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] + + in_back_right[i] * down_mix_coeff[3]) + .to_int()}; + + out_front_left[i] = left_sample; + out_front_right[i] = right_sample; + } + + std::memset(out_center.data(), 0, out_center.size_bytes()); + std::memset(out_lfe.data(), 0, out_lfe.size_bytes()); + std::memset(out_back_left.data(), 0, out_back_left.size_bytes()); + std::memset(out_back_right.data(), 0, out_back_right.size_bytes()); +} + +bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h new file mode 100644 index 000000000..dc133a73b --- /dev/null +++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for downmixing 6 channels to 2. + * Channel layout (SMPTE): + * 0 - front left + * 1 - front right + * 2 - center + * 3 - lfe + * 4 - back left + * 5 - back right + */ +struct DownMix6chTo2chCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Coefficients used for downmixing + std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/resample.cpp b/src/audio_core/renderer/command/resample/resample.cpp new file mode 100644 index 000000000..070c9d2b8 --- /dev/null +++ b/src/audio_core/renderer/command/resample/resample.cpp @@ -0,0 +1,883 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/command/resample/resample.h" + +namespace AudioCore::AudioRenderer { + +static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) { + if (sample_rate_ratio == 1.0f) { + for (u32 i = 0; i < samples_to_write; i++) { + output[i] = input[i]; + } + } else { + u32 read_index{0}; + for (u32 i = 0; i < samples_to_write; i++) { + output[i] = input[read_index + (fraction >= 0.5f)]; + fraction += sample_rate_ratio; + read_index += static_cast<u32>(fraction.to_int_floor()); + fraction.clear_int(); + } + } +} + +static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, + const u32 samples_to_write) { + static constexpr std::array<f32, 512> lut0 = { + 0.20141602f, 0.59283447f, 0.20513916f, 0.00009155f, 0.19772339f, 0.59277344f, 0.20889282f, + 0.00027466f, 0.19406128f, 0.59262085f, 0.21264648f, 0.00045776f, 0.19039917f, 0.59240723f, + 0.21646118f, 0.00067139f, 0.18679810f, 0.59213257f, 0.22030640f, 0.00085449f, 0.18322754f, + 0.59176636f, 0.22415161f, 0.00103760f, 0.17968750f, 0.59133911f, 0.22802734f, 0.00125122f, + 0.17617798f, 0.59085083f, 0.23193359f, 0.00146484f, 0.17269897f, 0.59027100f, 0.23583984f, + 0.00167847f, 0.16925049f, 0.58963013f, 0.23977661f, 0.00189209f, 0.16583252f, 0.58892822f, + 0.24374390f, 0.00210571f, 0.16244507f, 0.58816528f, 0.24774170f, 0.00234985f, 0.15908813f, + 0.58731079f, 0.25173950f, 0.00256348f, 0.15576172f, 0.58639526f, 0.25576782f, 0.00280762f, + 0.15249634f, 0.58541870f, 0.25979614f, 0.00308228f, 0.14923096f, 0.58435059f, 0.26385498f, + 0.00332642f, 0.14602661f, 0.58325195f, 0.26794434f, 0.00360107f, 0.14285278f, 0.58206177f, + 0.27203369f, 0.00387573f, 0.13973999f, 0.58078003f, 0.27612305f, 0.00418091f, 0.13662720f, + 0.57946777f, 0.28024292f, 0.00448608f, 0.13357544f, 0.57806396f, 0.28436279f, 0.00479126f, + 0.13052368f, 0.57662964f, 0.28851318f, 0.00512695f, 0.12753296f, 0.57510376f, 0.29266357f, + 0.00546265f, 0.12460327f, 0.57351685f, 0.29681396f, 0.00579834f, 0.12167358f, 0.57183838f, + 0.30099487f, 0.00616455f, 0.11880493f, 0.57012939f, 0.30517578f, 0.00656128f, 0.11596680f, + 0.56835938f, 0.30935669f, 0.00695801f, 0.11318970f, 0.56649780f, 0.31353760f, 0.00735474f, + 0.11041260f, 0.56457520f, 0.31771851f, 0.00778198f, 0.10769653f, 0.56262207f, 0.32192993f, + 0.00823975f, 0.10501099f, 0.56057739f, 0.32614136f, 0.00869751f, 0.10238647f, 0.55847168f, + 0.33032227f, 0.00915527f, 0.09976196f, 0.55633545f, 0.33453369f, 0.00967407f, 0.09722900f, + 0.55410767f, 0.33874512f, 0.01019287f, 0.09469604f, 0.55181885f, 0.34295654f, 0.01071167f, + 0.09222412f, 0.54949951f, 0.34713745f, 0.01126099f, 0.08978271f, 0.54708862f, 0.35134888f, + 0.01184082f, 0.08737183f, 0.54464722f, 0.35552979f, 0.01245117f, 0.08499146f, 0.54214478f, + 0.35974121f, 0.01306152f, 0.08267212f, 0.53958130f, 0.36392212f, 0.01370239f, 0.08041382f, + 0.53695679f, 0.36810303f, 0.01437378f, 0.07815552f, 0.53427124f, 0.37225342f, 0.01507568f, + 0.07595825f, 0.53155518f, 0.37640381f, 0.01577759f, 0.07379150f, 0.52877808f, 0.38055420f, + 0.01651001f, 0.07165527f, 0.52593994f, 0.38470459f, 0.01727295f, 0.06958008f, 0.52307129f, + 0.38882446f, 0.01806641f, 0.06753540f, 0.52014160f, 0.39294434f, 0.01889038f, 0.06552124f, + 0.51715088f, 0.39703369f, 0.01974487f, 0.06356812f, 0.51409912f, 0.40112305f, 0.02059937f, + 0.06164551f, 0.51101685f, 0.40518188f, 0.02148438f, 0.05975342f, 0.50790405f, 0.40921021f, + 0.02243042f, 0.05789185f, 0.50473022f, 0.41323853f, 0.02337646f, 0.05609131f, 0.50152588f, + 0.41726685f, 0.02435303f, 0.05432129f, 0.49826050f, 0.42123413f, 0.02539062f, 0.05258179f, + 0.49493408f, 0.42520142f, 0.02642822f, 0.05087280f, 0.49160767f, 0.42913818f, 0.02749634f, + 0.04922485f, 0.48822021f, 0.43307495f, 0.02859497f, 0.04760742f, 0.48477173f, 0.43695068f, + 0.02975464f, 0.04602051f, 0.48132324f, 0.44082642f, 0.03091431f, 0.04446411f, 0.47781372f, + 0.44467163f, 0.03210449f, 0.04293823f, 0.47424316f, 0.44845581f, 0.03335571f, 0.04147339f, + 0.47067261f, 0.45223999f, 0.03460693f, 0.04003906f, 0.46704102f, 0.45599365f, 0.03591919f, + 0.03863525f, 0.46340942f, 0.45971680f, 0.03726196f, 0.03726196f, 0.45971680f, 0.46340942f, + 0.03863525f, 0.03591919f, 0.45599365f, 0.46704102f, 0.04003906f, 0.03460693f, 0.45223999f, + 0.47067261f, 0.04147339f, 0.03335571f, 0.44845581f, 0.47424316f, 0.04293823f, 0.03210449f, + 0.44467163f, 0.47781372f, 0.04446411f, 0.03091431f, 0.44082642f, 0.48132324f, 0.04602051f, + 0.02975464f, 0.43695068f, 0.48477173f, 0.04760742f, 0.02859497f, 0.43307495f, 0.48822021f, + 0.04922485f, 0.02749634f, 0.42913818f, 0.49160767f, 0.05087280f, 0.02642822f, 0.42520142f, + 0.49493408f, 0.05258179f, 0.02539062f, 0.42123413f, 0.49826050f, 0.05432129f, 0.02435303f, + 0.41726685f, 0.50152588f, 0.05609131f, 0.02337646f, 0.41323853f, 0.50473022f, 0.05789185f, + 0.02243042f, 0.40921021f, 0.50790405f, 0.05975342f, 0.02148438f, 0.40518188f, 0.51101685f, + 0.06164551f, 0.02059937f, 0.40112305f, 0.51409912f, 0.06356812f, 0.01974487f, 0.39703369f, + 0.51715088f, 0.06552124f, 0.01889038f, 0.39294434f, 0.52014160f, 0.06753540f, 0.01806641f, + 0.38882446f, 0.52307129f, 0.06958008f, 0.01727295f, 0.38470459f, 0.52593994f, 0.07165527f, + 0.01651001f, 0.38055420f, 0.52877808f, 0.07379150f, 0.01577759f, 0.37640381f, 0.53155518f, + 0.07595825f, 0.01507568f, 0.37225342f, 0.53427124f, 0.07815552f, 0.01437378f, 0.36810303f, + 0.53695679f, 0.08041382f, 0.01370239f, 0.36392212f, 0.53958130f, 0.08267212f, 0.01306152f, + 0.35974121f, 0.54214478f, 0.08499146f, 0.01245117f, 0.35552979f, 0.54464722f, 0.08737183f, + 0.01184082f, 0.35134888f, 0.54708862f, 0.08978271f, 0.01126099f, 0.34713745f, 0.54949951f, + 0.09222412f, 0.01071167f, 0.34295654f, 0.55181885f, 0.09469604f, 0.01019287f, 0.33874512f, + 0.55410767f, 0.09722900f, 0.00967407f, 0.33453369f, 0.55633545f, 0.09976196f, 0.00915527f, + 0.33032227f, 0.55847168f, 0.10238647f, 0.00869751f, 0.32614136f, 0.56057739f, 0.10501099f, + 0.00823975f, 0.32192993f, 0.56262207f, 0.10769653f, 0.00778198f, 0.31771851f, 0.56457520f, + 0.11041260f, 0.00735474f, 0.31353760f, 0.56649780f, 0.11318970f, 0.00695801f, 0.30935669f, + 0.56835938f, 0.11596680f, 0.00656128f, 0.30517578f, 0.57012939f, 0.11880493f, 0.00616455f, + 0.30099487f, 0.57183838f, 0.12167358f, 0.00579834f, 0.29681396f, 0.57351685f, 0.12460327f, + 0.00546265f, 0.29266357f, 0.57510376f, 0.12753296f, 0.00512695f, 0.28851318f, 0.57662964f, + 0.13052368f, 0.00479126f, 0.28436279f, 0.57806396f, 0.13357544f, 0.00448608f, 0.28024292f, + 0.57946777f, 0.13662720f, 0.00418091f, 0.27612305f, 0.58078003f, 0.13973999f, 0.00387573f, + 0.27203369f, 0.58206177f, 0.14285278f, 0.00360107f, 0.26794434f, 0.58325195f, 0.14602661f, + 0.00332642f, 0.26385498f, 0.58435059f, 0.14923096f, 0.00308228f, 0.25979614f, 0.58541870f, + 0.15249634f, 0.00280762f, 0.25576782f, 0.58639526f, 0.15576172f, 0.00256348f, 0.25173950f, + 0.58731079f, 0.15908813f, 0.00234985f, 0.24774170f, 0.58816528f, 0.16244507f, 0.00210571f, + 0.24374390f, 0.58892822f, 0.16583252f, 0.00189209f, 0.23977661f, 0.58963013f, 0.16925049f, + 0.00167847f, 0.23583984f, 0.59027100f, 0.17269897f, 0.00146484f, 0.23193359f, 0.59085083f, + 0.17617798f, 0.00125122f, 0.22802734f, 0.59133911f, 0.17968750f, 0.00103760f, 0.22415161f, + 0.59176636f, 0.18322754f, 0.00085449f, 0.22030640f, 0.59213257f, 0.18679810f, 0.00067139f, + 0.21646118f, 0.59240723f, 0.19039917f, 0.00045776f, 0.21264648f, 0.59262085f, 0.19406128f, + 0.00027466f, 0.20889282f, 0.59277344f, 0.19772339f, 0.00009155f, 0.20513916f, 0.59283447f, + 0.20141602f, + }; + + static constexpr std::array<f32, 512> lut1 = { + 0.00207520f, 0.99606323f, 0.00210571f, -0.00015259f, -0.00610352f, 0.99578857f, + 0.00646973f, -0.00045776f, -0.01000977f, 0.99526978f, 0.01095581f, -0.00079346f, + -0.01373291f, 0.99444580f, 0.01562500f, -0.00109863f, -0.01733398f, 0.99337769f, + 0.02041626f, -0.00143433f, -0.02075195f, 0.99203491f, 0.02539062f, -0.00177002f, + -0.02404785f, 0.99041748f, 0.03051758f, -0.00210571f, -0.02719116f, 0.98855591f, + 0.03582764f, -0.00244141f, -0.03021240f, 0.98641968f, 0.04125977f, -0.00280762f, + -0.03308105f, 0.98400879f, 0.04687500f, -0.00314331f, -0.03579712f, 0.98135376f, + 0.05261230f, -0.00350952f, -0.03839111f, 0.97842407f, 0.05856323f, -0.00390625f, + -0.04083252f, 0.97521973f, 0.06463623f, -0.00427246f, -0.04315186f, 0.97180176f, + 0.07086182f, -0.00466919f, -0.04534912f, 0.96810913f, 0.07727051f, -0.00509644f, + -0.04742432f, 0.96414185f, 0.08383179f, -0.00549316f, -0.04934692f, 0.95996094f, + 0.09054565f, -0.00592041f, -0.05114746f, 0.95550537f, 0.09741211f, -0.00637817f, + -0.05285645f, 0.95083618f, 0.10443115f, -0.00683594f, -0.05441284f, 0.94589233f, + 0.11160278f, -0.00732422f, -0.05584717f, 0.94073486f, 0.11892700f, -0.00781250f, + -0.05718994f, 0.93533325f, 0.12643433f, -0.00830078f, -0.05841064f, 0.92968750f, + 0.13406372f, -0.00881958f, -0.05953979f, 0.92382812f, 0.14184570f, -0.00936890f, + -0.06054688f, 0.91772461f, 0.14978027f, -0.00991821f, -0.06146240f, 0.91143799f, + 0.15783691f, -0.01046753f, -0.06225586f, 0.90490723f, 0.16607666f, -0.01104736f, + -0.06295776f, 0.89816284f, 0.17443848f, -0.01165771f, -0.06356812f, 0.89120483f, + 0.18292236f, -0.01229858f, -0.06408691f, 0.88403320f, 0.19155884f, -0.01293945f, + -0.06451416f, 0.87667847f, 0.20034790f, -0.01358032f, -0.06484985f, 0.86914062f, + 0.20925903f, -0.01428223f, -0.06509399f, 0.86138916f, 0.21829224f, -0.01495361f, + -0.06527710f, 0.85345459f, 0.22744751f, -0.01568604f, -0.06536865f, 0.84533691f, + 0.23675537f, -0.01641846f, -0.06536865f, 0.83703613f, 0.24615479f, -0.01718140f, + -0.06533813f, 0.82858276f, 0.25567627f, -0.01794434f, -0.06518555f, 0.81991577f, + 0.26531982f, -0.01873779f, -0.06500244f, 0.81112671f, 0.27505493f, -0.01956177f, + -0.06472778f, 0.80215454f, 0.28491211f, -0.02038574f, -0.06442261f, 0.79306030f, + 0.29489136f, -0.02124023f, -0.06402588f, 0.78378296f, 0.30496216f, -0.02209473f, + -0.06359863f, 0.77438354f, 0.31512451f, -0.02297974f, -0.06307983f, 0.76486206f, + 0.32537842f, -0.02389526f, -0.06253052f, 0.75518799f, 0.33569336f, -0.02481079f, + -0.06195068f, 0.74539185f, 0.34613037f, -0.02575684f, -0.06130981f, 0.73547363f, + 0.35662842f, -0.02670288f, -0.06060791f, 0.72543335f, 0.36721802f, -0.02767944f, + -0.05987549f, 0.71527100f, 0.37786865f, -0.02865601f, -0.05911255f, 0.70504761f, + 0.38858032f, -0.02966309f, -0.05831909f, 0.69470215f, 0.39935303f, -0.03067017f, + -0.05746460f, 0.68426514f, 0.41018677f, -0.03170776f, -0.05661011f, 0.67373657f, + 0.42108154f, -0.03271484f, -0.05569458f, 0.66311646f, 0.43200684f, -0.03378296f, + -0.05477905f, 0.65246582f, 0.44299316f, -0.03482056f, -0.05383301f, 0.64169312f, + 0.45401001f, -0.03588867f, -0.05285645f, 0.63088989f, 0.46505737f, -0.03695679f, + -0.05187988f, 0.62002563f, 0.47613525f, -0.03802490f, -0.05087280f, 0.60910034f, + 0.48721313f, -0.03912354f, -0.04983521f, 0.59814453f, 0.49832153f, -0.04019165f, + -0.04879761f, 0.58712769f, 0.50946045f, -0.04129028f, -0.04772949f, 0.57611084f, + 0.52056885f, -0.04235840f, -0.04669189f, 0.56503296f, 0.53170776f, -0.04345703f, + -0.04562378f, 0.55392456f, 0.54281616f, -0.04452515f, -0.04452515f, 0.54281616f, + 0.55392456f, -0.04562378f, -0.04345703f, 0.53170776f, 0.56503296f, -0.04669189f, + -0.04235840f, 0.52056885f, 0.57611084f, -0.04772949f, -0.04129028f, 0.50946045f, + 0.58712769f, -0.04879761f, -0.04019165f, 0.49832153f, 0.59814453f, -0.04983521f, + -0.03912354f, 0.48721313f, 0.60910034f, -0.05087280f, -0.03802490f, 0.47613525f, + 0.62002563f, -0.05187988f, -0.03695679f, 0.46505737f, 0.63088989f, -0.05285645f, + -0.03588867f, 0.45401001f, 0.64169312f, -0.05383301f, -0.03482056f, 0.44299316f, + 0.65246582f, -0.05477905f, -0.03378296f, 0.43200684f, 0.66311646f, -0.05569458f, + -0.03271484f, 0.42108154f, 0.67373657f, -0.05661011f, -0.03170776f, 0.41018677f, + 0.68426514f, -0.05746460f, -0.03067017f, 0.39935303f, 0.69470215f, -0.05831909f, + -0.02966309f, 0.38858032f, 0.70504761f, -0.05911255f, -0.02865601f, 0.37786865f, + 0.71527100f, -0.05987549f, -0.02767944f, 0.36721802f, 0.72543335f, -0.06060791f, + -0.02670288f, 0.35662842f, 0.73547363f, -0.06130981f, -0.02575684f, 0.34613037f, + 0.74539185f, -0.06195068f, -0.02481079f, 0.33569336f, 0.75518799f, -0.06253052f, + -0.02389526f, 0.32537842f, 0.76486206f, -0.06307983f, -0.02297974f, 0.31512451f, + 0.77438354f, -0.06359863f, -0.02209473f, 0.30496216f, 0.78378296f, -0.06402588f, + -0.02124023f, 0.29489136f, 0.79306030f, -0.06442261f, -0.02038574f, 0.28491211f, + 0.80215454f, -0.06472778f, -0.01956177f, 0.27505493f, 0.81112671f, -0.06500244f, + -0.01873779f, 0.26531982f, 0.81991577f, -0.06518555f, -0.01794434f, 0.25567627f, + 0.82858276f, -0.06533813f, -0.01718140f, 0.24615479f, 0.83703613f, -0.06536865f, + -0.01641846f, 0.23675537f, 0.84533691f, -0.06536865f, -0.01568604f, 0.22744751f, + 0.85345459f, -0.06527710f, -0.01495361f, 0.21829224f, 0.86138916f, -0.06509399f, + -0.01428223f, 0.20925903f, 0.86914062f, -0.06484985f, -0.01358032f, 0.20034790f, + 0.87667847f, -0.06451416f, -0.01293945f, 0.19155884f, 0.88403320f, -0.06408691f, + -0.01229858f, 0.18292236f, 0.89120483f, -0.06356812f, -0.01165771f, 0.17443848f, + 0.89816284f, -0.06295776f, -0.01104736f, 0.16607666f, 0.90490723f, -0.06225586f, + -0.01046753f, 0.15783691f, 0.91143799f, -0.06146240f, -0.00991821f, 0.14978027f, + 0.91772461f, -0.06054688f, -0.00936890f, 0.14184570f, 0.92382812f, -0.05953979f, + -0.00881958f, 0.13406372f, 0.92968750f, -0.05841064f, -0.00830078f, 0.12643433f, + 0.93533325f, -0.05718994f, -0.00781250f, 0.11892700f, 0.94073486f, -0.05584717f, + -0.00732422f, 0.11160278f, 0.94589233f, -0.05441284f, -0.00683594f, 0.10443115f, + 0.95083618f, -0.05285645f, -0.00637817f, 0.09741211f, 0.95550537f, -0.05114746f, + -0.00592041f, 0.09054565f, 0.95996094f, -0.04934692f, -0.00549316f, 0.08383179f, + 0.96414185f, -0.04742432f, -0.00509644f, 0.07727051f, 0.96810913f, -0.04534912f, + -0.00466919f, 0.07086182f, 0.97180176f, -0.04315186f, -0.00427246f, 0.06463623f, + 0.97521973f, -0.04083252f, -0.00390625f, 0.05856323f, 0.97842407f, -0.03839111f, + -0.00350952f, 0.05261230f, 0.98135376f, -0.03579712f, -0.00314331f, 0.04687500f, + 0.98400879f, -0.03308105f, -0.00280762f, 0.04125977f, 0.98641968f, -0.03021240f, + -0.00244141f, 0.03582764f, 0.98855591f, -0.02719116f, -0.00210571f, 0.03051758f, + 0.99041748f, -0.02404785f, -0.00177002f, 0.02539062f, 0.99203491f, -0.02075195f, + -0.00143433f, 0.02041626f, 0.99337769f, -0.01733398f, -0.00109863f, 0.01562500f, + 0.99444580f, -0.01373291f, -0.00079346f, 0.01095581f, 0.99526978f, -0.01000977f, + -0.00045776f, 0.00646973f, 0.99578857f, -0.00610352f, -0.00015259f, 0.00210571f, + 0.99606323f, -0.00207520f, + }; + + static constexpr std::array<f32, 512> lut2 = { + 0.09750366f, 0.80221558f, 0.10159302f, -0.00097656f, 0.09350586f, 0.80203247f, + 0.10580444f, -0.00103760f, 0.08959961f, 0.80169678f, 0.11010742f, -0.00115967f, + 0.08578491f, 0.80117798f, 0.11447144f, -0.00128174f, 0.08203125f, 0.80047607f, + 0.11892700f, -0.00140381f, 0.07836914f, 0.79962158f, 0.12347412f, -0.00152588f, + 0.07479858f, 0.79861450f, 0.12814331f, -0.00164795f, 0.07135010f, 0.79742432f, + 0.13287354f, -0.00177002f, 0.06796265f, 0.79605103f, 0.13769531f, -0.00192261f, + 0.06469727f, 0.79452515f, 0.14260864f, -0.00204468f, 0.06149292f, 0.79284668f, + 0.14761353f, -0.00219727f, 0.05834961f, 0.79098511f, 0.15270996f, -0.00231934f, + 0.05532837f, 0.78894043f, 0.15789795f, -0.00247192f, 0.05236816f, 0.78674316f, + 0.16317749f, -0.00265503f, 0.04949951f, 0.78442383f, 0.16851807f, -0.00280762f, + 0.04672241f, 0.78189087f, 0.17398071f, -0.00299072f, 0.04400635f, 0.77920532f, + 0.17950439f, -0.00314331f, 0.04141235f, 0.77636719f, 0.18511963f, -0.00332642f, + 0.03887939f, 0.77337646f, 0.19082642f, -0.00350952f, 0.03640747f, 0.77023315f, + 0.19659424f, -0.00369263f, 0.03402710f, 0.76693726f, 0.20248413f, -0.00387573f, + 0.03173828f, 0.76348877f, 0.20843506f, -0.00405884f, 0.02951050f, 0.75985718f, + 0.21444702f, -0.00427246f, 0.02737427f, 0.75610352f, 0.22055054f, -0.00445557f, + 0.02529907f, 0.75219727f, 0.22674561f, -0.00466919f, 0.02331543f, 0.74816895f, + 0.23300171f, -0.00485229f, 0.02139282f, 0.74398804f, 0.23931885f, -0.00506592f, + 0.01956177f, 0.73965454f, 0.24572754f, -0.00531006f, 0.01779175f, 0.73519897f, + 0.25219727f, -0.00552368f, 0.01605225f, 0.73059082f, 0.25872803f, -0.00570679f, + 0.01440430f, 0.72586060f, 0.26535034f, -0.00592041f, 0.01281738f, 0.72100830f, + 0.27203369f, -0.00616455f, 0.01132202f, 0.71600342f, 0.27877808f, -0.00637817f, + 0.00988770f, 0.71090698f, 0.28558350f, -0.00656128f, 0.00851440f, 0.70565796f, + 0.29244995f, -0.00677490f, 0.00720215f, 0.70031738f, 0.29934692f, -0.00701904f, + 0.00592041f, 0.69485474f, 0.30633545f, -0.00723267f, 0.00469971f, 0.68927002f, + 0.31338501f, -0.00741577f, 0.00357056f, 0.68356323f, 0.32046509f, -0.00762939f, + 0.00247192f, 0.67773438f, 0.32760620f, -0.00787354f, 0.00143433f, 0.67184448f, + 0.33477783f, -0.00808716f, 0.00045776f, 0.66583252f, 0.34197998f, -0.00827026f, + -0.00048828f, 0.65972900f, 0.34924316f, -0.00845337f, -0.00134277f, 0.65353394f, + 0.35656738f, -0.00863647f, -0.00216675f, 0.64721680f, 0.36389160f, -0.00885010f, + -0.00296021f, 0.64083862f, 0.37127686f, -0.00903320f, -0.00369263f, 0.63433838f, + 0.37869263f, -0.00921631f, -0.00436401f, 0.62777710f, 0.38613892f, -0.00933838f, + -0.00497437f, 0.62115479f, 0.39361572f, -0.00949097f, -0.00558472f, 0.61444092f, + 0.40109253f, -0.00964355f, -0.00613403f, 0.60763550f, 0.40859985f, -0.00979614f, + -0.00665283f, 0.60076904f, 0.41610718f, -0.00991821f, -0.00714111f, 0.59384155f, + 0.42364502f, -0.01000977f, -0.00756836f, 0.58685303f, 0.43121338f, -0.01013184f, + -0.00796509f, 0.57977295f, 0.43875122f, -0.01022339f, -0.00833130f, 0.57266235f, + 0.44631958f, -0.01028442f, -0.00866699f, 0.56552124f, 0.45388794f, -0.01034546f, + -0.00897217f, 0.55831909f, 0.46145630f, -0.01040649f, -0.00921631f, 0.55105591f, + 0.46902466f, -0.01040649f, -0.00946045f, 0.54373169f, 0.47659302f, -0.01040649f, + -0.00967407f, 0.53640747f, 0.48413086f, -0.01037598f, -0.00985718f, 0.52902222f, + 0.49166870f, -0.01037598f, -0.01000977f, 0.52160645f, 0.49917603f, -0.01031494f, + -0.01013184f, 0.51416016f, 0.50668335f, -0.01025391f, -0.01025391f, 0.50668335f, + 0.51416016f, -0.01013184f, -0.01031494f, 0.49917603f, 0.52160645f, -0.01000977f, + -0.01037598f, 0.49166870f, 0.52902222f, -0.00985718f, -0.01037598f, 0.48413086f, + 0.53640747f, -0.00967407f, -0.01040649f, 0.47659302f, 0.54373169f, -0.00946045f, + -0.01040649f, 0.46902466f, 0.55105591f, -0.00921631f, -0.01040649f, 0.46145630f, + 0.55831909f, -0.00897217f, -0.01034546f, 0.45388794f, 0.56552124f, -0.00866699f, + -0.01028442f, 0.44631958f, 0.57266235f, -0.00833130f, -0.01022339f, 0.43875122f, + 0.57977295f, -0.00796509f, -0.01013184f, 0.43121338f, 0.58685303f, -0.00756836f, + -0.01000977f, 0.42364502f, 0.59384155f, -0.00714111f, -0.00991821f, 0.41610718f, + 0.60076904f, -0.00665283f, -0.00979614f, 0.40859985f, 0.60763550f, -0.00613403f, + -0.00964355f, 0.40109253f, 0.61444092f, -0.00558472f, -0.00949097f, 0.39361572f, + 0.62115479f, -0.00497437f, -0.00933838f, 0.38613892f, 0.62777710f, -0.00436401f, + -0.00921631f, 0.37869263f, 0.63433838f, -0.00369263f, -0.00903320f, 0.37127686f, + 0.64083862f, -0.00296021f, -0.00885010f, 0.36389160f, 0.64721680f, -0.00216675f, + -0.00863647f, 0.35656738f, 0.65353394f, -0.00134277f, -0.00845337f, 0.34924316f, + 0.65972900f, -0.00048828f, -0.00827026f, 0.34197998f, 0.66583252f, 0.00045776f, + -0.00808716f, 0.33477783f, 0.67184448f, 0.00143433f, -0.00787354f, 0.32760620f, + 0.67773438f, 0.00247192f, -0.00762939f, 0.32046509f, 0.68356323f, 0.00357056f, + -0.00741577f, 0.31338501f, 0.68927002f, 0.00469971f, -0.00723267f, 0.30633545f, + 0.69485474f, 0.00592041f, -0.00701904f, 0.29934692f, 0.70031738f, 0.00720215f, + -0.00677490f, 0.29244995f, 0.70565796f, 0.00851440f, -0.00656128f, 0.28558350f, + 0.71090698f, 0.00988770f, -0.00637817f, 0.27877808f, 0.71600342f, 0.01132202f, + -0.00616455f, 0.27203369f, 0.72100830f, 0.01281738f, -0.00592041f, 0.26535034f, + 0.72586060f, 0.01440430f, -0.00570679f, 0.25872803f, 0.73059082f, 0.01605225f, + -0.00552368f, 0.25219727f, 0.73519897f, 0.01779175f, -0.00531006f, 0.24572754f, + 0.73965454f, 0.01956177f, -0.00506592f, 0.23931885f, 0.74398804f, 0.02139282f, + -0.00485229f, 0.23300171f, 0.74816895f, 0.02331543f, -0.00466919f, 0.22674561f, + 0.75219727f, 0.02529907f, -0.00445557f, 0.22055054f, 0.75610352f, 0.02737427f, + -0.00427246f, 0.21444702f, 0.75985718f, 0.02951050f, -0.00405884f, 0.20843506f, + 0.76348877f, 0.03173828f, -0.00387573f, 0.20248413f, 0.76693726f, 0.03402710f, + -0.00369263f, 0.19659424f, 0.77023315f, 0.03640747f, -0.00350952f, 0.19082642f, + 0.77337646f, 0.03887939f, -0.00332642f, 0.18511963f, 0.77636719f, 0.04141235f, + -0.00314331f, 0.17950439f, 0.77920532f, 0.04400635f, -0.00299072f, 0.17398071f, + 0.78189087f, 0.04672241f, -0.00280762f, 0.16851807f, 0.78442383f, 0.04949951f, + -0.00265503f, 0.16317749f, 0.78674316f, 0.05236816f, -0.00247192f, 0.15789795f, + 0.78894043f, 0.05532837f, -0.00231934f, 0.15270996f, 0.79098511f, 0.05834961f, + -0.00219727f, 0.14761353f, 0.79284668f, 0.06149292f, -0.00204468f, 0.14260864f, + 0.79452515f, 0.06469727f, -0.00192261f, 0.13769531f, 0.79605103f, 0.06796265f, + -0.00177002f, 0.13287354f, 0.79742432f, 0.07135010f, -0.00164795f, 0.12814331f, + 0.79861450f, 0.07479858f, -0.00152588f, 0.12347412f, 0.79962158f, 0.07836914f, + -0.00140381f, 0.11892700f, 0.80047607f, 0.08203125f, -0.00128174f, 0.11447144f, + 0.80117798f, 0.08578491f, -0.00115967f, 0.11010742f, 0.80169678f, 0.08959961f, + -0.00103760f, 0.10580444f, 0.80203247f, 0.09350586f, -0.00097656f, 0.10159302f, + 0.80221558f, 0.09750366f, + }; + + const auto get_lut = [&]() -> std::span<const f32> { + if (sample_rate_ratio <= 1.0f) { + return std::span<const f32>(lut2.data(), lut2.size()); + } else if (sample_rate_ratio < 1.3f) { + return std::span<const f32>(lut1.data(), lut1.size()); + } else { + return std::span<const f32>(lut0.data(), lut0.size()); + } + }; + + auto lut{get_lut()}; + u32 read_index{0}; + for (u32 i = 0; i < samples_to_write; i++) { + const auto lut_index{(fraction.get_frac() >> 8) * 4}; + const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]}; + const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]}; + const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]}; + const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]}; + output[i] = (sample0 + sample1 + sample2 + sample3).to_int_floor(); + fraction += sample_rate_ratio; + read_index += static_cast<u32>(fraction.to_int_floor()); + fraction.clear_int(); + } +} + +static void ResampleHighQuality(std::span<s32> output, std::span<const s16> input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) { + static constexpr std::array<f32, 1024> lut0 = { + -0.01776123f, -0.00070190f, 0.26672363f, 0.50006104f, 0.26956177f, 0.00024414f, + -0.01800537f, 0.00000000f, -0.01748657f, -0.00164795f, 0.26388550f, 0.50003052f, + 0.27236938f, 0.00122070f, -0.01824951f, -0.00003052f, -0.01724243f, -0.00256348f, + 0.26107788f, 0.49996948f, 0.27520752f, 0.00219727f, -0.01849365f, -0.00003052f, + -0.01699829f, -0.00344849f, 0.25823975f, 0.49984741f, 0.27801514f, 0.00320435f, + -0.01873779f, -0.00006104f, -0.01675415f, -0.00433350f, 0.25543213f, 0.49972534f, + 0.28085327f, 0.00424194f, -0.01898193f, -0.00006104f, -0.01651001f, -0.00518799f, + 0.25259399f, 0.49954224f, 0.28366089f, 0.00527954f, -0.01922607f, -0.00009155f, + -0.01626587f, -0.00604248f, 0.24978638f, 0.49932861f, 0.28646851f, 0.00634766f, + -0.01947021f, -0.00012207f, -0.01602173f, -0.00686646f, 0.24697876f, 0.49908447f, + 0.28930664f, 0.00744629f, -0.01971436f, -0.00015259f, -0.01574707f, -0.00765991f, + 0.24414062f, 0.49877930f, 0.29211426f, 0.00854492f, -0.01995850f, -0.00015259f, + -0.01550293f, -0.00845337f, 0.24133301f, 0.49847412f, 0.29492188f, 0.00967407f, + -0.02020264f, -0.00018311f, -0.01525879f, -0.00921631f, 0.23852539f, 0.49810791f, + 0.29772949f, 0.01083374f, -0.02044678f, -0.00021362f, -0.01501465f, -0.00997925f, + 0.23571777f, 0.49774170f, 0.30050659f, 0.01199341f, -0.02069092f, -0.00024414f, + -0.01477051f, -0.01071167f, 0.23291016f, 0.49731445f, 0.30331421f, 0.01318359f, + -0.02093506f, -0.00027466f, -0.01452637f, -0.01141357f, 0.23010254f, 0.49685669f, + 0.30609131f, 0.01437378f, -0.02117920f, -0.00030518f, -0.01428223f, -0.01211548f, + 0.22732544f, 0.49636841f, 0.30886841f, 0.01559448f, -0.02142334f, -0.00033569f, + -0.01403809f, -0.01278687f, 0.22451782f, 0.49581909f, 0.31164551f, 0.01684570f, + -0.02163696f, -0.00039673f, -0.01379395f, -0.01345825f, 0.22174072f, 0.49526978f, + 0.31442261f, 0.01809692f, -0.02188110f, -0.00042725f, -0.01358032f, -0.01409912f, + 0.21896362f, 0.49465942f, 0.31719971f, 0.01937866f, -0.02209473f, -0.00045776f, + -0.01333618f, -0.01473999f, 0.21618652f, 0.49404907f, 0.31994629f, 0.02069092f, + -0.02233887f, -0.00048828f, -0.01309204f, -0.01535034f, 0.21343994f, 0.49337769f, + 0.32269287f, 0.02203369f, -0.02255249f, -0.00054932f, -0.01284790f, -0.01596069f, + 0.21066284f, 0.49267578f, 0.32543945f, 0.02337646f, -0.02279663f, -0.00057983f, + -0.01263428f, -0.01654053f, 0.20791626f, 0.49194336f, 0.32818604f, 0.02471924f, + -0.02301025f, -0.00064087f, -0.01239014f, -0.01708984f, 0.20516968f, 0.49118042f, + 0.33090210f, 0.02612305f, -0.02322388f, -0.00067139f, -0.01214600f, -0.01763916f, + 0.20242310f, 0.49035645f, 0.33361816f, 0.02752686f, -0.02343750f, -0.00073242f, + -0.01193237f, -0.01818848f, 0.19970703f, 0.48953247f, 0.33633423f, 0.02896118f, + -0.02365112f, -0.00079346f, -0.01168823f, -0.01867676f, 0.19696045f, 0.48864746f, + 0.33901978f, 0.03039551f, -0.02386475f, -0.00082397f, -0.01147461f, -0.01919556f, + 0.19427490f, 0.48776245f, 0.34170532f, 0.03186035f, -0.02407837f, -0.00088501f, + -0.01123047f, -0.01968384f, 0.19155884f, 0.48681641f, 0.34439087f, 0.03335571f, + -0.02429199f, -0.00094604f, -0.01101685f, -0.02014160f, 0.18887329f, 0.48583984f, + 0.34704590f, 0.03485107f, -0.02447510f, -0.00100708f, -0.01080322f, -0.02059937f, + 0.18615723f, 0.48483276f, 0.34970093f, 0.03637695f, -0.02468872f, -0.00106812f, + -0.01058960f, -0.02102661f, 0.18350220f, 0.48379517f, 0.35235596f, 0.03793335f, + -0.02487183f, -0.00112915f, -0.01034546f, -0.02145386f, 0.18081665f, 0.48272705f, + 0.35498047f, 0.03948975f, -0.02505493f, -0.00119019f, -0.01013184f, -0.02188110f, + 0.17816162f, 0.48162842f, 0.35760498f, 0.04107666f, -0.02523804f, -0.00125122f, + -0.00991821f, -0.02227783f, 0.17550659f, 0.48049927f, 0.36019897f, 0.04269409f, + -0.02542114f, -0.00131226f, -0.00970459f, -0.02264404f, 0.17288208f, 0.47933960f, + 0.36279297f, 0.04431152f, -0.02560425f, -0.00140381f, -0.00952148f, -0.02301025f, + 0.17025757f, 0.47814941f, 0.36538696f, 0.04595947f, -0.02578735f, -0.00146484f, + -0.00930786f, -0.02337646f, 0.16763306f, 0.47689819f, 0.36795044f, 0.04763794f, + -0.02593994f, -0.00152588f, -0.00909424f, -0.02371216f, 0.16503906f, 0.47564697f, + 0.37048340f, 0.04931641f, -0.02609253f, -0.00161743f, -0.00888062f, -0.02401733f, + 0.16244507f, 0.47436523f, 0.37304688f, 0.05102539f, -0.02627563f, -0.00170898f, + -0.00869751f, -0.02435303f, 0.15988159f, 0.47302246f, 0.37554932f, 0.05276489f, + -0.02642822f, -0.00177002f, -0.00848389f, -0.02462769f, 0.15731812f, 0.47167969f, + 0.37805176f, 0.05450439f, -0.02658081f, -0.00186157f, -0.00830078f, -0.02493286f, + 0.15475464f, 0.47027588f, 0.38055420f, 0.05627441f, -0.02670288f, -0.00195312f, + -0.00808716f, -0.02520752f, 0.15222168f, 0.46887207f, 0.38302612f, 0.05804443f, + -0.02685547f, -0.00204468f, -0.00790405f, -0.02545166f, 0.14968872f, 0.46743774f, + 0.38546753f, 0.05987549f, -0.02697754f, -0.00213623f, -0.00772095f, -0.02569580f, + 0.14718628f, 0.46594238f, 0.38790894f, 0.06170654f, -0.02709961f, -0.00222778f, + -0.00753784f, -0.02593994f, 0.14468384f, 0.46444702f, 0.39031982f, 0.06353760f, + -0.02722168f, -0.00231934f, -0.00735474f, -0.02615356f, 0.14218140f, 0.46289062f, + 0.39273071f, 0.06539917f, -0.02734375f, -0.00241089f, -0.00717163f, -0.02636719f, + 0.13970947f, 0.46133423f, 0.39511108f, 0.06729126f, -0.02743530f, -0.00250244f, + -0.00698853f, -0.02655029f, 0.13726807f, 0.45974731f, 0.39749146f, 0.06918335f, + -0.02755737f, -0.00259399f, -0.00680542f, -0.02673340f, 0.13479614f, 0.45812988f, + 0.39984131f, 0.07113647f, -0.02764893f, -0.00271606f, -0.00662231f, -0.02691650f, + 0.13238525f, 0.45648193f, 0.40216064f, 0.07305908f, -0.02774048f, -0.00280762f, + -0.00643921f, -0.02706909f, 0.12997437f, 0.45480347f, 0.40447998f, 0.07504272f, + -0.02780151f, -0.00292969f, -0.00628662f, -0.02722168f, 0.12756348f, 0.45309448f, + 0.40676880f, 0.07699585f, -0.02789307f, -0.00305176f, -0.00610352f, -0.02734375f, + 0.12518311f, 0.45135498f, 0.40902710f, 0.07901001f, -0.02795410f, -0.00314331f, + -0.00595093f, -0.02746582f, 0.12280273f, 0.44958496f, 0.41128540f, 0.08102417f, + -0.02801514f, -0.00326538f, -0.00579834f, -0.02758789f, 0.12045288f, 0.44778442f, + 0.41351318f, 0.08306885f, -0.02804565f, -0.00338745f, -0.00561523f, -0.02770996f, + 0.11813354f, 0.44598389f, 0.41571045f, 0.08511353f, -0.02810669f, -0.00350952f, + -0.00546265f, -0.02780151f, 0.11581421f, 0.44412231f, 0.41787720f, 0.08718872f, + -0.02813721f, -0.00363159f, -0.00531006f, -0.02786255f, 0.11349487f, 0.44226074f, + 0.42004395f, 0.08929443f, -0.02816772f, -0.00375366f, -0.00515747f, -0.02795410f, + 0.11120605f, 0.44036865f, 0.42218018f, 0.09140015f, -0.02816772f, -0.00387573f, + -0.00500488f, -0.02801514f, 0.10894775f, 0.43844604f, 0.42431641f, 0.09353638f, + -0.02819824f, -0.00402832f, -0.00485229f, -0.02807617f, 0.10668945f, 0.43649292f, + 0.42639160f, 0.09570312f, -0.02819824f, -0.00415039f, -0.00469971f, -0.02810669f, + 0.10446167f, 0.43453979f, 0.42846680f, 0.09786987f, -0.02819824f, -0.00427246f, + -0.00457764f, -0.02813721f, 0.10223389f, 0.43252563f, 0.43051147f, 0.10003662f, + -0.02816772f, -0.00442505f, -0.00442505f, -0.02816772f, 0.10003662f, 0.43051147f, + 0.43252563f, 0.10223389f, -0.02813721f, -0.00457764f, -0.00427246f, -0.02819824f, + 0.09786987f, 0.42846680f, 0.43453979f, 0.10446167f, -0.02810669f, -0.00469971f, + -0.00415039f, -0.02819824f, 0.09570312f, 0.42639160f, 0.43649292f, 0.10668945f, + -0.02807617f, -0.00485229f, -0.00402832f, -0.02819824f, 0.09353638f, 0.42431641f, + 0.43844604f, 0.10894775f, -0.02801514f, -0.00500488f, -0.00387573f, -0.02816772f, + 0.09140015f, 0.42218018f, 0.44036865f, 0.11120605f, -0.02795410f, -0.00515747f, + -0.00375366f, -0.02816772f, 0.08929443f, 0.42004395f, 0.44226074f, 0.11349487f, + -0.02786255f, -0.00531006f, -0.00363159f, -0.02813721f, 0.08718872f, 0.41787720f, + 0.44412231f, 0.11581421f, -0.02780151f, -0.00546265f, -0.00350952f, -0.02810669f, + 0.08511353f, 0.41571045f, 0.44598389f, 0.11813354f, -0.02770996f, -0.00561523f, + -0.00338745f, -0.02804565f, 0.08306885f, 0.41351318f, 0.44778442f, 0.12045288f, + -0.02758789f, -0.00579834f, -0.00326538f, -0.02801514f, 0.08102417f, 0.41128540f, + 0.44958496f, 0.12280273f, -0.02746582f, -0.00595093f, -0.00314331f, -0.02795410f, + 0.07901001f, 0.40902710f, 0.45135498f, 0.12518311f, -0.02734375f, -0.00610352f, + -0.00305176f, -0.02789307f, 0.07699585f, 0.40676880f, 0.45309448f, 0.12756348f, + -0.02722168f, -0.00628662f, -0.00292969f, -0.02780151f, 0.07504272f, 0.40447998f, + 0.45480347f, 0.12997437f, -0.02706909f, -0.00643921f, -0.00280762f, -0.02774048f, + 0.07305908f, 0.40216064f, 0.45648193f, 0.13238525f, -0.02691650f, -0.00662231f, + -0.00271606f, -0.02764893f, 0.07113647f, 0.39984131f, 0.45812988f, 0.13479614f, + -0.02673340f, -0.00680542f, -0.00259399f, -0.02755737f, 0.06918335f, 0.39749146f, + 0.45974731f, 0.13726807f, -0.02655029f, -0.00698853f, -0.00250244f, -0.02743530f, + 0.06729126f, 0.39511108f, 0.46133423f, 0.13970947f, -0.02636719f, -0.00717163f, + -0.00241089f, -0.02734375f, 0.06539917f, 0.39273071f, 0.46289062f, 0.14218140f, + -0.02615356f, -0.00735474f, -0.00231934f, -0.02722168f, 0.06353760f, 0.39031982f, + 0.46444702f, 0.14468384f, -0.02593994f, -0.00753784f, -0.00222778f, -0.02709961f, + 0.06170654f, 0.38790894f, 0.46594238f, 0.14718628f, -0.02569580f, -0.00772095f, + -0.00213623f, -0.02697754f, 0.05987549f, 0.38546753f, 0.46743774f, 0.14968872f, + -0.02545166f, -0.00790405f, -0.00204468f, -0.02685547f, 0.05804443f, 0.38302612f, + 0.46887207f, 0.15222168f, -0.02520752f, -0.00808716f, -0.00195312f, -0.02670288f, + 0.05627441f, 0.38055420f, 0.47027588f, 0.15475464f, -0.02493286f, -0.00830078f, + -0.00186157f, -0.02658081f, 0.05450439f, 0.37805176f, 0.47167969f, 0.15731812f, + -0.02462769f, -0.00848389f, -0.00177002f, -0.02642822f, 0.05276489f, 0.37554932f, + 0.47302246f, 0.15988159f, -0.02435303f, -0.00869751f, -0.00170898f, -0.02627563f, + 0.05102539f, 0.37304688f, 0.47436523f, 0.16244507f, -0.02401733f, -0.00888062f, + -0.00161743f, -0.02609253f, 0.04931641f, 0.37048340f, 0.47564697f, 0.16503906f, + -0.02371216f, -0.00909424f, -0.00152588f, -0.02593994f, 0.04763794f, 0.36795044f, + 0.47689819f, 0.16763306f, -0.02337646f, -0.00930786f, -0.00146484f, -0.02578735f, + 0.04595947f, 0.36538696f, 0.47814941f, 0.17025757f, -0.02301025f, -0.00952148f, + -0.00140381f, -0.02560425f, 0.04431152f, 0.36279297f, 0.47933960f, 0.17288208f, + -0.02264404f, -0.00970459f, -0.00131226f, -0.02542114f, 0.04269409f, 0.36019897f, + 0.48049927f, 0.17550659f, -0.02227783f, -0.00991821f, -0.00125122f, -0.02523804f, + 0.04107666f, 0.35760498f, 0.48162842f, 0.17816162f, -0.02188110f, -0.01013184f, + -0.00119019f, -0.02505493f, 0.03948975f, 0.35498047f, 0.48272705f, 0.18081665f, + -0.02145386f, -0.01034546f, -0.00112915f, -0.02487183f, 0.03793335f, 0.35235596f, + 0.48379517f, 0.18350220f, -0.02102661f, -0.01058960f, -0.00106812f, -0.02468872f, + 0.03637695f, 0.34970093f, 0.48483276f, 0.18615723f, -0.02059937f, -0.01080322f, + -0.00100708f, -0.02447510f, 0.03485107f, 0.34704590f, 0.48583984f, 0.18887329f, + -0.02014160f, -0.01101685f, -0.00094604f, -0.02429199f, 0.03335571f, 0.34439087f, + 0.48681641f, 0.19155884f, -0.01968384f, -0.01123047f, -0.00088501f, -0.02407837f, + 0.03186035f, 0.34170532f, 0.48776245f, 0.19427490f, -0.01919556f, -0.01147461f, + -0.00082397f, -0.02386475f, 0.03039551f, 0.33901978f, 0.48864746f, 0.19696045f, + -0.01867676f, -0.01168823f, -0.00079346f, -0.02365112f, 0.02896118f, 0.33633423f, + 0.48953247f, 0.19970703f, -0.01818848f, -0.01193237f, -0.00073242f, -0.02343750f, + 0.02752686f, 0.33361816f, 0.49035645f, 0.20242310f, -0.01763916f, -0.01214600f, + -0.00067139f, -0.02322388f, 0.02612305f, 0.33090210f, 0.49118042f, 0.20516968f, + -0.01708984f, -0.01239014f, -0.00064087f, -0.02301025f, 0.02471924f, 0.32818604f, + 0.49194336f, 0.20791626f, -0.01654053f, -0.01263428f, -0.00057983f, -0.02279663f, + 0.02337646f, 0.32543945f, 0.49267578f, 0.21066284f, -0.01596069f, -0.01284790f, + -0.00054932f, -0.02255249f, 0.02203369f, 0.32269287f, 0.49337769f, 0.21343994f, + -0.01535034f, -0.01309204f, -0.00048828f, -0.02233887f, 0.02069092f, 0.31994629f, + 0.49404907f, 0.21618652f, -0.01473999f, -0.01333618f, -0.00045776f, -0.02209473f, + 0.01937866f, 0.31719971f, 0.49465942f, 0.21896362f, -0.01409912f, -0.01358032f, + -0.00042725f, -0.02188110f, 0.01809692f, 0.31442261f, 0.49526978f, 0.22174072f, + -0.01345825f, -0.01379395f, -0.00039673f, -0.02163696f, 0.01684570f, 0.31164551f, + 0.49581909f, 0.22451782f, -0.01278687f, -0.01403809f, -0.00033569f, -0.02142334f, + 0.01559448f, 0.30886841f, 0.49636841f, 0.22732544f, -0.01211548f, -0.01428223f, + -0.00030518f, -0.02117920f, 0.01437378f, 0.30609131f, 0.49685669f, 0.23010254f, + -0.01141357f, -0.01452637f, -0.00027466f, -0.02093506f, 0.01318359f, 0.30331421f, + 0.49731445f, 0.23291016f, -0.01071167f, -0.01477051f, -0.00024414f, -0.02069092f, + 0.01199341f, 0.30050659f, 0.49774170f, 0.23571777f, -0.00997925f, -0.01501465f, + -0.00021362f, -0.02044678f, 0.01083374f, 0.29772949f, 0.49810791f, 0.23852539f, + -0.00921631f, -0.01525879f, -0.00018311f, -0.02020264f, 0.00967407f, 0.29492188f, + 0.49847412f, 0.24133301f, -0.00845337f, -0.01550293f, -0.00015259f, -0.01995850f, + 0.00854492f, 0.29211426f, 0.49877930f, 0.24414062f, -0.00765991f, -0.01574707f, + -0.00015259f, -0.01971436f, 0.00744629f, 0.28930664f, 0.49908447f, 0.24697876f, + -0.00686646f, -0.01602173f, -0.00012207f, -0.01947021f, 0.00634766f, 0.28646851f, + 0.49932861f, 0.24978638f, -0.00604248f, -0.01626587f, -0.00009155f, -0.01922607f, + 0.00527954f, 0.28366089f, 0.49954224f, 0.25259399f, -0.00518799f, -0.01651001f, + -0.00006104f, -0.01898193f, 0.00424194f, 0.28085327f, 0.49972534f, 0.25543213f, + -0.00433350f, -0.01675415f, -0.00006104f, -0.01873779f, 0.00320435f, 0.27801514f, + 0.49984741f, 0.25823975f, -0.00344849f, -0.01699829f, -0.00003052f, -0.01849365f, + 0.00219727f, 0.27520752f, 0.49996948f, 0.26107788f, -0.00256348f, -0.01724243f, + -0.00003052f, -0.01824951f, 0.00122070f, 0.27236938f, 0.50003052f, 0.26388550f, + -0.00164795f, -0.01748657f, 0.00000000f, -0.01800537f, 0.00024414f, 0.26956177f, + 0.50006104f, 0.26672363f, -0.00070190f, -0.01776123f, + }; + + static constexpr std::array<f32, 1024> lut1 = { + 0.01275635f, -0.07745361f, 0.18670654f, 0.75119019f, 0.19219971f, -0.07821655f, + 0.01272583f, 0.00000000f, 0.01281738f, -0.07666016f, 0.18124390f, 0.75106812f, + 0.19772339f, -0.07897949f, 0.01266479f, 0.00003052f, 0.01284790f, -0.07583618f, + 0.17581177f, 0.75088501f, 0.20330811f, -0.07971191f, 0.01257324f, 0.00006104f, + 0.01287842f, -0.07501221f, 0.17044067f, 0.75057983f, 0.20892334f, -0.08041382f, + 0.01248169f, 0.00009155f, 0.01290894f, -0.07415771f, 0.16510010f, 0.75018311f, + 0.21453857f, -0.08111572f, 0.01239014f, 0.00012207f, 0.01290894f, -0.07330322f, + 0.15979004f, 0.74966431f, 0.22021484f, -0.08178711f, 0.01229858f, 0.00015259f, + 0.01290894f, -0.07241821f, 0.15454102f, 0.74908447f, 0.22592163f, -0.08242798f, + 0.01217651f, 0.00018311f, 0.01290894f, -0.07150269f, 0.14932251f, 0.74838257f, + 0.23165894f, -0.08303833f, 0.01205444f, 0.00021362f, 0.01290894f, -0.07058716f, + 0.14416504f, 0.74755859f, 0.23742676f, -0.08364868f, 0.01193237f, 0.00024414f, + 0.01287842f, -0.06967163f, 0.13903809f, 0.74667358f, 0.24322510f, -0.08419800f, + 0.01177979f, 0.00027466f, 0.01284790f, -0.06872559f, 0.13397217f, 0.74566650f, + 0.24905396f, -0.08474731f, 0.01162720f, 0.00033569f, 0.01281738f, -0.06777954f, + 0.12893677f, 0.74456787f, 0.25491333f, -0.08526611f, 0.01147461f, 0.00036621f, + 0.01278687f, -0.06683350f, 0.12396240f, 0.74337769f, 0.26077271f, -0.08575439f, + 0.01129150f, 0.00042725f, 0.01275635f, -0.06585693f, 0.11901855f, 0.74206543f, + 0.26669312f, -0.08621216f, 0.01110840f, 0.00045776f, 0.01269531f, -0.06488037f, + 0.11413574f, 0.74069214f, 0.27261353f, -0.08663940f, 0.01092529f, 0.00051880f, + 0.01263428f, -0.06387329f, 0.10931396f, 0.73919678f, 0.27853394f, -0.08700562f, + 0.01071167f, 0.00057983f, 0.01257324f, -0.06286621f, 0.10452271f, 0.73760986f, + 0.28451538f, -0.08737183f, 0.01049805f, 0.00064087f, 0.01251221f, -0.06185913f, + 0.09979248f, 0.73593140f, 0.29049683f, -0.08770752f, 0.01025391f, 0.00067139f, + 0.01242065f, -0.06082153f, 0.09512329f, 0.73413086f, 0.29647827f, -0.08801270f, + 0.01000977f, 0.00073242f, 0.01232910f, -0.05981445f, 0.09051514f, 0.73226929f, + 0.30249023f, -0.08828735f, 0.00973511f, 0.00079346f, 0.01226807f, -0.05877686f, + 0.08593750f, 0.73028564f, 0.30853271f, -0.08850098f, 0.00949097f, 0.00088501f, + 0.01214600f, -0.05773926f, 0.08142090f, 0.72824097f, 0.31457520f, -0.08871460f, + 0.00918579f, 0.00094604f, 0.01205444f, -0.05670166f, 0.07696533f, 0.72607422f, + 0.32061768f, -0.08886719f, 0.00891113f, 0.00100708f, 0.01196289f, -0.05563354f, + 0.07257080f, 0.72381592f, 0.32669067f, -0.08898926f, 0.00860596f, 0.00106812f, + 0.01187134f, -0.05459595f, 0.06820679f, 0.72146606f, 0.33276367f, -0.08908081f, + 0.00827026f, 0.00115967f, 0.01174927f, -0.05352783f, 0.06393433f, 0.71902466f, + 0.33883667f, -0.08911133f, 0.00796509f, 0.00122070f, 0.01162720f, -0.05245972f, + 0.05969238f, 0.71649170f, 0.34494019f, -0.08914185f, 0.00759888f, 0.00131226f, + 0.01150513f, -0.05139160f, 0.05551147f, 0.71389771f, 0.35101318f, -0.08911133f, + 0.00726318f, 0.00137329f, 0.01138306f, -0.05032349f, 0.05139160f, 0.71118164f, + 0.35711670f, -0.08901978f, 0.00686646f, 0.00146484f, 0.01126099f, -0.04928589f, + 0.04733276f, 0.70837402f, 0.36322021f, -0.08892822f, 0.00650024f, 0.00155640f, + 0.01113892f, -0.04821777f, 0.04333496f, 0.70550537f, 0.36932373f, -0.08877563f, + 0.00610352f, 0.00164795f, 0.01101685f, -0.04714966f, 0.03939819f, 0.70251465f, + 0.37542725f, -0.08856201f, 0.00567627f, 0.00173950f, 0.01086426f, -0.04608154f, + 0.03549194f, 0.69946289f, 0.38153076f, -0.08834839f, 0.00527954f, 0.00183105f, + 0.01074219f, -0.04501343f, 0.03167725f, 0.69631958f, 0.38763428f, -0.08804321f, + 0.00482178f, 0.00192261f, 0.01058960f, -0.04394531f, 0.02792358f, 0.69308472f, + 0.39370728f, -0.08773804f, 0.00436401f, 0.00201416f, 0.01043701f, -0.04287720f, + 0.02420044f, 0.68975830f, 0.39981079f, -0.08737183f, 0.00390625f, 0.00210571f, + 0.01031494f, -0.04180908f, 0.02056885f, 0.68637085f, 0.40588379f, -0.08694458f, + 0.00344849f, 0.00222778f, 0.01016235f, -0.04074097f, 0.01699829f, 0.68289185f, + 0.41195679f, -0.08648682f, 0.00296021f, 0.00231934f, 0.01000977f, -0.03970337f, + 0.01345825f, 0.67932129f, 0.41802979f, -0.08596802f, 0.00244141f, 0.00244141f, + 0.00985718f, -0.03863525f, 0.01000977f, 0.67568970f, 0.42407227f, -0.08541870f, + 0.00192261f, 0.00253296f, 0.00970459f, -0.03759766f, 0.00662231f, 0.67196655f, + 0.43011475f, -0.08480835f, 0.00140381f, 0.00265503f, 0.00955200f, -0.03652954f, + 0.00326538f, 0.66815186f, 0.43612671f, -0.08416748f, 0.00085449f, 0.00277710f, + 0.00936890f, -0.03549194f, 0.00000000f, 0.66427612f, 0.44213867f, -0.08346558f, + 0.00027466f, 0.00289917f, 0.00921631f, -0.03445435f, -0.00320435f, 0.66030884f, + 0.44812012f, -0.08270264f, -0.00027466f, 0.00299072f, 0.00906372f, -0.03344727f, + -0.00634766f, 0.65631104f, 0.45407104f, -0.08190918f, -0.00088501f, 0.00311279f, + 0.00891113f, -0.03240967f, -0.00946045f, 0.65219116f, 0.46002197f, -0.08105469f, + -0.00146484f, 0.00323486f, 0.00872803f, -0.03140259f, -0.01248169f, 0.64801025f, + 0.46594238f, -0.08013916f, -0.00210571f, 0.00338745f, 0.00857544f, -0.03039551f, + -0.01544189f, 0.64376831f, 0.47183228f, -0.07919312f, -0.00271606f, 0.00350952f, + 0.00842285f, -0.02938843f, -0.01834106f, 0.63946533f, 0.47772217f, -0.07818604f, + -0.00335693f, 0.00363159f, 0.00823975f, -0.02838135f, -0.02117920f, 0.63507080f, + 0.48358154f, -0.07711792f, -0.00402832f, 0.00375366f, 0.00808716f, -0.02740479f, + -0.02395630f, 0.63061523f, 0.48937988f, -0.07598877f, -0.00469971f, 0.00390625f, + 0.00793457f, -0.02642822f, -0.02667236f, 0.62609863f, 0.49517822f, -0.07482910f, + -0.00537109f, 0.00402832f, 0.00775146f, -0.02545166f, -0.02932739f, 0.62152100f, + 0.50094604f, -0.07357788f, -0.00607300f, 0.00418091f, 0.00759888f, -0.02450562f, + -0.03192139f, 0.61685181f, 0.50665283f, -0.07229614f, -0.00677490f, 0.00430298f, + 0.00741577f, -0.02352905f, -0.03445435f, 0.61215210f, 0.51235962f, -0.07098389f, + -0.00750732f, 0.00445557f, 0.00726318f, -0.02258301f, -0.03689575f, 0.60736084f, + 0.51800537f, -0.06958008f, -0.00823975f, 0.00460815f, 0.00711060f, -0.02166748f, + -0.03930664f, 0.60253906f, 0.52362061f, -0.06811523f, -0.00897217f, 0.00476074f, + 0.00692749f, -0.02075195f, -0.04165649f, 0.59762573f, 0.52920532f, -0.06661987f, + -0.00973511f, 0.00488281f, 0.00677490f, -0.01983643f, -0.04394531f, 0.59268188f, + 0.53475952f, -0.06506348f, -0.01052856f, 0.00503540f, 0.00662231f, -0.01892090f, + -0.04617310f, 0.58767700f, 0.54025269f, -0.06344604f, -0.01129150f, 0.00518799f, + 0.00643921f, -0.01803589f, -0.04830933f, 0.58261108f, 0.54571533f, -0.06173706f, + -0.01208496f, 0.00534058f, 0.00628662f, -0.01715088f, -0.05041504f, 0.57748413f, + 0.55111694f, -0.05999756f, -0.01290894f, 0.00549316f, 0.00613403f, -0.01626587f, + -0.05245972f, 0.57232666f, 0.55648804f, -0.05819702f, -0.01373291f, 0.00564575f, + 0.00598145f, -0.01541138f, -0.05444336f, 0.56707764f, 0.56182861f, -0.05636597f, + -0.01455688f, 0.00582886f, 0.00582886f, -0.01455688f, -0.05636597f, 0.56182861f, + 0.56707764f, -0.05444336f, -0.01541138f, 0.00598145f, 0.00564575f, -0.01373291f, + -0.05819702f, 0.55648804f, 0.57232666f, -0.05245972f, -0.01626587f, 0.00613403f, + 0.00549316f, -0.01290894f, -0.05999756f, 0.55111694f, 0.57748413f, -0.05041504f, + -0.01715088f, 0.00628662f, 0.00534058f, -0.01208496f, -0.06173706f, 0.54571533f, + 0.58261108f, -0.04830933f, -0.01803589f, 0.00643921f, 0.00518799f, -0.01129150f, + -0.06344604f, 0.54025269f, 0.58767700f, -0.04617310f, -0.01892090f, 0.00662231f, + 0.00503540f, -0.01052856f, -0.06506348f, 0.53475952f, 0.59268188f, -0.04394531f, + -0.01983643f, 0.00677490f, 0.00488281f, -0.00973511f, -0.06661987f, 0.52920532f, + 0.59762573f, -0.04165649f, -0.02075195f, 0.00692749f, 0.00476074f, -0.00897217f, + -0.06811523f, 0.52362061f, 0.60253906f, -0.03930664f, -0.02166748f, 0.00711060f, + 0.00460815f, -0.00823975f, -0.06958008f, 0.51800537f, 0.60736084f, -0.03689575f, + -0.02258301f, 0.00726318f, 0.00445557f, -0.00750732f, -0.07098389f, 0.51235962f, + 0.61215210f, -0.03445435f, -0.02352905f, 0.00741577f, 0.00430298f, -0.00677490f, + -0.07229614f, 0.50665283f, 0.61685181f, -0.03192139f, -0.02450562f, 0.00759888f, + 0.00418091f, -0.00607300f, -0.07357788f, 0.50094604f, 0.62152100f, -0.02932739f, + -0.02545166f, 0.00775146f, 0.00402832f, -0.00537109f, -0.07482910f, 0.49517822f, + 0.62609863f, -0.02667236f, -0.02642822f, 0.00793457f, 0.00390625f, -0.00469971f, + -0.07598877f, 0.48937988f, 0.63061523f, -0.02395630f, -0.02740479f, 0.00808716f, + 0.00375366f, -0.00402832f, -0.07711792f, 0.48358154f, 0.63507080f, -0.02117920f, + -0.02838135f, 0.00823975f, 0.00363159f, -0.00335693f, -0.07818604f, 0.47772217f, + 0.63946533f, -0.01834106f, -0.02938843f, 0.00842285f, 0.00350952f, -0.00271606f, + -0.07919312f, 0.47183228f, 0.64376831f, -0.01544189f, -0.03039551f, 0.00857544f, + 0.00338745f, -0.00210571f, -0.08013916f, 0.46594238f, 0.64801025f, -0.01248169f, + -0.03140259f, 0.00872803f, 0.00323486f, -0.00146484f, -0.08105469f, 0.46002197f, + 0.65219116f, -0.00946045f, -0.03240967f, 0.00891113f, 0.00311279f, -0.00088501f, + -0.08190918f, 0.45407104f, 0.65631104f, -0.00634766f, -0.03344727f, 0.00906372f, + 0.00299072f, -0.00027466f, -0.08270264f, 0.44812012f, 0.66030884f, -0.00320435f, + -0.03445435f, 0.00921631f, 0.00289917f, 0.00027466f, -0.08346558f, 0.44213867f, + 0.66427612f, 0.00000000f, -0.03549194f, 0.00936890f, 0.00277710f, 0.00085449f, + -0.08416748f, 0.43612671f, 0.66815186f, 0.00326538f, -0.03652954f, 0.00955200f, + 0.00265503f, 0.00140381f, -0.08480835f, 0.43011475f, 0.67196655f, 0.00662231f, + -0.03759766f, 0.00970459f, 0.00253296f, 0.00192261f, -0.08541870f, 0.42407227f, + 0.67568970f, 0.01000977f, -0.03863525f, 0.00985718f, 0.00244141f, 0.00244141f, + -0.08596802f, 0.41802979f, 0.67932129f, 0.01345825f, -0.03970337f, 0.01000977f, + 0.00231934f, 0.00296021f, -0.08648682f, 0.41195679f, 0.68289185f, 0.01699829f, + -0.04074097f, 0.01016235f, 0.00222778f, 0.00344849f, -0.08694458f, 0.40588379f, + 0.68637085f, 0.02056885f, -0.04180908f, 0.01031494f, 0.00210571f, 0.00390625f, + -0.08737183f, 0.39981079f, 0.68975830f, 0.02420044f, -0.04287720f, 0.01043701f, + 0.00201416f, 0.00436401f, -0.08773804f, 0.39370728f, 0.69308472f, 0.02792358f, + -0.04394531f, 0.01058960f, 0.00192261f, 0.00482178f, -0.08804321f, 0.38763428f, + 0.69631958f, 0.03167725f, -0.04501343f, 0.01074219f, 0.00183105f, 0.00527954f, + -0.08834839f, 0.38153076f, 0.69946289f, 0.03549194f, -0.04608154f, 0.01086426f, + 0.00173950f, 0.00567627f, -0.08856201f, 0.37542725f, 0.70251465f, 0.03939819f, + -0.04714966f, 0.01101685f, 0.00164795f, 0.00610352f, -0.08877563f, 0.36932373f, + 0.70550537f, 0.04333496f, -0.04821777f, 0.01113892f, 0.00155640f, 0.00650024f, + -0.08892822f, 0.36322021f, 0.70837402f, 0.04733276f, -0.04928589f, 0.01126099f, + 0.00146484f, 0.00686646f, -0.08901978f, 0.35711670f, 0.71118164f, 0.05139160f, + -0.05032349f, 0.01138306f, 0.00137329f, 0.00726318f, -0.08911133f, 0.35101318f, + 0.71389771f, 0.05551147f, -0.05139160f, 0.01150513f, 0.00131226f, 0.00759888f, + -0.08914185f, 0.34494019f, 0.71649170f, 0.05969238f, -0.05245972f, 0.01162720f, + 0.00122070f, 0.00796509f, -0.08911133f, 0.33883667f, 0.71902466f, 0.06393433f, + -0.05352783f, 0.01174927f, 0.00115967f, 0.00827026f, -0.08908081f, 0.33276367f, + 0.72146606f, 0.06820679f, -0.05459595f, 0.01187134f, 0.00106812f, 0.00860596f, + -0.08898926f, 0.32669067f, 0.72381592f, 0.07257080f, -0.05563354f, 0.01196289f, + 0.00100708f, 0.00891113f, -0.08886719f, 0.32061768f, 0.72607422f, 0.07696533f, + -0.05670166f, 0.01205444f, 0.00094604f, 0.00918579f, -0.08871460f, 0.31457520f, + 0.72824097f, 0.08142090f, -0.05773926f, 0.01214600f, 0.00088501f, 0.00949097f, + -0.08850098f, 0.30853271f, 0.73028564f, 0.08593750f, -0.05877686f, 0.01226807f, + 0.00079346f, 0.00973511f, -0.08828735f, 0.30249023f, 0.73226929f, 0.09051514f, + -0.05981445f, 0.01232910f, 0.00073242f, 0.01000977f, -0.08801270f, 0.29647827f, + 0.73413086f, 0.09512329f, -0.06082153f, 0.01242065f, 0.00067139f, 0.01025391f, + -0.08770752f, 0.29049683f, 0.73593140f, 0.09979248f, -0.06185913f, 0.01251221f, + 0.00064087f, 0.01049805f, -0.08737183f, 0.28451538f, 0.73760986f, 0.10452271f, + -0.06286621f, 0.01257324f, 0.00057983f, 0.01071167f, -0.08700562f, 0.27853394f, + 0.73919678f, 0.10931396f, -0.06387329f, 0.01263428f, 0.00051880f, 0.01092529f, + -0.08663940f, 0.27261353f, 0.74069214f, 0.11413574f, -0.06488037f, 0.01269531f, + 0.00045776f, 0.01110840f, -0.08621216f, 0.26669312f, 0.74206543f, 0.11901855f, + -0.06585693f, 0.01275635f, 0.00042725f, 0.01129150f, -0.08575439f, 0.26077271f, + 0.74337769f, 0.12396240f, -0.06683350f, 0.01278687f, 0.00036621f, 0.01147461f, + -0.08526611f, 0.25491333f, 0.74456787f, 0.12893677f, -0.06777954f, 0.01281738f, + 0.00033569f, 0.01162720f, -0.08474731f, 0.24905396f, 0.74566650f, 0.13397217f, + -0.06872559f, 0.01284790f, 0.00027466f, 0.01177979f, -0.08419800f, 0.24322510f, + 0.74667358f, 0.13903809f, -0.06967163f, 0.01287842f, 0.00024414f, 0.01193237f, + -0.08364868f, 0.23742676f, 0.74755859f, 0.14416504f, -0.07058716f, 0.01290894f, + 0.00021362f, 0.01205444f, -0.08303833f, 0.23165894f, 0.74838257f, 0.14932251f, + -0.07150269f, 0.01290894f, 0.00018311f, 0.01217651f, -0.08242798f, 0.22592163f, + 0.74908447f, 0.15454102f, -0.07241821f, 0.01290894f, 0.00015259f, 0.01229858f, + -0.08178711f, 0.22021484f, 0.74966431f, 0.15979004f, -0.07330322f, 0.01290894f, + 0.00012207f, 0.01239014f, -0.08111572f, 0.21453857f, 0.75018311f, 0.16510010f, + -0.07415771f, 0.01290894f, 0.00009155f, 0.01248169f, -0.08041382f, 0.20892334f, + 0.75057983f, 0.17044067f, -0.07501221f, 0.01287842f, 0.00006104f, 0.01257324f, + -0.07971191f, 0.20330811f, 0.75088501f, 0.17581177f, -0.07583618f, 0.01284790f, + 0.00003052f, 0.01266479f, -0.07897949f, 0.19772339f, 0.75106812f, 0.18124390f, + -0.07666016f, 0.01281738f, 0.00000000f, 0.01272583f, -0.07821655f, 0.19219971f, + 0.75119019f, 0.18670654f, -0.07745361f, 0.01275635f, + }; + + static constexpr std::array<f32, 1024> lut2 = { + -0.00036621f, 0.00143433f, -0.00408936f, 0.99996948f, 0.00247192f, -0.00048828f, + 0.00006104f, 0.00000000f, -0.00079346f, 0.00329590f, -0.01052856f, 0.99975586f, + 0.00918579f, -0.00241089f, 0.00051880f, -0.00003052f, -0.00122070f, 0.00512695f, + -0.01684570f, 0.99929810f, 0.01605225f, -0.00439453f, 0.00097656f, -0.00006104f, + -0.00161743f, 0.00689697f, -0.02297974f, 0.99862671f, 0.02304077f, -0.00640869f, + 0.00143433f, -0.00009155f, -0.00201416f, 0.00866699f, -0.02899170f, 0.99774170f, + 0.03018188f, -0.00845337f, 0.00192261f, -0.00015259f, -0.00238037f, 0.01037598f, + -0.03488159f, 0.99664307f, 0.03741455f, -0.01055908f, 0.00241089f, -0.00018311f, + -0.00274658f, 0.01202393f, -0.04061890f, 0.99533081f, 0.04483032f, -0.01266479f, + 0.00292969f, -0.00024414f, -0.00308228f, 0.01364136f, -0.04620361f, 0.99377441f, + 0.05233765f, -0.01483154f, 0.00344849f, -0.00027466f, -0.00341797f, 0.01522827f, + -0.05163574f, 0.99200439f, 0.05999756f, -0.01699829f, 0.00396729f, -0.00033569f, + -0.00375366f, 0.01678467f, -0.05691528f, 0.99002075f, 0.06777954f, -0.01922607f, + 0.00451660f, -0.00039673f, -0.00405884f, 0.01828003f, -0.06207275f, 0.98782349f, + 0.07568359f, -0.02145386f, 0.00506592f, -0.00042725f, -0.00436401f, 0.01971436f, + -0.06707764f, 0.98541260f, 0.08370972f, -0.02374268f, 0.00564575f, -0.00048828f, + -0.00463867f, 0.02114868f, -0.07192993f, 0.98278809f, 0.09185791f, -0.02603149f, + 0.00622559f, -0.00054932f, -0.00494385f, 0.02252197f, -0.07666016f, 0.97991943f, + 0.10012817f, -0.02835083f, 0.00680542f, -0.00061035f, -0.00518799f, 0.02383423f, + -0.08123779f, 0.97686768f, 0.10848999f, -0.03073120f, 0.00738525f, -0.00070190f, + -0.00543213f, 0.02511597f, -0.08566284f, 0.97360229f, 0.11700439f, -0.03308105f, + 0.00799561f, -0.00076294f, -0.00567627f, 0.02636719f, -0.08993530f, 0.97012329f, + 0.12561035f, -0.03549194f, 0.00860596f, -0.00082397f, -0.00592041f, 0.02755737f, + -0.09405518f, 0.96643066f, 0.13436890f, -0.03790283f, 0.00924683f, -0.00091553f, + -0.00613403f, 0.02868652f, -0.09805298f, 0.96252441f, 0.14318848f, -0.04034424f, + 0.00985718f, -0.00097656f, -0.00631714f, 0.02981567f, -0.10189819f, 0.95843506f, + 0.15213013f, -0.04281616f, 0.01049805f, -0.00106812f, -0.00653076f, 0.03085327f, + -0.10559082f, 0.95413208f, 0.16119385f, -0.04528809f, 0.01113892f, -0.00112915f, + -0.00671387f, 0.03189087f, -0.10916138f, 0.94961548f, 0.17034912f, -0.04779053f, + 0.01181030f, -0.00122070f, -0.00686646f, 0.03286743f, -0.11254883f, 0.94491577f, + 0.17959595f, -0.05029297f, 0.01248169f, -0.00131226f, -0.00701904f, 0.03378296f, + -0.11584473f, 0.94000244f, 0.18893433f, -0.05279541f, 0.01315308f, -0.00140381f, + -0.00717163f, 0.03466797f, -0.11895752f, 0.93490601f, 0.19839478f, -0.05532837f, + 0.01382446f, -0.00149536f, -0.00732422f, 0.03552246f, -0.12194824f, 0.92962646f, + 0.20791626f, -0.05786133f, 0.01449585f, -0.00158691f, -0.00744629f, 0.03631592f, + -0.12478638f, 0.92413330f, 0.21752930f, -0.06042480f, 0.01519775f, -0.00167847f, + -0.00753784f, 0.03707886f, -0.12750244f, 0.91848755f, 0.22723389f, -0.06298828f, + 0.01586914f, -0.00177002f, -0.00765991f, 0.03781128f, -0.13006592f, 0.91262817f, + 0.23703003f, -0.06555176f, 0.01657104f, -0.00189209f, -0.00775146f, 0.03848267f, + -0.13250732f, 0.90658569f, 0.24691772f, -0.06808472f, 0.01727295f, -0.00198364f, + -0.00784302f, 0.03909302f, -0.13479614f, 0.90036011f, 0.25683594f, -0.07064819f, + 0.01797485f, -0.00210571f, -0.00790405f, 0.03970337f, -0.13696289f, 0.89395142f, + 0.26687622f, -0.07321167f, 0.01870728f, -0.00219727f, -0.00796509f, 0.04025269f, + -0.13900757f, 0.88739014f, 0.27694702f, -0.07577515f, 0.01940918f, -0.00231934f, + -0.00802612f, 0.04077148f, -0.14089966f, 0.88064575f, 0.28710938f, -0.07833862f, + 0.02011108f, -0.00244141f, -0.00808716f, 0.04122925f, -0.14263916f, 0.87374878f, + 0.29733276f, -0.08090210f, 0.02084351f, -0.00253296f, -0.00811768f, 0.04165649f, + -0.14428711f, 0.86666870f, 0.30761719f, -0.08343506f, 0.02154541f, -0.00265503f, + -0.00814819f, 0.04205322f, -0.14578247f, 0.85940552f, 0.31793213f, -0.08596802f, + 0.02227783f, -0.00277710f, -0.00814819f, 0.04238892f, -0.14715576f, 0.85202026f, + 0.32833862f, -0.08847046f, 0.02297974f, -0.00289917f, -0.00817871f, 0.04272461f, + -0.14840698f, 0.84445190f, 0.33874512f, -0.09097290f, 0.02371216f, -0.00302124f, + -0.00817871f, 0.04299927f, -0.14953613f, 0.83673096f, 0.34924316f, -0.09347534f, + 0.02441406f, -0.00314331f, -0.00817871f, 0.04321289f, -0.15054321f, 0.82888794f, + 0.35977173f, -0.09594727f, 0.02514648f, -0.00326538f, -0.00814819f, 0.04342651f, + -0.15142822f, 0.82086182f, 0.37033081f, -0.09838867f, 0.02584839f, -0.00341797f, + -0.00814819f, 0.04357910f, -0.15219116f, 0.81271362f, 0.38092041f, -0.10079956f, + 0.02655029f, -0.00354004f, -0.00811768f, 0.04373169f, -0.15283203f, 0.80441284f, + 0.39154053f, -0.10321045f, 0.02725220f, -0.00366211f, -0.00808716f, 0.04382324f, + -0.15338135f, 0.79598999f, 0.40219116f, -0.10559082f, 0.02795410f, -0.00381470f, + -0.00805664f, 0.04388428f, -0.15377808f, 0.78741455f, 0.41287231f, -0.10794067f, + 0.02865601f, -0.00393677f, -0.00799561f, 0.04388428f, -0.15408325f, 0.77871704f, + 0.42358398f, -0.11026001f, 0.02935791f, -0.00405884f, -0.00793457f, 0.04388428f, + -0.15426636f, 0.76989746f, 0.43429565f, -0.11251831f, 0.03002930f, -0.00421143f, + -0.00787354f, 0.04385376f, -0.15435791f, 0.76095581f, 0.44500732f, -0.11477661f, + 0.03070068f, -0.00433350f, -0.00781250f, 0.04379272f, -0.15435791f, 0.75192261f, + 0.45574951f, -0.11697388f, 0.03137207f, -0.00448608f, -0.00775146f, 0.04367065f, + -0.15420532f, 0.74273682f, 0.46649170f, -0.11914062f, 0.03201294f, -0.00460815f, + -0.00769043f, 0.04354858f, -0.15399170f, 0.73345947f, 0.47723389f, -0.12127686f, + 0.03268433f, -0.00473022f, -0.00759888f, 0.04339600f, -0.15365601f, 0.72406006f, + 0.48794556f, -0.12335205f, 0.03329468f, -0.00488281f, -0.00750732f, 0.04321289f, + -0.15322876f, 0.71456909f, 0.49868774f, -0.12539673f, 0.03393555f, -0.00500488f, + -0.00741577f, 0.04296875f, -0.15270996f, 0.70498657f, 0.50936890f, -0.12738037f, + 0.03454590f, -0.00515747f, -0.00732422f, 0.04272461f, -0.15209961f, 0.69528198f, + 0.52008057f, -0.12930298f, 0.03515625f, -0.00527954f, -0.00723267f, 0.04248047f, + -0.15136719f, 0.68551636f, 0.53076172f, -0.13119507f, 0.03573608f, -0.00543213f, + -0.00714111f, 0.04217529f, -0.15057373f, 0.67565918f, 0.54138184f, -0.13299561f, + 0.03631592f, -0.00555420f, -0.00701904f, 0.04183960f, -0.14968872f, 0.66571045f, + 0.55200195f, -0.13476562f, 0.03689575f, -0.00567627f, -0.00692749f, 0.04150391f, + -0.14871216f, 0.65567017f, 0.56259155f, -0.13647461f, 0.03741455f, -0.00582886f, + -0.00680542f, 0.04113770f, -0.14767456f, 0.64556885f, 0.57315063f, -0.13812256f, + 0.03796387f, -0.00595093f, -0.00668335f, 0.04074097f, -0.14651489f, 0.63540649f, + 0.58364868f, -0.13970947f, 0.03845215f, -0.00607300f, -0.00656128f, 0.04031372f, + -0.14529419f, 0.62518311f, 0.59411621f, -0.14120483f, 0.03897095f, -0.00619507f, + -0.00643921f, 0.03988647f, -0.14401245f, 0.61486816f, 0.60452271f, -0.14263916f, + 0.03942871f, -0.00631714f, -0.00631714f, 0.03942871f, -0.14263916f, 0.60452271f, + 0.61486816f, -0.14401245f, 0.03988647f, -0.00643921f, -0.00619507f, 0.03897095f, + -0.14120483f, 0.59411621f, 0.62518311f, -0.14529419f, 0.04031372f, -0.00656128f, + -0.00607300f, 0.03845215f, -0.13970947f, 0.58364868f, 0.63540649f, -0.14651489f, + 0.04074097f, -0.00668335f, -0.00595093f, 0.03796387f, -0.13812256f, 0.57315063f, + 0.64556885f, -0.14767456f, 0.04113770f, -0.00680542f, -0.00582886f, 0.03741455f, + -0.13647461f, 0.56259155f, 0.65567017f, -0.14871216f, 0.04150391f, -0.00692749f, + -0.00567627f, 0.03689575f, -0.13476562f, 0.55200195f, 0.66571045f, -0.14968872f, + 0.04183960f, -0.00701904f, -0.00555420f, 0.03631592f, -0.13299561f, 0.54138184f, + 0.67565918f, -0.15057373f, 0.04217529f, -0.00714111f, -0.00543213f, 0.03573608f, + -0.13119507f, 0.53076172f, 0.68551636f, -0.15136719f, 0.04248047f, -0.00723267f, + -0.00527954f, 0.03515625f, -0.12930298f, 0.52008057f, 0.69528198f, -0.15209961f, + 0.04272461f, -0.00732422f, -0.00515747f, 0.03454590f, -0.12738037f, 0.50936890f, + 0.70498657f, -0.15270996f, 0.04296875f, -0.00741577f, -0.00500488f, 0.03393555f, + -0.12539673f, 0.49868774f, 0.71456909f, -0.15322876f, 0.04321289f, -0.00750732f, + -0.00488281f, 0.03329468f, -0.12335205f, 0.48794556f, 0.72406006f, -0.15365601f, + 0.04339600f, -0.00759888f, -0.00473022f, 0.03268433f, -0.12127686f, 0.47723389f, + 0.73345947f, -0.15399170f, 0.04354858f, -0.00769043f, -0.00460815f, 0.03201294f, + -0.11914062f, 0.46649170f, 0.74273682f, -0.15420532f, 0.04367065f, -0.00775146f, + -0.00448608f, 0.03137207f, -0.11697388f, 0.45574951f, 0.75192261f, -0.15435791f, + 0.04379272f, -0.00781250f, -0.00433350f, 0.03070068f, -0.11477661f, 0.44500732f, + 0.76095581f, -0.15435791f, 0.04385376f, -0.00787354f, -0.00421143f, 0.03002930f, + -0.11251831f, 0.43429565f, 0.76989746f, -0.15426636f, 0.04388428f, -0.00793457f, + -0.00405884f, 0.02935791f, -0.11026001f, 0.42358398f, 0.77871704f, -0.15408325f, + 0.04388428f, -0.00799561f, -0.00393677f, 0.02865601f, -0.10794067f, 0.41287231f, + 0.78741455f, -0.15377808f, 0.04388428f, -0.00805664f, -0.00381470f, 0.02795410f, + -0.10559082f, 0.40219116f, 0.79598999f, -0.15338135f, 0.04382324f, -0.00808716f, + -0.00366211f, 0.02725220f, -0.10321045f, 0.39154053f, 0.80441284f, -0.15283203f, + 0.04373169f, -0.00811768f, -0.00354004f, 0.02655029f, -0.10079956f, 0.38092041f, + 0.81271362f, -0.15219116f, 0.04357910f, -0.00814819f, -0.00341797f, 0.02584839f, + -0.09838867f, 0.37033081f, 0.82086182f, -0.15142822f, 0.04342651f, -0.00814819f, + -0.00326538f, 0.02514648f, -0.09594727f, 0.35977173f, 0.82888794f, -0.15054321f, + 0.04321289f, -0.00817871f, -0.00314331f, 0.02441406f, -0.09347534f, 0.34924316f, + 0.83673096f, -0.14953613f, 0.04299927f, -0.00817871f, -0.00302124f, 0.02371216f, + -0.09097290f, 0.33874512f, 0.84445190f, -0.14840698f, 0.04272461f, -0.00817871f, + -0.00289917f, 0.02297974f, -0.08847046f, 0.32833862f, 0.85202026f, -0.14715576f, + 0.04238892f, -0.00814819f, -0.00277710f, 0.02227783f, -0.08596802f, 0.31793213f, + 0.85940552f, -0.14578247f, 0.04205322f, -0.00814819f, -0.00265503f, 0.02154541f, + -0.08343506f, 0.30761719f, 0.86666870f, -0.14428711f, 0.04165649f, -0.00811768f, + -0.00253296f, 0.02084351f, -0.08090210f, 0.29733276f, 0.87374878f, -0.14263916f, + 0.04122925f, -0.00808716f, -0.00244141f, 0.02011108f, -0.07833862f, 0.28710938f, + 0.88064575f, -0.14089966f, 0.04077148f, -0.00802612f, -0.00231934f, 0.01940918f, + -0.07577515f, 0.27694702f, 0.88739014f, -0.13900757f, 0.04025269f, -0.00796509f, + -0.00219727f, 0.01870728f, -0.07321167f, 0.26687622f, 0.89395142f, -0.13696289f, + 0.03970337f, -0.00790405f, -0.00210571f, 0.01797485f, -0.07064819f, 0.25683594f, + 0.90036011f, -0.13479614f, 0.03909302f, -0.00784302f, -0.00198364f, 0.01727295f, + -0.06808472f, 0.24691772f, 0.90658569f, -0.13250732f, 0.03848267f, -0.00775146f, + -0.00189209f, 0.01657104f, -0.06555176f, 0.23703003f, 0.91262817f, -0.13006592f, + 0.03781128f, -0.00765991f, -0.00177002f, 0.01586914f, -0.06298828f, 0.22723389f, + 0.91848755f, -0.12750244f, 0.03707886f, -0.00753784f, -0.00167847f, 0.01519775f, + -0.06042480f, 0.21752930f, 0.92413330f, -0.12478638f, 0.03631592f, -0.00744629f, + -0.00158691f, 0.01449585f, -0.05786133f, 0.20791626f, 0.92962646f, -0.12194824f, + 0.03552246f, -0.00732422f, -0.00149536f, 0.01382446f, -0.05532837f, 0.19839478f, + 0.93490601f, -0.11895752f, 0.03466797f, -0.00717163f, -0.00140381f, 0.01315308f, + -0.05279541f, 0.18893433f, 0.94000244f, -0.11584473f, 0.03378296f, -0.00701904f, + -0.00131226f, 0.01248169f, -0.05029297f, 0.17959595f, 0.94491577f, -0.11254883f, + 0.03286743f, -0.00686646f, -0.00122070f, 0.01181030f, -0.04779053f, 0.17034912f, + 0.94961548f, -0.10916138f, 0.03189087f, -0.00671387f, -0.00112915f, 0.01113892f, + -0.04528809f, 0.16119385f, 0.95413208f, -0.10559082f, 0.03085327f, -0.00653076f, + -0.00106812f, 0.01049805f, -0.04281616f, 0.15213013f, 0.95843506f, -0.10189819f, + 0.02981567f, -0.00631714f, -0.00097656f, 0.00985718f, -0.04034424f, 0.14318848f, + 0.96252441f, -0.09805298f, 0.02868652f, -0.00613403f, -0.00091553f, 0.00924683f, + -0.03790283f, 0.13436890f, 0.96643066f, -0.09405518f, 0.02755737f, -0.00592041f, + -0.00082397f, 0.00860596f, -0.03549194f, 0.12561035f, 0.97012329f, -0.08993530f, + 0.02636719f, -0.00567627f, -0.00076294f, 0.00799561f, -0.03308105f, 0.11700439f, + 0.97360229f, -0.08566284f, 0.02511597f, -0.00543213f, -0.00070190f, 0.00738525f, + -0.03073120f, 0.10848999f, 0.97686768f, -0.08123779f, 0.02383423f, -0.00518799f, + -0.00061035f, 0.00680542f, -0.02835083f, 0.10012817f, 0.97991943f, -0.07666016f, + 0.02252197f, -0.00494385f, -0.00054932f, 0.00622559f, -0.02603149f, 0.09185791f, + 0.98278809f, -0.07192993f, 0.02114868f, -0.00463867f, -0.00048828f, 0.00564575f, + -0.02374268f, 0.08370972f, 0.98541260f, -0.06707764f, 0.01971436f, -0.00436401f, + -0.00042725f, 0.00506592f, -0.02145386f, 0.07568359f, 0.98782349f, -0.06207275f, + 0.01828003f, -0.00405884f, -0.00039673f, 0.00451660f, -0.01922607f, 0.06777954f, + 0.99002075f, -0.05691528f, 0.01678467f, -0.00375366f, -0.00033569f, 0.00396729f, + -0.01699829f, 0.05999756f, 0.99200439f, -0.05163574f, 0.01522827f, -0.00341797f, + -0.00027466f, 0.00344849f, -0.01483154f, 0.05233765f, 0.99377441f, -0.04620361f, + 0.01364136f, -0.00308228f, -0.00024414f, 0.00292969f, -0.01266479f, 0.04483032f, + 0.99533081f, -0.04061890f, 0.01202393f, -0.00274658f, -0.00018311f, 0.00241089f, + -0.01055908f, 0.03741455f, 0.99664307f, -0.03488159f, 0.01037598f, -0.00238037f, + -0.00015259f, 0.00192261f, -0.00845337f, 0.03018188f, 0.99774170f, -0.02899170f, + 0.00866699f, -0.00201416f, -0.00009155f, 0.00143433f, -0.00640869f, 0.02304077f, + 0.99862671f, -0.02297974f, 0.00689697f, -0.00161743f, -0.00006104f, 0.00097656f, + -0.00439453f, 0.01605225f, 0.99929810f, -0.01684570f, 0.00512695f, -0.00122070f, + -0.00003052f, 0.00051880f, -0.00241089f, 0.00918579f, 0.99975586f, -0.01052856f, + 0.00329590f, -0.00079346f, 0.00000000f, 0.00006104f, -0.00048828f, 0.00247192f, + 0.99996948f, -0.00408936f, 0.00143433f, -0.00036621f, + }; + + const auto get_lut = [&]() -> std::span<const f32> { + if (sample_rate_ratio <= 1.0f) { + return std::span<const f32>(lut2.data(), lut2.size()); + } else if (sample_rate_ratio < 1.3f) { + return std::span<const f32>(lut1.data(), lut1.size()); + } else { + return std::span<const f32>(lut0.data(), lut0.size()); + } + }; + + auto lut{get_lut()}; + u32 read_index{0}; + for (u32 i = 0; i < samples_to_write; i++) { + const auto lut_index{(fraction.get_frac() >> 8) * 8}; + const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]}; + const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]}; + const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]}; + const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]}; + const Common::FixedPoint<56, 8> sample4{input[read_index + 4] * lut[lut_index + 4]}; + const Common::FixedPoint<56, 8> sample5{input[read_index + 5] * lut[lut_index + 5]}; + const Common::FixedPoint<56, 8> sample6{input[read_index + 6] * lut[lut_index + 6]}; + const Common::FixedPoint<56, 8> sample7{input[read_index + 7] * lut[lut_index + 7]}; + output[i] = (sample0 + sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7) + .to_int_floor(); + fraction += sample_rate_ratio; + read_index += static_cast<u32>(fraction.to_int_floor()); + fraction.clear_int(); + } +} + +void Resample(std::span<s32> output, std::span<const s16> input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write, + const SrcQuality src_quality) { + + switch (src_quality) { + case SrcQuality::Low: + ResampleLowQuality(output, input, sample_rate_ratio, fraction, samples_to_write); + break; + case SrcQuality::Medium: + ResampleNormalQuality(output, input, sample_rate_ratio, fraction, samples_to_write); + break; + case SrcQuality::High: + ResampleHighQuality(output, input, sample_rate_ratio, fraction, samples_to_write); + break; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/resample.h b/src/audio_core/renderer/command/resample/resample.h new file mode 100644 index 000000000..ba9209b82 --- /dev/null +++ b/src/audio_core/renderer/command/resample/resample.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/common/common.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Resample an input buffer into an output buffer, according to the sample_rate_ratio. + * + * @param output - Output buffer. + * @param input - Input buffer. + * @param sample_rate_ratio - Ratio for resampling. + e.g 32000/48000 = 0.666 input samples read per output. + * @param fraction - Current read fraction, written to and should be passed back in for + * multiple calls. + * @param samples_to_write - Number of samples to write. + * @param src_quality - Resampling quality. + */ +void Resample(std::span<s32> output, std::span<const s16> input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/upsample.cpp b/src/audio_core/renderer/command/resample/upsample.cpp new file mode 100644 index 000000000..6c3ff31f7 --- /dev/null +++ b/src/audio_core/renderer/command/resample/upsample.cpp @@ -0,0 +1,262 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <array> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/resample/upsample.h" +#include "audio_core/renderer/upsampler/upsampler_info.h" + +namespace AudioCore::AudioRenderer { +/** + * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K. + * + * @param output - Output buffer. + * @param input - Input buffer. + * @param target_sample_count - Number of samples for output. + * @param state - Upsampler state, updated each call. + */ +static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input, + const u32 target_sample_count, const u32 source_sample_count, + UpsamplerState* state) { + constexpr u32 WindowSize = 10; + constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{ + 51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f, + -1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f, + }; + constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{ + 105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f, + -1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f, + }; + constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{ + 122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f, + -1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f, + }; + constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{ + 23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f, + -0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f, + }; + constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{ + 80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f, + -1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f, + }; + + if (!state->initialized) { + switch (source_sample_count) { + case 40: + state->window_size = WindowSize; + state->ratio = 6.0f; + state->history.fill(0); + break; + + case 80: + state->window_size = WindowSize; + state->ratio = 3.0f; + state->history.fill(0); + break; + + case 160: + state->window_size = WindowSize; + state->ratio = 1.5f; + state->history.fill(0); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count); + // This continues anyway, but let's assume 160 for sanity + state->window_size = WindowSize; + state->ratio = 1.5f; + state->history.fill(0); + break; + } + + state->history_input_index = 0; + state->history_output_index = 9; + state->history_start_index = 0; + state->history_end_index = UpsamplerState::HistorySize - 1; + state->initialized = true; + } + + if (target_sample_count == 0) { + return; + } + + u32 read_index{0}; + + auto increment = [&]() -> void { + state->history[state->history_input_index] = input[read_index++]; + state->history_input_index = + static_cast<u16>((state->history_input_index + 1) % UpsamplerState::HistorySize); + state->history_output_index = + static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize); + }; + + auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1, + std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 { + auto output_index{state->history_output_index}; + auto start_pos{output_index - state->history_start_index + 1U}; + auto end_pos{10U}; + + if (start_pos < 10) { + end_pos = start_pos; + } + + u64 prev_contrib{0}; + u32 coeff_index{0}; + for (; coeff_index < end_pos; coeff_index++, output_index--) { + prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) * + coeffs1[coeff_index].to_raw(); + } + + auto end_index{state->history_end_index}; + for (; start_pos < 9; start_pos++, coeff_index++, end_index--) { + prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) * + coeffs1[coeff_index].to_raw(); + } + + output_index = + static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize); + start_pos = state->history_end_index - output_index + 1U; + end_pos = 10U; + + if (start_pos < 10) { + end_pos = start_pos; + } + + u64 next_contrib{0}; + coeff_index = 0; + for (; coeff_index < end_pos; coeff_index++, output_index++) { + next_contrib += static_cast<u64>(state->history[output_index].to_raw()) * + coeffs2[coeff_index].to_raw(); + } + + auto start_index{state->history_start_index}; + for (; start_pos < 9; start_pos++, start_index++, coeff_index++) { + next_contrib += static_cast<u64>(state->history[start_index].to_raw()) * + coeffs2[coeff_index].to_raw(); + } + + return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8); + }; + + switch (state->ratio.to_int_floor()) { + // 40 -> 240 + case 6: + for (u32 write_index = 0; write_index < target_sample_count; write_index++) { + switch (state->sample_index) { + case 0: + increment(); + output[write_index] = state->history[state->history_output_index].to_int_floor(); + break; + + case 1: + output[write_index] = calculate_sample(SincWindow3, SincWindow4); + break; + + case 2: + output[write_index] = calculate_sample(SincWindow2, SincWindow1); + break; + + case 3: + output[write_index] = calculate_sample(SincWindow5, SincWindow5); + break; + + case 4: + output[write_index] = calculate_sample(SincWindow1, SincWindow2); + break; + + case 5: + output[write_index] = calculate_sample(SincWindow4, SincWindow3); + break; + } + state->sample_index = static_cast<u8>((state->sample_index + 1) % 6); + } + break; + + // 80 -> 240 + case 3: + for (u32 write_index = 0; write_index < target_sample_count; write_index++) { + switch (state->sample_index) { + case 0: + increment(); + output[write_index] = state->history[state->history_output_index].to_int_floor(); + break; + + case 1: + output[write_index] = calculate_sample(SincWindow2, SincWindow1); + break; + + case 2: + output[write_index] = calculate_sample(SincWindow1, SincWindow2); + break; + } + state->sample_index = static_cast<u8>((state->sample_index + 1) % 3); + } + break; + + // 160 -> 240 + default: + for (u32 write_index = 0; write_index < target_sample_count; write_index++) { + switch (state->sample_index) { + case 0: + increment(); + output[write_index] = state->history[state->history_output_index].to_int_floor(); + break; + + case 1: + output[write_index] = calculate_sample(SincWindow1, SincWindow2); + break; + + case 2: + increment(); + output[write_index] = calculate_sample(SincWindow2, SincWindow1); + break; + } + state->sample_index = static_cast<u8>((state->sample_index + 1) % 3); + } + + break; + } +} + +auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) -> void { + string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}", + source_sample_count, source_sample_rate); + const auto upsampler{reinterpret_cast<UpsamplerInfo*>(upsampler_info)}; + if (upsampler != nullptr) { + string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ", + upsampler->enabled, upsampler->sample_count); + for (u32 i = 0; i < upsampler->input_count; i++) { + string += fmt::format("{:02X}, ", upsampler->inputs[i]); + } + } + string += "\n"; +} + +void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) { + const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)}; + const auto input_count{std::min(info->input_count, buffer_count)}; + const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count}; + + for (u32 i = 0; i < input_count; i++) { + const auto channel{inputs_[i]}; + + if (channel >= 0 && channel < static_cast<s16>(processor.buffer_count)) { + auto state{&info->states[i]}; + std::span<s32> output{ + reinterpret_cast<s32*>(samples_buffer + info->sample_count * channel * sizeof(s32)), + info->sample_count}; + auto input{processor.mix_buffers.subspan(channel * processor.sample_count, + processor.sample_count)}; + + SrcProcessFrame(output, input, info->sample_count, source_sample_count, state); + } + } +} + +bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/upsample.h b/src/audio_core/renderer/command/resample/upsample.h new file mode 100644 index 000000000..bfc94e8af --- /dev/null +++ b/src/audio_core/renderer/command/resample/upsample.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for upsampling a mix buffer to 48Khz. + * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz. + */ +struct UpsampleCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Pointer to the output samples buffer. + CpuAddr samples_buffer; + /// Pointer to input mix buffer indexes. + CpuAddr inputs; + /// Number of input mix buffers. + u32 buffer_count; + /// Unknown, unused. + u32 unk_20; + /// Source data sample count. + u32 source_sample_count; + /// Source data sample rate. + u32 source_sample_rate; + /// Pointer to the upsampler info for this command. + CpuAddr upsampler_info; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/circular_buffer.cpp b/src/audio_core/renderer/command/sink/circular_buffer.cpp new file mode 100644 index 000000000..ded5afc94 --- /dev/null +++ b/src/audio_core/renderer/command/sink/circular_buffer.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <vector> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/sink/circular_buffer.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { + +void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ", + input_count, size, pos); + for (u32 i = 0; i < input_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n"; +} + +void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) { + constexpr s32 min{std::numeric_limits<s16>::min()}; + constexpr s32 max{std::numeric_limits<s16>::max()}; + + std::vector<s16> output(processor.sample_count); + for (u32 channel = 0; channel < input_count; channel++) { + auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count, + processor.sample_count)}; + for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) { + output[sample_index] = static_cast<s16>(std::clamp(input[sample_index], min, max)); + } + + processor.memory->WriteBlockUnsafe(address + pos, output.data(), + output.size() * sizeof(s16)); + pos += static_cast<u32>(processor.sample_count * sizeof(s16)); + if (pos >= size) { + pos = 0; + } + } +} + +bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/circular_buffer.h b/src/audio_core/renderer/command/sink/circular_buffer.h new file mode 100644 index 000000000..e7d5be26e --- /dev/null +++ b/src/audio_core/renderer/command/sink/circular_buffer.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for sinking samples to a circular buffer. + */ +struct CircularBufferSinkCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Number of input mix buffers + u32 input_count; + /// Input mix buffer indexes + std::array<s16, MaxChannels> inputs; + /// Circular buffer address + CpuAddr address; + /// Circular buffer size + u32 size; + /// Current buffer offset + u32 pos; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp new file mode 100644 index 000000000..47e0c6722 --- /dev/null +++ b/src/audio_core/renderer/command/sink/device.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/sink/device.h" +#include "audio_core/sink/sink.h" + +namespace AudioCore::AudioRenderer { + +void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ", + std::string_view(name), session_id, input_count); + for (u32 i = 0; i < input_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n"; +} + +void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) { + constexpr s32 min = std::numeric_limits<s16>::min(); + constexpr s32 max = std::numeric_limits<s16>::max(); + + auto stream{processor.GetOutputSinkStream()}; + stream->SetSystemChannels(input_count); + + Sink::SinkBuffer out_buffer{ + .frames{TargetSampleCount}, + .frames_played{0}, + .tag{0}, + .consumed{false}, + }; + + std::vector<s16> samples(out_buffer.frames * input_count); + + for (u32 channel = 0; channel < input_count; channel++) { + const auto offset{inputs[channel] * out_buffer.frames}; + + for (u32 index = 0; index < out_buffer.frames; index++) { + samples[index * input_count + channel] = + static_cast<s16>(std::clamp(sample_buffer[offset + index], min, max)); + } + } + + out_buffer.tag = reinterpret_cast<u64>(samples.data()); + stream->AppendBuffer(out_buffer, samples); +} + +bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/device.h b/src/audio_core/renderer/command/sink/device.h new file mode 100644 index 000000000..1099bcf8c --- /dev/null +++ b/src/audio_core/renderer/command/sink/device.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for sinking samples to an output device. + */ +struct DeviceSinkCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Device name + char name[0x100]; + /// System session id (unused) + s32 session_id; + /// Sample buffer to sink + std::span<s32> sample_buffer; + /// Number of input channels + u32 input_count; + /// Mix buffer indexes for each channel + std::array<s16, MaxChannels> inputs; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/aux_.cpp b/src/audio_core/renderer/effect/aux_.cpp new file mode 100644 index 000000000..51e780ef1 --- /dev/null +++ b/src/audio_core/renderer/effect/aux_.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/aux_.h" + +namespace AudioCore::AudioRenderer { + +void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + if (buffer_unmapped || in_params.is_new) { + const bool send_unmapped{!pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_specific->send_buffer_info_address, + sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))}; + const bool return_unmapped{!pool_mapper.TryAttachBuffer( + error_info, workbuffers[1], in_specific->return_buffer_info_address, + sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))}; + + buffer_unmapped = send_unmapped || return_unmapped; + + if (!buffer_unmapped) { + auto send{workbuffers[0].GetReference(false)}; + send_buffer_info = send + sizeof(AuxInfoDsp); + send_buffer = send + sizeof(AuxBufferInfo); + + auto ret{workbuffers[1].GetReference(false)}; + return_buffer_info = ret + sizeof(AuxInfoDsp); + return_buffer = ret + sizeof(AuxBufferInfo); + } + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion2)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (buffer_unmapped || in_params.is_new) { + const bool send_unmapped{!pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], params->send_buffer_info_address, + sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))}; + const bool return_unmapped{!pool_mapper.TryAttachBuffer( + error_info, workbuffers[1], params->return_buffer_info_address, + sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))}; + + buffer_unmapped = send_unmapped || return_unmapped; + + if (!buffer_unmapped) { + auto send{workbuffers[0].GetReference(false)}; + send_buffer_info = send + sizeof(AuxInfoDsp); + send_buffer = send + sizeof(AuxBufferInfo); + + auto ret{workbuffers[1].GetReference(false)}; + return_buffer_info = ret + sizeof(AuxInfoDsp); + return_buffer = ret + sizeof(AuxBufferInfo); + } + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void AuxInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } +} + +void AuxInfo::InitializeResultState(EffectResultState& result_state) {} + +void AuxInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} + +CpuAddr AuxInfo::GetWorkbuffer(s32 index) { + return workbuffers[index].GetReference(true); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/aux_.h b/src/audio_core/renderer/effect/aux_.h new file mode 100644 index 000000000..4d3d9e3d9 --- /dev/null +++ b/src/audio_core/renderer/effect/aux_.h @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Auxiliary Buffer used for Aux commands. + * Send and return buffers are available (names from the game's perspective). + * Send is read by the host, containing a buffer of samples to be used for whatever purpose. + * Return is written by the host, writing a mix buffer back to the game. + * This allows the game to use pre-processed samples skipping the other render processing, + * and to examine or modify what the audio renderer has generated. + */ +class AuxInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array<s8, MaxMixBuffers> inputs; + /* 0x18 */ std::array<s8, MaxMixBuffers> outputs; + /* 0x30 */ u32 mix_buffer_count; + /* 0x34 */ u32 sample_rate; + /* 0x38 */ u32 count_max; + /* 0x3C */ u32 mix_buffer_count_max; + /* 0x40 */ CpuAddr send_buffer_info_address; + /* 0x48 */ CpuAddr send_buffer_address; + /* 0x50 */ CpuAddr return_buffer_info_address; + /* 0x58 */ CpuAddr return_buffer_address; + /* 0x60 */ u32 mix_buffer_sample_size; + /* 0x64 */ u32 sample_count; + /* 0x68 */ u32 mix_buffer_sample_count; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "AuxInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array<s8, MaxMixBuffers> inputs; + /* 0x18 */ std::array<s8, MaxMixBuffers> outputs; + /* 0x30 */ u32 mix_buffer_count; + /* 0x34 */ u32 sample_rate; + /* 0x38 */ u32 count_max; + /* 0x3C */ u32 mix_buffer_count_max; + /* 0x40 */ CpuAddr send_buffer_info_address; + /* 0x48 */ CpuAddr send_buffer_address; + /* 0x50 */ CpuAddr return_buffer_info_address; + /* 0x58 */ CpuAddr return_buffer_address; + /* 0x60 */ u32 mix_buffer_sample_size; + /* 0x64 */ u32 sample_count; + /* 0x68 */ u32 mix_buffer_sample_count; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "AuxInfo::ParameterVersion2 has the wrong size!"); + + struct AuxInfoDsp { + /* 0x00 */ u32 read_offset; + /* 0x04 */ u32 write_offset; + /* 0x08 */ u32 lost_sample_count; + /* 0x0C */ u32 total_sample_count; + /* 0x10 */ char unk10[0x30]; + }; + static_assert(sizeof(AuxInfoDsp) == 0x40, "AuxInfo::AuxInfoDsp has the wrong size!"); + + struct AuxBufferInfo { + /* 0x00 */ AuxInfoDsp cpu_info; + /* 0x40 */ AuxInfoDsp dsp_info; + }; + static_assert(sizeof(AuxBufferInfo) == 0x80, "AuxInfo::AuxBufferInfo has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/biquad_filter.cpp b/src/audio_core/renderer/effect/biquad_filter.cpp new file mode 100644 index 000000000..a1efb3231 --- /dev/null +++ b/src/audio_core/renderer/effect/biquad_filter.cpp @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/biquad_filter.h" + +namespace AudioCore::AudioRenderer { + +void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion2)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void BiquadFilterInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + params->state = ParameterState::Updated; +} + +void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {} + +void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state, + EffectResultState& dsp_state) {} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/biquad_filter.h b/src/audio_core/renderer/effect/biquad_filter.h new file mode 100644 index 000000000..f53fd5bab --- /dev/null +++ b/src/audio_core/renderer/effect/biquad_filter.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +class BiquadFilterInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ std::array<s16, 3> b; + /* 0x12 */ std::array<s16, 2> a; + /* 0x16 */ s8 channel_count; + /* 0x17 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "BiquadFilterInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ std::array<s16, 3> b; + /* 0x12 */ std::array<s16, 2> a; + /* 0x16 */ s8 channel_count; + /* 0x17 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "BiquadFilterInfo::ParameterVersion2 has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/buffer_mixer.cpp b/src/audio_core/renderer/effect/buffer_mixer.cpp new file mode 100644 index 000000000..9c8877f01 --- /dev/null +++ b/src/audio_core/renderer/effect/buffer_mixer.cpp @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/buffer_mixer.h" + +namespace AudioCore::AudioRenderer { + +void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion2)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void BufferMixerInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } +} + +void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {} + +void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state, + EffectResultState& dsp_state) {} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/buffer_mixer.h b/src/audio_core/renderer/effect/buffer_mixer.h new file mode 100644 index 000000000..23eed4a8b --- /dev/null +++ b/src/audio_core/renderer/effect/buffer_mixer.h @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +class BufferMixerInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array<s8, MaxMixBuffers> inputs; + /* 0x18 */ std::array<s8, MaxMixBuffers> outputs; + /* 0x30 */ std::array<f32, MaxMixBuffers> volumes; + /* 0x90 */ u32 mix_count; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "BufferMixerInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array<s8, MaxMixBuffers> inputs; + /* 0x18 */ std::array<s8, MaxMixBuffers> outputs; + /* 0x30 */ std::array<f32, MaxMixBuffers> volumes; + /* 0x90 */ u32 mix_count; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "BufferMixerInfo::ParameterVersion2 has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/capture.cpp b/src/audio_core/renderer/effect/capture.cpp new file mode 100644 index 000000000..3f038efdb --- /dev/null +++ b/src/audio_core/renderer/effect/capture.cpp @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/aux_.h" +#include "audio_core/renderer/effect/capture.h" + +namespace AudioCore::AudioRenderer { + +void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{ + reinterpret_cast<const AuxInfo::ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<AuxInfo::ParameterVersion1*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + if (buffer_unmapped || in_params.is_new) { + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_specific->send_buffer_info_address, + in_specific->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo)); + + if (!buffer_unmapped) { + const auto send_address{workbuffers[0].GetReference(false)}; + send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp); + send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo); + return_buffer_info = 0; + return_buffer = 0; + } + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{ + reinterpret_cast<const AuxInfo::ParameterVersion2*>(in_params.specific.data())}; + auto params{reinterpret_cast<AuxInfo::ParameterVersion2*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion2)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (buffer_unmapped || in_params.is_new) { + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], params->send_buffer_info_address, + params->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo)); + + if (!buffer_unmapped) { + const auto send_address{workbuffers[0].GetReference(false)}; + send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp); + send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo); + return_buffer_info = 0; + return_buffer = 0; + } + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void CaptureInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } +} + +void CaptureInfo::InitializeResultState(EffectResultState& result_state) {} + +void CaptureInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} + +CpuAddr CaptureInfo::GetWorkbuffer(s32 index) { + return workbuffers[index].GetReference(true); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/capture.h b/src/audio_core/renderer/effect/capture.h new file mode 100644 index 000000000..6fbed8e6b --- /dev/null +++ b/src/audio_core/renderer/effect/capture.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +class CaptureInfo : public EffectInfoBase { +public: + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/compressor.cpp b/src/audio_core/renderer/effect/compressor.cpp new file mode 100644 index 000000000..220ae02f9 --- /dev/null +++ b/src/audio_core/renderer/effect/compressor.cpp @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/compressor.h" + +namespace AudioCore::AudioRenderer { + +void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {} + +void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void CompressorInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + params->state = ParameterState::Updated; +} + +CpuAddr CompressorInfo::GetWorkbuffer(s32 index) { + return GetSingleBuffer(index); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/compressor.h b/src/audio_core/renderer/effect/compressor.h new file mode 100644 index 000000000..019a5ae58 --- /dev/null +++ b/src/audio_core/renderer/effect/compressor.h @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +class CompressorInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ s16 channel_count_max; + /* 0x0E */ s16 channel_count; + /* 0x10 */ s32 sample_rate; + /* 0x14 */ f32 threshold; + /* 0x18 */ f32 compressor_ratio; + /* 0x1C */ s32 attack_time; + /* 0x20 */ s32 release_time; + /* 0x24 */ f32 unk_24; + /* 0x28 */ f32 unk_28; + /* 0x2C */ f32 unk_2C; + /* 0x30 */ f32 out_gain; + /* 0x34 */ ParameterState state; + /* 0x35 */ bool makeup_gain_enabled; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "CompressorInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ s16 channel_count_max; + /* 0x0E */ s16 channel_count; + /* 0x10 */ s32 sample_rate; + /* 0x14 */ f32 threshold; + /* 0x18 */ f32 compressor_ratio; + /* 0x1C */ s32 attack_time; + /* 0x20 */ s32 release_time; + /* 0x24 */ f32 unk_24; + /* 0x28 */ f32 unk_28; + /* 0x2C */ f32 unk_2C; + /* 0x30 */ f32 out_gain; + /* 0x34 */ ParameterState state; + /* 0x35 */ bool makeup_gain_enabled; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "CompressorInfo::ParameterVersion2 has the wrong size!"); + + struct State { + f32 unk_00; + f32 unk_04; + f32 unk_08; + f32 unk_0C; + f32 unk_10; + f32 unk_14; + f32 unk_18; + f32 makeup_gain; + f32 unk_20; + char unk_24[0x1C]; + }; + static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), + "CompressorInfo::State has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/delay.cpp b/src/audio_core/renderer/effect/delay.cpp new file mode 100644 index 000000000..d9853efd9 --- /dev/null +++ b/src/audio_core/renderer/effect/delay.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/delay.h" + +namespace AudioCore::AudioRenderer { + +void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void DelayInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + params->state = ParameterState::Updated; +} + +void DelayInfo::InitializeResultState(EffectResultState& result_state) {} + +void DelayInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} + +CpuAddr DelayInfo::GetWorkbuffer(s32 index) { + return GetSingleBuffer(index); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/delay.h b/src/audio_core/renderer/effect/delay.h new file mode 100644 index 000000000..accc42a06 --- /dev/null +++ b/src/audio_core/renderer/effect/delay.h @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <vector> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +class DelayInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x10 */ u32 delay_time_max; + /* 0x14 */ u32 delay_time; + /* 0x18 */ Common::FixedPoint<18, 14> sample_rate; + /* 0x1C */ Common::FixedPoint<18, 14> in_gain; + /* 0x20 */ Common::FixedPoint<18, 14> feedback_gain; + /* 0x24 */ Common::FixedPoint<18, 14> wet_gain; + /* 0x28 */ Common::FixedPoint<18, 14> dry_gain; + /* 0x2C */ Common::FixedPoint<18, 14> channel_spread; + /* 0x30 */ Common::FixedPoint<18, 14> lowpass_amount; + /* 0x34 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "DelayInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ s16 channel_count_max; + /* 0x0E */ s16 channel_count; + /* 0x10 */ s32 delay_time_max; + /* 0x14 */ s32 delay_time; + /* 0x18 */ s32 sample_rate; + /* 0x1C */ s32 in_gain; + /* 0x20 */ s32 feedback_gain; + /* 0x24 */ s32 wet_gain; + /* 0x28 */ s32 dry_gain; + /* 0x2C */ s32 channel_spread; + /* 0x30 */ s32 lowpass_amount; + /* 0x34 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "DelayInfo::ParameterVersion2 has the wrong size!"); + + struct DelayLine { + Common::FixedPoint<50, 14> Read() const { + return buffer[buffer_pos]; + } + + void Write(const Common::FixedPoint<50, 14> value) { + buffer[buffer_pos] = value; + buffer_pos = static_cast<u32>((buffer_pos + 1) % buffer.size()); + } + + s32 sample_count_max{}; + s32 sample_count{}; + std::vector<Common::FixedPoint<50, 14>> buffer{}; + u32 buffer_pos{}; + Common::FixedPoint<18, 14> decay_rate{}; + }; + + struct State { + /* 0x000 */ std::array<s32, 8> unk_000; + /* 0x020 */ std::array<DelayLine, MaxChannels> delay_lines; + /* 0x0B0 */ Common::FixedPoint<18, 14> feedback_gain; + /* 0x0B4 */ Common::FixedPoint<18, 14> delay_feedback_gain; + /* 0x0B8 */ Common::FixedPoint<18, 14> delay_feedback_cross_gain; + /* 0x0BC */ Common::FixedPoint<18, 14> lowpass_gain; + /* 0x0C0 */ Common::FixedPoint<18, 14> lowpass_feedback_gain; + /* 0x0C4 */ std::array<Common::FixedPoint<50, 14>, MaxChannels> lowpass_z; + }; + static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), + "DelayInfo::State has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/effect_context.cpp b/src/audio_core/renderer/effect/effect_context.cpp new file mode 100644 index 000000000..74c7801c9 --- /dev/null +++ b/src/audio_core/renderer/effect/effect_context.cpp @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/effect_context.h" + +namespace AudioCore::AudioRenderer { + +void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, + std::span<EffectResultState> result_states_cpu_, + std::span<EffectResultState> result_states_dsp_, + const size_t dsp_state_count_) { + effect_infos = effect_infos_; + effect_count = effect_count_; + result_states_cpu = result_states_cpu_; + result_states_dsp = result_states_dsp_; + dsp_state_count = dsp_state_count_; +} + +EffectInfoBase& EffectContext::GetInfo(const u32 index) { + return effect_infos[index]; +} + +EffectResultState& EffectContext::GetResultState(const u32 index) { + return result_states_cpu[index]; +} + +EffectResultState& EffectContext::GetDspSharedResultState(const u32 index) { + return result_states_dsp[index]; +} + +u32 EffectContext::GetCount() const { + return effect_count; +} + +void EffectContext::UpdateStateByDspShared() { + for (size_t i = 0; i < dsp_state_count; i++) { + effect_infos[i].UpdateResultState(result_states_cpu[i], result_states_dsp[i]); + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h new file mode 100644 index 000000000..85955bd9c --- /dev/null +++ b/src/audio_core/renderer/effect/effect_context.h @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/effect/effect_info_base.h" +#include "audio_core/renderer/effect/effect_result_state.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +class EffectContext { +public: + /** + * Initialize the effect context + * @param effect_infos List of effect infos for this context + * @param effect_count The number of effects in the list + * @param result_states_cpu The workbuffer of result states for the CPU for this context + * @param result_states_dsp The workbuffer of result states for the DSP for this context + * @param state_count The number of result states + */ + void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, + std::span<EffectResultState> result_states_cpu_, + std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count); + + /** + * Get the EffectInfo for a given index + * @param index Which effect to return + * @return Pointer to the effect + */ + EffectInfoBase& GetInfo(const u32 index); + + /** + * Get the CPU result state for a given index + * @param index Which result to return + * @return Pointer to the effect result state + */ + EffectResultState& GetResultState(const u32 index); + + /** + * Get the DSP result state for a given index + * @param index Which result to return + * @return Pointer to the effect result state + */ + EffectResultState& GetDspSharedResultState(const u32 index); + + /** + * Get the number of effects in this context + * @return The number of effects + */ + u32 GetCount() const; + + /** + * Update the CPU and DSP result states for all effects + */ + void UpdateStateByDspShared(); + +private: + /// Workbuffer for all of the effects + std::span<EffectInfoBase> effect_infos{}; + /// Number of effects in the workbuffer + u32 effect_count{}; + /// Workbuffer of states for all effects, kept host-side and not directly modified, dsp states + /// are copied here on the next render frame + std::span<EffectResultState> result_states_cpu{}; + /// Workbuffer of states for all effects, used by the AudioRenderer to track effect state + /// between calls + std::span<EffectResultState> result_states_dsp{}; + /// Number of result states in the workbuffers + size_t dsp_state_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h new file mode 100644 index 000000000..43d0589cc --- /dev/null +++ b/src/audio_core/renderer/effect/effect_info_base.h @@ -0,0 +1,435 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/effect/effect_result_state.h" +#include "audio_core/renderer/memory/address_info.h" +#include "audio_core/renderer/memory/pool_mapper.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Base of all effects. Holds various data and functions used for all derived effects. + * Should not be used directly. + */ +class EffectInfoBase { +public: + enum class Type : u8 { + Invalid, + Mix, + Aux, + Delay, + Reverb, + I3dl2Reverb, + BiquadFilter, + LightLimiter, + Capture, + Compressor, + }; + + enum class UsageState { + Invalid, + New, + Enabled, + Disabled, + }; + + enum class OutStatus : u8 { + Invalid, + New, + Initialized, + Used, + Removed, + }; + + enum class ParameterState : u8 { + Initialized, + Updating, + Updated, + }; + + struct InParameterVersion1 { + /* 0x00 */ Type type; + /* 0x01 */ bool is_new; + /* 0x02 */ bool enabled; + /* 0x04 */ u32 mix_id; + /* 0x08 */ CpuAddr workbuffer; + /* 0x10 */ CpuAddr workbuffer_size; + /* 0x18 */ u32 process_order; + /* 0x1C */ char unk1C[0x4]; + /* 0x20 */ std::array<u8, 0xA0> specific; + }; + static_assert(sizeof(InParameterVersion1) == 0xC0, + "EffectInfoBase::InParameterVersion1 has the wrong size!"); + + struct InParameterVersion2 { + /* 0x00 */ Type type; + /* 0x01 */ bool is_new; + /* 0x02 */ bool enabled; + /* 0x04 */ u32 mix_id; + /* 0x08 */ CpuAddr workbuffer; + /* 0x10 */ CpuAddr workbuffer_size; + /* 0x18 */ u32 process_order; + /* 0x1C */ char unk1C[0x4]; + /* 0x20 */ std::array<u8, 0xA0> specific; + }; + static_assert(sizeof(InParameterVersion2) == 0xC0, + "EffectInfoBase::InParameterVersion2 has the wrong size!"); + + struct OutStatusVersion1 { + /* 0x00 */ OutStatus state; + /* 0x01 */ char unk01[0xF]; + }; + static_assert(sizeof(OutStatusVersion1) == 0x10, + "EffectInfoBase::OutStatusVersion1 has the wrong size!"); + + struct OutStatusVersion2 { + /* 0x00 */ OutStatus state; + /* 0x01 */ char unk01[0xF]; + /* 0x10 */ EffectResultState result_state; + }; + static_assert(sizeof(OutStatusVersion2) == 0x90, + "EffectInfoBase::OutStatusVersion2 has the wrong size!"); + + struct State { + std::array<u8, 0x500> buffer; + }; + static_assert(sizeof(State) == 0x500, "EffectInfoBase::State has the wrong size!"); + + EffectInfoBase() { + Cleanup(); + } + + virtual ~EffectInfoBase() = default; + + /** + * Cleanup this effect, resetting it to a starting state. + */ + void Cleanup() { + type = Type::Invalid; + enabled = false; + mix_id = UnusedMixId; + process_order = InvalidProcessOrder; + buffer_unmapped = false; + parameter = {}; + for (auto& workbuffer : workbuffers) { + workbuffer.Setup(CpuAddr(0), 0); + } + } + + /** + * Forcibly unmap all assigned workbuffers from the AudioRenderer. + * + * @param pool_mapper - Mapper to unmap the buffers. + */ + void ForceUnmapBuffers(const PoolMapper& pool_mapper) { + for (auto& workbuffer : workbuffers) { + if (workbuffer.GetReference(false) != 0) { + pool_mapper.ForceUnmapPointer(workbuffer); + } + } + } + + /** + * Check if this effect is enabled. + * + * @return True if effect is enabled, otherwise false. + */ + bool IsEnabled() const { + return enabled; + } + + /** + * Check if this effect should not be generated. + * + * @return True if effect should be skipped, otherwise false. + */ + bool ShouldSkip() const { + return buffer_unmapped; + } + + /** + * Get the type of this effect. + * + * @return The type of this effect. See EffectInfoBase::Type + */ + Type GetType() const { + return type; + } + + /** + * Set the type of this effect. + * + * @param type_ - The new type of this effect. + */ + void SetType(const Type type_) { + type = type_; + } + + /** + * Get the mix id of this effect. + * + * @return Mix id of this effect. + */ + s32 GetMixId() const { + return mix_id; + } + + /** + * Get the processing order of this effect. + * + * @return Process order of this effect. + */ + s32 GetProcessingOrder() const { + return process_order; + } + + /** + * Get this effect's parameter data. + * + * @return Pointer to the parametter, must be cast to the correct type. + */ + u8* GetParameter() { + return parameter.data(); + } + + /** + * Get this effect's parameter data. + * + * @return Pointer to the parametter, must be cast to the correct type. + */ + u8* GetStateBuffer() { + return state.data(); + } + + /** + * Set this effect's usage state. + * + * @param usage - new usage state of this effect. + */ + void SetUsage(const UsageState usage) { + usage_state = usage; + } + + /** + * Check if this effects need to have its workbuffer information updated. + * Version 1. + * + * @param params - Input parameters. + * @return True if workbuffers need updating, otherwise false. + */ + bool ShouldUpdateWorkBufferInfo(const InParameterVersion1& params) const { + return buffer_unmapped || params.is_new; + } + + /** + * Check if this effects need to have its workbuffer information updated. + * Version 2. + * + * @param params - Input parameters. + * @return True if workbuffers need updating, otherwise false. + */ + bool ShouldUpdateWorkBufferInfo(const InParameterVersion2& params) const { + return buffer_unmapped || params.is_new; + } + + /** + * Get the current usage state of this effect. + * + * @return The current usage state. + */ + UsageState GetUsage() const { + return usage_state; + } + + /** + * Write the current state. Version 1. + * + * @param out_status - Status to write. + * @param renderer_active - Is the AudioRenderer active? + */ + void StoreStatus(OutStatusVersion1& out_status, const bool renderer_active) const { + if (renderer_active) { + if (usage_state != UsageState::Disabled) { + out_status.state = OutStatus::Used; + } else { + out_status.state = OutStatus::Removed; + } + } else if (usage_state == UsageState::New) { + out_status.state = OutStatus::Used; + } else { + out_status.state = OutStatus::Removed; + } + } + + /** + * Write the current state. Version 2. + * + * @param out_status - Status to write. + * @param renderer_active - Is the AudioRenderer active? + */ + void StoreStatus(OutStatusVersion2& out_status, const bool renderer_active) const { + if (renderer_active) { + if (usage_state != UsageState::Disabled) { + out_status.state = OutStatus::Used; + } else { + out_status.state = OutStatus::Removed; + } + } else if (usage_state == UsageState::New) { + out_status.state = OutStatus::Used; + } else { + out_status.state = OutStatus::Removed; + } + } + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + virtual void Update(BehaviorInfo::ErrorInfo& error_info, + [[maybe_unused]] const InParameterVersion1& params, + [[maybe_unused]] const PoolMapper& pool_mapper) { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + virtual void Update(BehaviorInfo::ErrorInfo& error_info, + [[maybe_unused]] const InParameterVersion2& params, + [[maybe_unused]] const PoolMapper& pool_mapper) { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } + + /** + * Update the info after command generation. Usually only changes its state. + */ + virtual void UpdateForCommandGeneration() {} + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + virtual void InitializeResultState([[maybe_unused]] EffectResultState& result_state) {} + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + virtual void UpdateResultState([[maybe_unused]] EffectResultState& cpu_state, + [[maybe_unused]] EffectResultState& dsp_state) {} + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + virtual CpuAddr GetWorkbuffer([[maybe_unused]] s32 index) { + return 0; + } + + /** + * Get the first workbuffer assigned to this effect. + * + * @param index - Workbuffer index. Unused. + * @return Address of the buffer. + */ + CpuAddr GetSingleBuffer([[maybe_unused]] const s32 index) { + if (enabled) { + return workbuffers[0].GetReference(true); + } + + if (usage_state != UsageState::Disabled) { + const auto ref{workbuffers[0].GetReference(false)}; + const auto size{workbuffers[0].GetSize()}; + if (ref != 0 && size > 0) { + // Invalidate DSP cache + } + } + return 0; + } + + /** + * Get the send buffer info, used by Aux and Capture. + * + * @return Address of the buffer info. + */ + CpuAddr GetSendBufferInfo() const { + return send_buffer_info; + } + + /** + * Get the send buffer, used by Aux and Capture. + * + * @return Address of the buffer. + */ + CpuAddr GetSendBuffer() const { + return send_buffer; + } + + /** + * Get the return buffer info, used by Aux and Capture. + * + * @return Address of the buffer info. + */ + CpuAddr GetReturnBufferInfo() const { + return return_buffer_info; + } + + /** + * Get the return buffer, used by Aux and Capture. + * + * @return Address of the buffer. + */ + CpuAddr GetReturnBuffer() const { + return return_buffer; + } + +protected: + /// Type of this effect. May be changed + Type type{Type::Invalid}; + /// Is this effect enabled? + bool enabled{}; + /// Are this effect's buffers unmapped? + bool buffer_unmapped{}; + /// Current usage state + UsageState usage_state{UsageState::Invalid}; + /// Mix id of this effect + s32 mix_id{UnusedMixId}; + /// Process order of this effect + s32 process_order{InvalidProcessOrder}; + /// Workbuffers assigned to this effect + std::array<AddressInfo, 2> workbuffers{AddressInfo(CpuAddr(0), 0), AddressInfo(CpuAddr(0), 0)}; + /// Aux/Capture buffer info for reading + CpuAddr send_buffer_info; + /// Aux/Capture buffer for reading + CpuAddr send_buffer; + /// Aux/Capture buffer info for writing + CpuAddr return_buffer_info; + /// Aux/Capture buffer for writing + CpuAddr return_buffer; + /// Parameters of this effect + std::array<u8, sizeof(InParameterVersion2)> parameter{}; + /// State of this effect used by the AudioRenderer across calls + std::array<u8, sizeof(State)> state{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/effect_reset.h b/src/audio_core/renderer/effect/effect_reset.h new file mode 100644 index 000000000..1ea67e334 --- /dev/null +++ b/src/audio_core/renderer/effect/effect_reset.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/effect/aux_.h" +#include "audio_core/renderer/effect/biquad_filter.h" +#include "audio_core/renderer/effect/buffer_mixer.h" +#include "audio_core/renderer/effect/capture.h" +#include "audio_core/renderer/effect/compressor.h" +#include "audio_core/renderer/effect/delay.h" +#include "audio_core/renderer/effect/i3dl2.h" +#include "audio_core/renderer/effect/light_limiter.h" +#include "audio_core/renderer/effect/reverb.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Reset an effect, and create a new one of the given type. + * + * @param effect - Effect to reset and re-construct. + * @param type - Type of the new effect to create. + */ +static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type) { + *effect = {}; + + switch (type) { + case EffectInfoBase::Type::Invalid: + std::construct_at<EffectInfoBase>(effect); + effect->SetType(EffectInfoBase::Type::Invalid); + break; + case EffectInfoBase::Type::Mix: + std::construct_at<BufferMixerInfo>(reinterpret_cast<BufferMixerInfo*>(effect)); + effect->SetType(EffectInfoBase::Type::Mix); + break; + case EffectInfoBase::Type::Aux: + std::construct_at<AuxInfo>(reinterpret_cast<AuxInfo*>(effect)); + effect->SetType(EffectInfoBase::Type::Aux); + break; + case EffectInfoBase::Type::Delay: + std::construct_at<DelayInfo>(reinterpret_cast<DelayInfo*>(effect)); + effect->SetType(EffectInfoBase::Type::Delay); + break; + case EffectInfoBase::Type::Reverb: + std::construct_at<ReverbInfo>(reinterpret_cast<ReverbInfo*>(effect)); + effect->SetType(EffectInfoBase::Type::Reverb); + break; + case EffectInfoBase::Type::I3dl2Reverb: + std::construct_at<I3dl2ReverbInfo>(reinterpret_cast<I3dl2ReverbInfo*>(effect)); + effect->SetType(EffectInfoBase::Type::I3dl2Reverb); + break; + case EffectInfoBase::Type::BiquadFilter: + std::construct_at<BiquadFilterInfo>(reinterpret_cast<BiquadFilterInfo*>(effect)); + effect->SetType(EffectInfoBase::Type::BiquadFilter); + break; + case EffectInfoBase::Type::LightLimiter: + std::construct_at<LightLimiterInfo>(reinterpret_cast<LightLimiterInfo*>(effect)); + effect->SetType(EffectInfoBase::Type::LightLimiter); + break; + case EffectInfoBase::Type::Capture: + std::construct_at<CaptureInfo>(reinterpret_cast<CaptureInfo*>(effect)); + effect->SetType(EffectInfoBase::Type::Capture); + break; + case EffectInfoBase::Type::Compressor: + std::construct_at<CompressorInfo>(reinterpret_cast<CompressorInfo*>(effect)); + effect->SetType(EffectInfoBase::Type::Compressor); + break; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/effect_result_state.h b/src/audio_core/renderer/effect/effect_result_state.h new file mode 100644 index 000000000..ae096ad69 --- /dev/null +++ b/src/audio_core/renderer/effect/effect_result_state.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +struct EffectResultState { + std::array<u8, 0x80> state; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/i3dl2.cpp b/src/audio_core/renderer/effect/i3dl2.cpp new file mode 100644 index 000000000..960b29cfc --- /dev/null +++ b/src/audio_core/renderer/effect/i3dl2.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/i3dl2.h" + +namespace AudioCore::AudioRenderer { + +void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void I3dl2ReverbInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + params->state = ParameterState::Updated; +} + +void I3dl2ReverbInfo::InitializeResultState(EffectResultState& result_state) {} + +void I3dl2ReverbInfo::UpdateResultState(EffectResultState& cpu_state, + EffectResultState& dsp_state) {} + +CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) { + return GetSingleBuffer(index); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h new file mode 100644 index 000000000..7a088a627 --- /dev/null +++ b/src/audio_core/renderer/effect/i3dl2.h @@ -0,0 +1,200 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <vector> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +class I3dl2ReverbInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x10 */ char unk10[0x4]; + /* 0x14 */ u32 sample_rate; + /* 0x18 */ f32 room_HF_gain; + /* 0x1C */ f32 reference_HF; + /* 0x20 */ f32 late_reverb_decay_time; + /* 0x24 */ f32 late_reverb_HF_decay_ratio; + /* 0x28 */ f32 room_gain; + /* 0x2C */ f32 reflection_gain; + /* 0x30 */ f32 reverb_gain; + /* 0x34 */ f32 late_reverb_diffusion; + /* 0x38 */ f32 reflection_delay; + /* 0x3C */ f32 late_reverb_delay_time; + /* 0x40 */ f32 late_reverb_density; + /* 0x44 */ f32 dry_gain; + /* 0x48 */ ParameterState state; + /* 0x49 */ char unk49[0x3]; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "I3dl2ReverbInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x10 */ char unk10[0x4]; + /* 0x14 */ u32 sample_rate; + /* 0x18 */ f32 room_HF_gain; + /* 0x1C */ f32 reference_HF; + /* 0x20 */ f32 late_reverb_decay_time; + /* 0x24 */ f32 late_reverb_HF_decay_ratio; + /* 0x28 */ f32 room_gain; + /* 0x2C */ f32 reflection_gain; + /* 0x30 */ f32 reverb_gain; + /* 0x34 */ f32 late_reverb_diffusion; + /* 0x38 */ f32 reflection_delay; + /* 0x3C */ f32 late_reverb_delay_time; + /* 0x40 */ f32 late_reverb_density; + /* 0x44 */ f32 dry_gain; + /* 0x48 */ ParameterState state; + /* 0x49 */ char unk49[0x3]; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "I3dl2ReverbInfo::ParameterVersion2 has the wrong size!"); + + static constexpr u32 MaxDelayLines = 4; + static constexpr u32 MaxDelayTaps = 20; + + struct I3dl2DelayLine { + void Initialize(const s32 delay_time) { + max_delay = delay_time; + buffer.resize(delay_time + 1, 0); + buffer_end = &buffer[delay_time]; + output = &buffer[0]; + SetDelay(delay_time); + wet_gain = 0.0f; + } + + void SetDelay(const s32 delay_time) { + if (max_delay < delay_time) { + return; + } + delay = delay_time; + input = &buffer[(output - buffer.data() + delay) % (max_delay + 1)]; + } + + Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) { + Write(sample); + + auto out_sample{Read()}; + + output++; + if (output >= buffer_end) { + output = buffer.data(); + } + + return out_sample; + } + + Common::FixedPoint<50, 14> Read() { + return *output; + } + + void Write(const Common::FixedPoint<50, 14> sample) { + *(input++) = sample; + if (input >= buffer_end) { + input = buffer.data(); + } + } + + Common::FixedPoint<50, 14> TapOut(const s32 index) { + auto out{input - (index + 1)}; + if (out < buffer.data()) { + out += max_delay + 1; + } + return *out; + } + + std::vector<Common::FixedPoint<50, 14>> buffer{}; + Common::FixedPoint<50, 14>* buffer_end{}; + s32 max_delay{}; + Common::FixedPoint<50, 14>* input{}; + Common::FixedPoint<50, 14>* output{}; + s32 delay{}; + f32 wet_gain{}; + }; + + struct State { + f32 lowpass_0; + f32 lowpass_1; + f32 lowpass_2; + I3dl2DelayLine early_delay_line; + std::array<s32, MaxDelayTaps> early_tap_steps; + f32 early_gain; + f32 late_gain; + s32 early_to_late_taps; + std::array<I3dl2DelayLine, MaxDelayLines> fdn_delay_lines; + std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines0; + std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines1; + f32 last_reverb_echo; + I3dl2DelayLine center_delay_line; + std::array<std::array<f32, 3>, MaxDelayLines> lowpass_coeff; + std::array<f32, MaxDelayLines> shelf_filter; + f32 dry_gain; + }; + static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), + "I3dl2ReverbInfo::State is too large!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/light_limiter.cpp b/src/audio_core/renderer/effect/light_limiter.cpp new file mode 100644 index 000000000..1635a952d --- /dev/null +++ b/src/audio_core/renderer/effect/light_limiter.cpp @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/light_limiter.h" + +namespace AudioCore::AudioRenderer { + +void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void LightLimiterInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + params->state = ParameterState::Updated; + params->statistics_reset_required = false; +} + +void LightLimiterInfo::InitializeResultState(EffectResultState& result_state) { + auto result_state_{reinterpret_cast<StatisticsInternal*>(result_state.state.data())}; + + result_state_->channel_max_sample.fill(0); + result_state_->channel_compression_gain_min.fill(1.0f); +} + +void LightLimiterInfo::UpdateResultState(EffectResultState& cpu_state, + EffectResultState& dsp_state) { + auto cpu_statistics{reinterpret_cast<StatisticsInternal*>(cpu_state.state.data())}; + auto dsp_statistics{reinterpret_cast<StatisticsInternal*>(dsp_state.state.data())}; + + *cpu_statistics = *dsp_statistics; +} + +CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) { + return GetSingleBuffer(index); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/light_limiter.h b/src/audio_core/renderer/effect/light_limiter.h new file mode 100644 index 000000000..338d67bbc --- /dev/null +++ b/src/audio_core/renderer/effect/light_limiter.h @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <vector> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +class LightLimiterInfo : public EffectInfoBase { +public: + enum class ProcessingMode { + Mode0, + Mode1, + }; + + struct ParameterVersion1 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x0C */ u32 sample_rate; + /* 0x14 */ s32 look_ahead_time_max; + /* 0x18 */ s32 attack_time; + /* 0x1C */ s32 release_time; + /* 0x20 */ s32 look_ahead_time; + /* 0x24 */ f32 attack_coeff; + /* 0x28 */ f32 release_coeff; + /* 0x2C */ f32 threshold; + /* 0x30 */ f32 input_gain; + /* 0x34 */ f32 output_gain; + /* 0x38 */ s32 look_ahead_samples_min; + /* 0x3C */ s32 look_ahead_samples_max; + /* 0x40 */ ParameterState state; + /* 0x41 */ bool statistics_enabled; + /* 0x42 */ bool statistics_reset_required; + /* 0x43 */ ProcessingMode processing_mode; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "LightLimiterInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x0C */ u32 sample_rate; + /* 0x14 */ s32 look_ahead_time_max; + /* 0x18 */ s32 attack_time; + /* 0x1C */ s32 release_time; + /* 0x20 */ s32 look_ahead_time; + /* 0x24 */ f32 attack_coeff; + /* 0x28 */ f32 release_coeff; + /* 0x2C */ f32 threshold; + /* 0x30 */ f32 input_gain; + /* 0x34 */ f32 output_gain; + /* 0x38 */ s32 look_ahead_samples_min; + /* 0x3C */ s32 look_ahead_samples_max; + /* 0x40 */ ParameterState state; + /* 0x41 */ bool statistics_enabled; + /* 0x42 */ bool statistics_reset_required; + /* 0x43 */ ProcessingMode processing_mode; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "LightLimiterInfo::ParameterVersion2 has the wrong size!"); + + struct State { + std::array<Common::FixedPoint<49, 15>, MaxChannels> samples_average; + std::array<Common::FixedPoint<49, 15>, MaxChannels> compression_gain; + std::array<s32, MaxChannels> look_ahead_sample_offsets; + std::array<std::vector<Common::FixedPoint<49, 15>>, MaxChannels> look_ahead_sample_buffers; + }; + static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), + "LightLimiterInfo::State has the wrong size!"); + + struct StatisticsInternal { + /* 0x00 */ std::array<f32, MaxChannels> channel_max_sample; + /* 0x18 */ std::array<f32, MaxChannels> channel_compression_gain_min; + }; + static_assert(sizeof(StatisticsInternal) == 0x30, + "LightLimiterInfo::StatisticsInternal has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new limiter statistics result state. Version 2 only. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side limiter statistics with the ADSP-side one. Version 2 only. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/reverb.cpp b/src/audio_core/renderer/effect/reverb.cpp new file mode 100644 index 000000000..2d32383d0 --- /dev/null +++ b/src/audio_core/renderer/effect/reverb.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/reverb.h" + +namespace AudioCore::AudioRenderer { + +void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())}; + auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion2)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void ReverbInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; + params->state = ParameterState::Updated; +} + +void ReverbInfo::InitializeResultState(EffectResultState& result_state) {} + +void ReverbInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} + +CpuAddr ReverbInfo::GetWorkbuffer(s32 index) { + return GetSingleBuffer(index); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h new file mode 100644 index 000000000..b4df9f6ef --- /dev/null +++ b/src/audio_core/renderer/effect/reverb.h @@ -0,0 +1,190 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <vector> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +class ReverbInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x10 */ u32 sample_rate; + /* 0x14 */ u32 early_mode; + /* 0x18 */ s32 early_gain; + /* 0x1C */ s32 pre_delay; + /* 0x20 */ s32 late_mode; + /* 0x24 */ s32 late_gain; + /* 0x28 */ s32 decay_time; + /* 0x2C */ s32 high_freq_Decay_ratio; + /* 0x30 */ s32 colouration; + /* 0x34 */ s32 base_gain; + /* 0x38 */ s32 wet_gain; + /* 0x3C */ s32 dry_gain; + /* 0x40 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "ReverbInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array<s8, MaxChannels> inputs; + /* 0x06 */ std::array<s8, MaxChannels> outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x10 */ u32 sample_rate; + /* 0x14 */ u32 early_mode; + /* 0x18 */ s32 early_gain; + /* 0x1C */ s32 pre_delay; + /* 0x20 */ s32 late_mode; + /* 0x24 */ s32 late_gain; + /* 0x28 */ s32 decay_time; + /* 0x2C */ s32 high_freq_decay_ratio; + /* 0x30 */ s32 colouration; + /* 0x34 */ s32 base_gain; + /* 0x38 */ s32 wet_gain; + /* 0x3C */ s32 dry_gain; + /* 0x40 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "ReverbInfo::ParameterVersion2 has the wrong size!"); + + static constexpr u32 MaxDelayLines = 4; + static constexpr u32 MaxDelayTaps = 10; + static constexpr u32 NumEarlyModes = 5; + static constexpr u32 NumLateModes = 5; + + struct ReverbDelayLine { + void Initialize(const s32 delay_time, const f32 decay_rate) { + buffer.resize(delay_time + 1, 0); + buffer_end = &buffer[delay_time]; + output = &buffer[0]; + decay = decay_rate; + sample_count_max = delay_time; + SetDelay(delay_time); + } + + void SetDelay(const s32 delay_time) { + if (sample_count_max < delay_time) { + return; + } + sample_count = delay_time; + input = &buffer[(output - buffer.data() + sample_count) % (sample_count_max + 1)]; + } + + Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) { + Write(sample); + + auto out_sample{Read()}; + + output++; + if (output >= buffer_end) { + output = buffer.data(); + } + + return out_sample; + } + + Common::FixedPoint<50, 14> Read() { + return *output; + } + + void Write(const Common::FixedPoint<50, 14> sample) { + *(input++) = sample; + if (input >= buffer_end) { + input = buffer.data(); + } + } + + Common::FixedPoint<50, 14> TapOut(const s32 index) { + auto out{input - (index + 1)}; + if (out < buffer.data()) { + out += sample_count; + } + return *out; + } + + s32 sample_count{}; + s32 sample_count_max{}; + std::vector<Common::FixedPoint<50, 14>> buffer{}; + Common::FixedPoint<50, 14>* buffer_end; + Common::FixedPoint<50, 14>* input{}; + Common::FixedPoint<50, 14>* output{}; + Common::FixedPoint<50, 14> decay{}; + }; + + struct State { + ReverbDelayLine pre_delay_line; + ReverbDelayLine center_delay_line; + std::array<s32, MaxDelayTaps> early_delay_times; + std::array<Common::FixedPoint<50, 14>, MaxDelayTaps> early_gains; + s32 pre_delay_time; + std::array<ReverbDelayLine, MaxDelayLines> decay_delay_lines; + std::array<ReverbDelayLine, MaxDelayLines> fdn_delay_lines; + std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_gain; + std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_prev_gain; + std::array<Common::FixedPoint<50, 14>, MaxDelayLines> prev_feedback_output; + }; + static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), + "ReverbInfo::State is too large!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h new file mode 100644 index 000000000..4cfefea8e --- /dev/null +++ b/src/audio_core/renderer/memory/address_info.h @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +/** + * Represents a region of mapped or unmapped memory. + */ +class AddressInfo { +public: + AddressInfo() = default; + AddressInfo(CpuAddr cpu_address_, u64 size_) : cpu_address{cpu_address_}, size{size_} {} + + /** + * Setup a new AddressInfo. + * + * @param cpu_address - The CPU address of this region. + * @param size - The size of this region. + */ + void Setup(CpuAddr cpu_address_, u64 size_) { + cpu_address = cpu_address_; + size = size_; + memory_pool = nullptr; + dsp_address = 0; + } + + /** + * Get the CPU address. + * + * @return The CpuAddr address + */ + CpuAddr GetCpuAddr() const { + return cpu_address; + } + + /** + * Assign this region to a memory pool. + * + * @param memory_pool_ - Memory pool to assign. + * @return The CpuAddr address of this region. + */ + void SetPool(MemoryPoolInfo* memory_pool_) { + memory_pool = memory_pool_; + } + + /** + * Get the size of this region. + * + * @return The size of this region. + */ + u64 GetSize() const { + return size; + } + + /** + * Get the ADSP address for this region. + * + * @return The ADSP address for this region. + */ + CpuAddr GetForceMappedDspAddr() const { + return dsp_address; + } + + /** + * Set the ADSP address for this region. + * + * @param dsp_addr - The new ADSP address for this region. + */ + void SetForceMappedDspAddr(CpuAddr dsp_addr) { + dsp_address = dsp_addr; + } + + /** + * Check whether this region has an active memory pool. + * + * @return True if this region has a mapped memory pool, otherwise false. + */ + bool HasMappedMemoryPool() const { + return memory_pool != nullptr && memory_pool->GetDspAddress() != 0; + } + + /** + * Check whether this region is mapped to the ADSP. + * + * @return True if this region is mapped, otherwise false. + */ + bool IsMapped() const { + return HasMappedMemoryPool() || dsp_address != 0; + } + + /** + * Get a usable reference to this region of memory. + * + * @param mark_in_use - Whether this region should be marked as being in use. + * @return A valid memory address if valid, otherwise 0. + */ + CpuAddr GetReference(bool mark_in_use) { + if (!HasMappedMemoryPool()) { + return dsp_address; + } + + if (mark_in_use) { + memory_pool->SetUsed(true); + } + + return memory_pool->Translate(cpu_address, size); + } + +private: + /// CPU address of this region + CpuAddr cpu_address; + /// Size of this region + u64 size; + /// The memory this region is mapped to + MemoryPoolInfo* memory_pool; + /// ADSP address of this region + CpuAddr dsp_address; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/memory/memory_pool_info.cpp b/src/audio_core/renderer/memory/memory_pool_info.cpp new file mode 100644 index 000000000..9b7824af1 --- /dev/null +++ b/src/audio_core/renderer/memory/memory_pool_info.cpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/memory/memory_pool_info.h" + +namespace AudioCore::AudioRenderer { + +CpuAddr MemoryPoolInfo::GetCpuAddress() const { + return cpu_address; +} + +CpuAddr MemoryPoolInfo::GetDspAddress() const { + return dsp_address; +} + +u64 MemoryPoolInfo::GetSize() const { + return size; +} + +MemoryPoolInfo::Location MemoryPoolInfo::GetLocation() const { + return location; +} + +void MemoryPoolInfo::SetCpuAddress(const CpuAddr address, const u64 size_) { + cpu_address = address; + size = size_; +} + +void MemoryPoolInfo::SetDspAddress(const CpuAddr address) { + dsp_address = address; +} + +bool MemoryPoolInfo::Contains(const CpuAddr address_, const u64 size_) const { + return cpu_address <= address_ && (address_ + size_) <= (cpu_address + size); +} + +bool MemoryPoolInfo::IsMapped() const { + return dsp_address != 0; +} + +CpuAddr MemoryPoolInfo::Translate(const CpuAddr address, const u64 size_) const { + if (!Contains(address, size_)) { + return 0; + } + + if (!IsMapped()) { + return 0; + } + + return dsp_address + (address - cpu_address); +} + +void MemoryPoolInfo::SetUsed(const bool used) { + in_use = used; +} + +bool MemoryPoolInfo::IsUsed() const { + return in_use; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/memory/memory_pool_info.h b/src/audio_core/renderer/memory/memory_pool_info.h new file mode 100644 index 000000000..537a466ec --- /dev/null +++ b/src/audio_core/renderer/memory/memory_pool_info.h @@ -0,0 +1,170 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <memory> + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper). + */ +class MemoryPoolInfo { +public: + /** + * The location of this pool. + * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper). + * DSP pools are mapped in the current process sysmodule. + */ + enum class Location { + CPU = 1, + DSP = 2, + }; + + /** + * Current state of the pool + */ + enum class State { + Invalid, + Aquired, + RequestDetach, + Detached, + RequestAttach, + Attached, + Released, + }; + + /** + * Result code for updating the pool (See InfoUpdater::Update) + */ + enum class ResultState { + Success, + BadParam, + MapFailed, + InUse, + }; + + /** + * Input parameters coming from the game which are used to update current pools + * (See InfoUpdater::Update) + */ + struct InParameter { + /* 0x00 */ u64 address; + /* 0x08 */ u64 size; + /* 0x10 */ State state; + /* 0x14 */ bool in_use; + /* 0x18 */ char unk18[0x8]; + }; + static_assert(sizeof(InParameter) == 0x20, "MemoryPoolInfo::InParameter has the wrong size!"); + + /** + * Output status sent back to the game on update (See InfoUpdater::Update) + */ + struct OutStatus { + /* 0x00 */ State state; + /* 0x04 */ char unk04[0xC]; + }; + static_assert(sizeof(OutStatus) == 0x10, "MemoryPoolInfo::OutStatus has the wrong size!"); + + MemoryPoolInfo() = default; + MemoryPoolInfo(Location location_) : location{location_} {} + + /** + * Get the CPU address for this pool. + * + * @return The CPU address of this pool. + */ + CpuAddr GetCpuAddress() const; + + /** + * Get the DSP address for this pool. + * + * @return The DSP address of this pool. + */ + CpuAddr GetDspAddress() const; + + /** + * Get the size of this pool. + * + * @return The size of this pool. + */ + u64 GetSize() const; + + /** + * Get the location of this pool. + * + * @return The location for the pool (see MemoryPoolInfo::Location). + */ + Location GetLocation() const; + + /** + * Set the CPU address for this pool. + * + * @param address - The new CPU address for this pool. + * @param size - The new size for this pool. + */ + void SetCpuAddress(CpuAddr address, u64 size); + + /** + * Set the DSP address for this pool. + * + * @param address - The new DSP address for this pool. + */ + void SetDspAddress(CpuAddr address); + + /** + * Check whether the pool contains a given range. + * + * @param address - The buffer address to look for. + * @param size - The size of the given buffer. + * @return True if the range is within this pool, otherwise false. + */ + bool Contains(CpuAddr address, u64 size) const; + + /** + * Check whether this pool is mapped, which is when the dsp address is set. + * + * @return True if the pool is mapped, otherwise false. + */ + bool IsMapped() const; + + /** + * Translates a given CPU range into a relative offset for the DSP. + * + * @param address - The buffer address to look for. + * @param size - The size of the given buffer. + * @return Pointer to the DSP-mapped memory. + */ + CpuAddr Translate(CpuAddr address, u64 size) const; + + /** + * Set or unset whether this memory pool is in use. + * + * @param used - Use state for this pool. + */ + void SetUsed(bool used); + + /** + * Get whether this pool is in use. + * + * @return True if in use, otherwise false. + */ + bool IsUsed() const; + +private: + /// Base address for the CPU-side memory + CpuAddr cpu_address{}; + /// Base address for the DSP-side memory + CpuAddr dsp_address{}; + /// Size of this pool + u64 size{}; + /// Location of this pool, either CPU or DSP + Location location{Location::DSP}; + /// If this pool is in use + bool in_use{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/memory/pool_mapper.cpp b/src/audio_core/renderer/memory/pool_mapper.cpp new file mode 100644 index 000000000..2baf2ce08 --- /dev/null +++ b/src/audio_core/renderer/memory/pool_mapper.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/memory/address_info.h" +#include "audio_core/renderer/memory/pool_mapper.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace AudioCore::AudioRenderer { + +PoolMapper::PoolMapper(u32 process_handle_, bool force_map_) + : process_handle{process_handle_}, force_map{force_map_} {} + +PoolMapper::PoolMapper(u32 process_handle_, std::span<MemoryPoolInfo> pool_infos_, u32 pool_count_, + bool force_map_) + : process_handle{process_handle_}, pool_infos{pool_infos_.data()}, + pool_count{pool_count_}, force_map{force_map_} {} + +void PoolMapper::ClearUseState(std::span<MemoryPoolInfo> pools, const u32 count) { + for (u32 i = 0; i < count; i++) { + pools[i].SetUsed(false); + } +} + +MemoryPoolInfo* PoolMapper::FindMemoryPool(MemoryPoolInfo* pools, const u64 count, + const CpuAddr address, const u64 size) const { + auto pool{pools}; + for (u64 i = 0; i < count; i++, pool++) { + if (pool->Contains(address, size)) { + return pool; + } + } + return nullptr; +} + +MemoryPoolInfo* PoolMapper::FindMemoryPool(const CpuAddr address, const u64 size) const { + auto pool{pool_infos}; + for (u64 i = 0; i < pool_count; i++, pool++) { + if (pool->Contains(address, size)) { + return pool; + } + } + return nullptr; +} + +bool PoolMapper::FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools, + const u32 count) const { + if (address_info.GetCpuAddr() == 0) { + address_info.SetPool(nullptr); + return false; + } + + auto found_pool{ + FindMemoryPool(pools, count, address_info.GetCpuAddr(), address_info.GetSize())}; + if (found_pool != nullptr) { + address_info.SetPool(found_pool); + return true; + } + + if (force_map) { + address_info.SetForceMappedDspAddr(address_info.GetCpuAddr()); + } else { + address_info.SetPool(nullptr); + } + + return false; +} + +bool PoolMapper::FillDspAddr(AddressInfo& address_info) const { + if (address_info.GetCpuAddr() == 0) { + address_info.SetPool(nullptr); + return false; + } + + auto found_pool{FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())}; + if (found_pool != nullptr) { + address_info.SetPool(found_pool); + return true; + } + + if (force_map) { + address_info.SetForceMappedDspAddr(address_info.GetCpuAddr()); + } else { + address_info.SetPool(nullptr); + } + + return false; +} + +bool PoolMapper::TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info, + const CpuAddr address, const u64 size) const { + address_info.Setup(address, size); + + if (!FillDspAddr(address_info)) { + error_info.error_code = Service::Audio::ERR_POOL_MAPPING_FAILED; + error_info.address = address; + return force_map; + } + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + return true; +} + +bool PoolMapper::IsForceMapEnabled() const { + return force_map; +} + +u32 PoolMapper::GetProcessHandle(const MemoryPoolInfo* pool) const { + switch (pool->GetLocation()) { + case MemoryPoolInfo::Location::CPU: + return process_handle; + case MemoryPoolInfo::Location::DSP: + return Kernel::Svc::CurrentProcess; + } + LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location!"); + return Kernel::Svc::CurrentProcess; +} + +bool PoolMapper::Map([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr, + [[maybe_unused]] const u64 size) const { + // nn::audio::dsp::MapUserPointer(handle, cpu_addr, size); + return true; +} + +bool PoolMapper::Map(MemoryPoolInfo& pool) const { + switch (pool.GetLocation()) { + case MemoryPoolInfo::Location::CPU: + // Map with process_handle + pool.SetDspAddress(pool.GetCpuAddress()); + return true; + case MemoryPoolInfo::Location::DSP: + // Map with Kernel::Svc::CurrentProcess + pool.SetDspAddress(pool.GetCpuAddress()); + return true; + default: + LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!", + static_cast<u32>(pool.GetLocation())); + return false; + } +} + +bool PoolMapper::Unmap([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr, + [[maybe_unused]] const u64 size) const { + // nn::audio::dsp::UnmapUserPointer(handle, cpu_addr, size); + return true; +} + +bool PoolMapper::Unmap(MemoryPoolInfo& pool) const { + [[maybe_unused]] u32 handle{0}; + + switch (pool.GetLocation()) { + case MemoryPoolInfo::Location::CPU: + handle = process_handle; + break; + case MemoryPoolInfo::Location::DSP: + handle = Kernel::Svc::CurrentProcess; + break; + } + // nn::audio::dsp::UnmapUserPointer(handle, pool->cpu_address, pool->size); + pool.SetCpuAddress(0, 0); + pool.SetDspAddress(0); + return true; +} + +void PoolMapper::ForceUnmapPointer(const AddressInfo& address_info) const { + if (force_map) { + [[maybe_unused]] auto found_pool{ + FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())}; + // nn::audio::dsp::UnmapUserPointer(this->processHandle, address_info.GetCpuAddr(), 0); + } +} + +MemoryPoolInfo::ResultState PoolMapper::Update(MemoryPoolInfo& pool, + const MemoryPoolInfo::InParameter& in_params, + MemoryPoolInfo::OutStatus& out_params) const { + if (in_params.state != MemoryPoolInfo::State::RequestAttach && + in_params.state != MemoryPoolInfo::State::RequestDetach) { + return MemoryPoolInfo::ResultState::Success; + } + + if (in_params.address == 0 || in_params.size == 0 || !Common::Is4KBAligned(in_params.address) || + !Common::Is4KBAligned(in_params.size)) { + return MemoryPoolInfo::ResultState::BadParam; + } + + switch (in_params.state) { + case MemoryPoolInfo::State::RequestAttach: + pool.SetCpuAddress(in_params.address, in_params.size); + + Map(pool); + + if (pool.IsMapped()) { + out_params.state = MemoryPoolInfo::State::Attached; + return MemoryPoolInfo::ResultState::Success; + } + pool.SetCpuAddress(0, 0); + return MemoryPoolInfo::ResultState::MapFailed; + + case MemoryPoolInfo::State::RequestDetach: + if (pool.GetCpuAddress() != in_params.address || pool.GetSize() != in_params.size) { + return MemoryPoolInfo::ResultState::BadParam; + } + + if (pool.IsUsed()) { + return MemoryPoolInfo::ResultState::InUse; + } + + Unmap(pool); + + pool.SetCpuAddress(0, 0); + pool.SetDspAddress(0); + out_params.state = MemoryPoolInfo::State::Detached; + return MemoryPoolInfo::ResultState::Success; + + default: + LOG_ERROR(Service_Audio, "Invalid MemoryPoolInfo::State!"); + break; + } + + return MemoryPoolInfo::ResultState::Success; +} + +bool PoolMapper::InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, + const u64 size_) const { + switch (pool.GetLocation()) { + case MemoryPoolInfo::Location::CPU: + return false; + case MemoryPoolInfo::Location::DSP: + pool.SetCpuAddress(reinterpret_cast<u64>(memory), size_); + if (Map(Kernel::Svc::CurrentProcess, reinterpret_cast<u64>(memory), size_)) { + pool.SetDspAddress(pool.GetCpuAddress()); + return true; + } + return false; + default: + LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!", + static_cast<u32>(pool.GetLocation())); + return false; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/memory/pool_mapper.h b/src/audio_core/renderer/memory/pool_mapper.h new file mode 100644 index 000000000..9a691da7a --- /dev/null +++ b/src/audio_core/renderer/memory/pool_mapper.h @@ -0,0 +1,179 @@ +// 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 "common/common_types.h" +#include "core/hle/service/audio/errors.h" + +namespace AudioCore::AudioRenderer { +class AddressInfo; + +/** + * Utility functions for managing MemoryPoolInfos + */ +class PoolMapper { +public: + explicit PoolMapper(u32 process_handle, bool force_map); + explicit PoolMapper(u32 process_handle, std::span<MemoryPoolInfo> pool_infos, u32 pool_count, + bool force_map); + + /** + * Clear the usage state for all given pools. + * + * @param pools - The memory pools to clear. + * @param count - The number of pools. + */ + static void ClearUseState(std::span<MemoryPoolInfo> pools, u32 count); + + /** + * Find the memory pool containing the given address and size from a given list of pools. + * + * @param pools - The memory pools to search within. + * @param count - The number of pools. + * @param address - The address of the region to find. + * @param size - The size of the region to find. + * @return Pointer to the memory pool if found, otherwise nullptr. + */ + MemoryPoolInfo* FindMemoryPool(MemoryPoolInfo* pools, u64 count, CpuAddr address, + u64 size) const; + + /** + * Find the memory pool containing the given address and size from the PoolMapper's memory pool. + * + * @param address - The address of the region to find. + * @param size - The size of the region to find. + * @return Pointer to the memory pool if found, otherwise nullptr. + */ + MemoryPoolInfo* FindMemoryPool(CpuAddr address, u64 size) const; + + /** + * Set the PoolMapper's memory pool to one in the given list of pools, which contains + * address_info. + * + * @param address_info - The expected region to find within pools. + * @param pools - The list of pools to search within. + * @param count - The number of pools given. + * @return True if successfully mapped, otherwise false. + */ + bool FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools, u32 count) const; + + /** + * Set the PoolMapper's memory pool to the one containing address_info. + * + * @param address_info - The address to find the memory pool for. + * @return True if successfully mapped, otherwise false. + */ + bool FillDspAddr(AddressInfo& address_info) const; + + /** + * Try to attach a {address, size} region to the given address_info, and map it. Fills in the + * given error_info and address_info. + * + * @param error_info - Output error info. + * @param address_info - Output address info, initialized with the given {address, size} and + * attempted to map. + * @param address - Address of the region to map. + * @param size - Size of the region to map. + * @return True if successfully attached, otherwise false. + */ + bool TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info, + CpuAddr address, u64 size) const; + + /** + * Return whether force mapping is enabled. + * + * @return True if force mapping is enabled, otherwise false. + */ + bool IsForceMapEnabled() const; + + /** + * Get the process handle, depending on location. + * + * @param pool - The pool to check the location of. + * @return CurrentProcessHandle if location == DSP, + * the PoolMapper's process_handle if location == CPU + */ + u32 GetProcessHandle(const MemoryPoolInfo* pool) const; + + /** + * Map the given region with the given handle. This is a no-op. + * + * @param handle - The process handle to map to. + * @param cpu_addr - Address to map. + * @param size - Size to map. + * @return True if successfully mapped, otherwise false. + */ + bool Map(u32 handle, CpuAddr cpu_addr, u64 size) const; + + /** + * Map the given memory pool. + * + * @param pool - The pool to map. + * @return True if successfully mapped, otherwise false. + */ + bool Map(MemoryPoolInfo& pool) const; + + /** + * Unmap the given region with the given handle. + * + * @param handle - The process handle to unmap to. + * @param cpu_addr - Address to unmap. + * @param size - Size to unmap. + * @return True if successfully unmapped, otherwise false. + */ + bool Unmap(u32 handle, CpuAddr cpu_addr, u64 size) const; + + /** + * Unmap the given memory pool. + * + * @param pool - The pool to unmap. + * @return True if successfully unmapped, otherwise false. + */ + bool Unmap(MemoryPoolInfo& pool) const; + + /** + * Forcibly unmap the given region. + * + * @param address_info - The region to unmap. + */ + void ForceUnmapPointer(const AddressInfo& address_info) const; + + /** + * Update the given memory pool. + * + * @param pool - Pool to update. + * @param in_params - Input parameters for the update. + * @param out_params - Output parameters for the update. + * @return The result of the update. See MemoryPoolInfo::ResultState + */ + MemoryPoolInfo::ResultState Update(MemoryPoolInfo& pool, + const MemoryPoolInfo::InParameter& in_params, + MemoryPoolInfo::OutStatus& out_params) const; + + /** + * Initialize the PoolMapper's memory pool. + * + * @param pool - Input pool to initialize. + * @param memory - Pointer to the memory region for the pool. + * @param size - Size of the memory region for the pool. + * @return True if initialized successfully, otherwise false. + */ + bool InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, u64 size) const; + +private: + /// Process handle for this mapper, used when location == CPU + u32 process_handle; + /// List of memory pools assigned to this mapper + MemoryPoolInfo* pool_infos{}; + /// The number of pools + u64 pool_count{}; + /// Is forced mapping enabled + bool force_map; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_context.cpp b/src/audio_core/renderer/mix/mix_context.cpp new file mode 100644 index 000000000..2427c83ed --- /dev/null +++ b/src/audio_core/renderer/mix/mix_context.cpp @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <ranges> + +#include "audio_core/renderer/mix/mix_context.h" +#include "audio_core/renderer/splitter/splitter_context.h" + +namespace AudioCore::AudioRenderer { + +void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_, + const u32 count_, std::span<s32> effect_process_order_buffer_, + const u32 effect_count_, std::span<u8> node_states_workbuffer, + const u64 node_buffer_size, std::span<u8> edge_matrix_workbuffer, + const u64 edge_matrix_size) { + count = count_; + sorted_mix_infos = sorted_mix_infos_; + mix_infos = mix_infos_; + effect_process_order_buffer = effect_process_order_buffer_; + effect_count = effect_count_; + + if (node_states_workbuffer.size() > 0 && edge_matrix_workbuffer.size() > 0) { + node_states.Initialize(node_states_workbuffer, node_buffer_size, count); + edge_matrix.Initialize(edge_matrix_workbuffer, edge_matrix_size, count); + } + + for (s32 i = 0; i < count; i++) { + sorted_mix_infos[i] = &mix_infos[i]; + } +} + +MixInfo* MixContext::GetSortedInfo(const s32 index) { + return sorted_mix_infos[index]; +} + +void MixContext::SetSortedInfo(const s32 index, MixInfo& mix_info) { + sorted_mix_infos[index] = &mix_info; +} + +MixInfo* MixContext::GetInfo(const s32 index) { + return &mix_infos[index]; +} + +MixInfo* MixContext::GetFinalMixInfo() { + return &mix_infos[0]; +} + +s32 MixContext::GetCount() const { + return count; +} + +void MixContext::UpdateDistancesFromFinalMix() { + for (s32 i = 0; i < count; i++) { + mix_infos[i].distance_from_final_mix = InvalidDistanceFromFinalMix; + } + + for (s32 i = 0; i < count; i++) { + auto& mix_info{mix_infos[i]}; + sorted_mix_infos[i] = &mix_info; + + if (!mix_info.in_use) { + continue; + } + + auto mix_id{mix_info.mix_id}; + auto distance_to_final_mix{FinalMixId}; + + while (distance_to_final_mix < count) { + if (mix_id == FinalMixId) { + break; + } + + if (mix_id == UnusedMixId) { + distance_to_final_mix = InvalidDistanceFromFinalMix; + break; + } + + auto distance_from_final_mix{mix_infos[mix_id].distance_from_final_mix}; + if (distance_from_final_mix != InvalidDistanceFromFinalMix) { + distance_to_final_mix = distance_from_final_mix + 1; + break; + } + + distance_to_final_mix++; + mix_id = mix_infos[mix_id].dst_mix_id; + } + + if (distance_to_final_mix >= count) { + distance_to_final_mix = InvalidDistanceFromFinalMix; + } + mix_info.distance_from_final_mix = distance_to_final_mix; + } +} + +void MixContext::SortInfo() { + UpdateDistancesFromFinalMix(); + + std::ranges::sort(sorted_mix_infos, [](const MixInfo* lhs, const MixInfo* rhs) { + return lhs->distance_from_final_mix > rhs->distance_from_final_mix; + }); + + CalcMixBufferOffset(); +} + +void MixContext::CalcMixBufferOffset() { + s16 offset{0}; + for (s32 i = 0; i < count; i++) { + auto mix_info{sorted_mix_infos[i]}; + if (mix_info->in_use) { + const auto buffer_count{mix_info->buffer_count}; + mix_info->buffer_offset = offset; + offset += buffer_count; + } + } +} + +bool MixContext::TSortInfo(const SplitterContext& splitter_context) { + if (!splitter_context.UsingSplitter()) { + CalcMixBufferOffset(); + return true; + } + + if (!node_states.Tsort(edge_matrix)) { + return false; + } + + std::vector<s32> sorted_results{node_states.GetSortedResuls()}; + const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))}; + for (s32 i = 0; i < result_size; i++) { + sorted_mix_infos[i] = &mix_infos[sorted_results[i]]; + } + + CalcMixBufferOffset(); + return true; +} + +EdgeMatrix& MixContext::GetEdgeMatrix() { + return edge_matrix; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_context.h b/src/audio_core/renderer/mix/mix_context.h new file mode 100644 index 000000000..da3aa2829 --- /dev/null +++ b/src/audio_core/renderer/mix/mix_context.h @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/nodes/edge_matrix.h" +#include "audio_core/renderer/nodes/node_states.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class SplitterContext; + +/* + * Manages mixing states, sorting and building a node graph to describe a mix order. + */ +class MixContext { +public: + /** + * Initialize the mix context. + * + * @param sorted_mix_infos - Buffer for the sorted mix infos. + * @param mix_infos - Buffer for the mix infos. + * @param effect_process_order_buffer - Buffer for the effect process orders. + * @param effect_count - Number of effects in the buffer. + * @param node_states_workbuffer - Buffer for node states. + * @param node_buffer_size - Size of the node states buffer. + * @param edge_matrix_workbuffer - Buffer for edge matrix. + * @param edge_matrix_size - Size of the edge matrix buffer. + */ + void Initialize(std::span<MixInfo*> sorted_mix_infos, std::span<MixInfo> mix_infos, u32 count_, + std::span<s32> effect_process_order_buffer, u32 effect_count, + std::span<u8> node_states_workbuffer, u64 node_buffer_size, + std::span<u8> edge_matrix_workbuffer, u64 edge_matrix_size); + + /** + * Get a sorted mix at the given index. + * + * @param index - Index of sorted mix. + * @return The sorted mix. + */ + MixInfo* GetSortedInfo(s32 index); + + /** + * Set the sorted info at the given index. + * + * @param index - Index of sorted mix. + * @param mix_info - The new mix for this index. + */ + void SetSortedInfo(s32 index, MixInfo& mix_info); + + /** + * Get a mix at the given index. + * + * @param index - Index of mix. + * @return The mix. + */ + MixInfo* GetInfo(s32 index); + + /** + * Get the final mix. + * + * @return The final mix. + */ + MixInfo* GetFinalMixInfo(); + + /** + * Get the current number of mixes. + * + * @return The number of active mixes. + */ + s32 GetCount() const; + + /** + * Update all of the mixes' distance from the final mix. + * Needs to be called after altering the mix graph. + */ + void UpdateDistancesFromFinalMix(); + + /** + * Non-splitter sort, sorts the sorted mixes based on their distance from the final mix. + */ + void SortInfo(); + + /** + * Re-calculate the mix buffer offsets for each mix after altering the mix. + */ + void CalcMixBufferOffset(); + + /** + * Splitter sort, traverse the splitter node graph and sort the sorted mixes from results. + * + * @param splitter_context - Splitter context for the sort. + * @return True if the sort was successful, othewise false. + */ + bool TSortInfo(const SplitterContext& splitter_context); + + /** + * Get the edge matrix used for the mix graph. + * + * @return The edge matrix used. + */ + EdgeMatrix& GetEdgeMatrix(); + +private: + /// Array of sorted mixes + std::span<MixInfo*> sorted_mix_infos{}; + /// Array of mixes + std::span<MixInfo> mix_infos{}; + /// Number of active mixes + s32 count{}; + /// Array of effect process orderings + std::span<s32> effect_process_order_buffer{}; + /// Number of effects in the process ordering buffer + u64 effect_count{}; + /// Node states used in splitter sort + NodeStates node_states{}; + /// Edge matrix for connected nodes used in splitter sort + EdgeMatrix edge_matrix{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_info.cpp b/src/audio_core/renderer/mix/mix_info.cpp new file mode 100644 index 000000000..cc18e57ee --- /dev/null +++ b/src/audio_core/renderer/mix/mix_info.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/effect/effect_context.h" +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/nodes/edge_matrix.h" +#include "audio_core/renderer/splitter/splitter_context.h" + +namespace AudioCore::AudioRenderer { + +MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior) + : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_}, + long_size_pre_delay_supported{behavior.IsLongSizePreDelaySupported()} { + ClearEffectProcessingOrder(); +} + +void MixInfo::Cleanup() { + mix_id = UnusedMixId; + dst_mix_id = UnusedMixId; + dst_splitter_id = UnusedSplitterId; +} + +void MixInfo::ClearEffectProcessingOrder() { + for (s32 i = 0; i < effect_count; i++) { + effect_order_buffer[i] = -1; + } +} + +bool MixInfo::Update(EdgeMatrix& edge_matrix, const InParameter& in_params, + EffectContext& effect_context, SplitterContext& splitter_context, + const BehaviorInfo& behavior) { + volume = in_params.volume; + sample_rate = in_params.sample_rate; + buffer_count = static_cast<s16>(in_params.buffer_count); + in_use = in_params.in_use; + mix_id = in_params.mix_id; + node_id = in_params.node_id; + mix_volumes = in_params.mix_volumes; + + bool sort_required{false}; + if (behavior.IsSplitterSupported()) { + sort_required = UpdateConnection(edge_matrix, in_params, splitter_context); + } else { + if (dst_mix_id != in_params.dest_mix_id) { + dst_mix_id = in_params.dest_mix_id; + sort_required = true; + } + dst_splitter_id = UnusedSplitterId; + } + + ClearEffectProcessingOrder(); + + // Check all effects, and set their order if they belong to this mix. + const auto count{effect_context.GetCount()}; + for (u32 i = 0; i < count; i++) { + const auto& info{effect_context.GetInfo(i)}; + if (mix_id == info.GetMixId()) { + const auto processing_order{info.GetProcessingOrder()}; + if (processing_order > effect_count) { + break; + } + effect_order_buffer[processing_order] = i; + } + } + + return sort_required; +} + +bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params, + SplitterContext& splitter_context) { + auto has_new_connection{false}; + if (dst_splitter_id != UnusedSplitterId) { + auto& splitter_info{splitter_context.GetInfo(dst_splitter_id)}; + has_new_connection = splitter_info.HasNewConnection(); + } + + // Check if this mix matches the input parameters. + // If everything is the same, don't bother updating. + if (dst_mix_id == in_params.dest_mix_id && dst_splitter_id == in_params.dest_splitter_id && + !has_new_connection) { + return false; + } + + // Reset the mix in the graph, as we're about to update it. + edge_matrix.RemoveEdges(mix_id); + + if (in_params.dest_mix_id == UnusedMixId) { + if (in_params.dest_splitter_id != UnusedSplitterId) { + // If the splitter is used, connect this mix to each active destination. + auto& splitter_info{splitter_context.GetInfo(in_params.dest_splitter_id)}; + auto const destination_count{splitter_info.GetDestinationCount()}; + + for (u32 i = 0; i < destination_count; i++) { + auto destination{ + splitter_context.GetDesintationData(in_params.dest_splitter_id, i)}; + + if (destination) { + const auto destination_id{destination->GetMixId()}; + if (destination_id != UnusedMixId) { + edge_matrix.Connect(mix_id, destination_id); + } + } + } + } + } else { + // If the splitter is not used, only connect this mix to its destination. + edge_matrix.Connect(mix_id, in_params.dest_mix_id); + } + + dst_mix_id = in_params.dest_mix_id; + dst_splitter_id = in_params.dest_splitter_id; + return true; +} + +bool MixInfo::HasAnyConnection() const { + return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_info.h b/src/audio_core/renderer/mix/mix_info.h new file mode 100644 index 000000000..b5fa4c0c7 --- /dev/null +++ b/src/audio_core/renderer/mix/mix_info.h @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class EdgeMatrix; +class SplitterContext; +class EffectContext; +class BehaviorInfo; + +/** + * A single mix, which may feed through other mixes in a chain until reaching the final output mix. + */ +class MixInfo { +public: + struct InParameter { + /* 0x000 */ f32 volume; + /* 0x004 */ u32 sample_rate; + /* 0x008 */ u32 buffer_count; + /* 0x00C */ bool in_use; + /* 0x00D */ bool is_dirty; + /* 0x010 */ s32 mix_id; + /* 0x014 */ u32 effect_count; + /* 0x018 */ s32 node_id; + /* 0x01C */ char unk01C[0x8]; + /* 0x024 */ std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes; + /* 0x924 */ s32 dest_mix_id; + /* 0x928 */ s32 dest_splitter_id; + /* 0x92C */ char unk92C[0x4]; + }; + static_assert(sizeof(InParameter) == 0x930, "MixInfo::InParameter has the wrong size!"); + + struct InDirtyParameter { + /* 0x00 */ u32 magic; + /* 0x04 */ s32 count; + /* 0x08 */ char unk08[0x18]; + }; + static_assert(sizeof(InDirtyParameter) == 0x20, + "MixInfo::InDirtyParameter has the wrong size!"); + + MixInfo(std::span<s32> effect_order_buffer, s32 effect_count, BehaviorInfo& behavior); + + /** + * Clean up the mix, resetting it to a default state. + */ + void Cleanup(); + + /** + * Clear the effect process order for all effects in this mix. + */ + void ClearEffectProcessingOrder(); + + /** + * Update the mix according to the given parameters. + * + * @param edge_matrix - Updated with new splitter node connections, if supported. + * @param in_params - Input parameters. + * @param effect_context - Used to update the effect orderings. + * @param splitter_context - Used to update the mix graph if supported. + * @param behavior - Used for checking which features are supported. + * @return True if the mix was updated and a sort is required, otherwise false. + */ + bool Update(EdgeMatrix& edge_matrix, const InParameter& in_params, + EffectContext& effect_context, SplitterContext& splitter_context, + const BehaviorInfo& behavior); + + /** + * Update the mix's connection in the node graph according to the given parameters. + * + * @param edge_matrix - Updated with new splitter node connections, if supported. + * @param in_params - Input parameters. + * @param splitter_context - Used to update the mix graph if supported. + * @return True if the mix was updated and a sort is required, otherwise false. + */ + bool UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params, + SplitterContext& splitter_context); + + /** + * Check if this mix is connected to any other. + * + * @return True if the mix has a connection, otherwise false. + */ + bool HasAnyConnection() const; + + /// Volume of this mix + f32 volume{}; + /// Sample rate of this mix + u32 sample_rate{}; + /// Number of buffers in this mix + s16 buffer_count{}; + /// Is this mix in use? + bool in_use{}; + /// Is this mix enabled? + bool enabled{}; + /// Id of this mix + s32 mix_id{UnusedMixId}; + /// Node id of this mix + s32 node_id{}; + /// Buffer offset for this mix + s16 buffer_offset{}; + /// Distance to the final mix + s32 distance_from_final_mix{InvalidDistanceFromFinalMix}; + /// Array of effect orderings of all effects in this mix + std::span<s32> effect_order_buffer; + /// Number of effects in this mix + const s32 effect_count; + /// Id for next mix in the chain + s32 dst_mix_id{UnusedMixId}; + /// Mixing volumes for this mix used when this mix is chained with another + std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes{}; + /// Id for next mix in the graph when splitter is used + s32 dst_splitter_id{UnusedSplitterId}; + /// Is a longer pre-delay time supported for the reverb effect? + const bool long_size_pre_delay_supported; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/nodes/bit_array.h b/src/audio_core/renderer/nodes/bit_array.h new file mode 100644 index 000000000..b0d53cd51 --- /dev/null +++ b/src/audio_core/renderer/nodes/bit_array.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <vector> + +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Represents an array of bits used for nodes and edges for the mixing graph. + */ +struct BitArray { + void reset() { + buffer.assign(buffer.size(), false); + } + + /// Bits + std::vector<bool> buffer{}; + /// Size of the buffer + u32 size{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/nodes/edge_matrix.cpp b/src/audio_core/renderer/nodes/edge_matrix.cpp new file mode 100644 index 000000000..5573f33b9 --- /dev/null +++ b/src/audio_core/renderer/nodes/edge_matrix.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/nodes/edge_matrix.h" + +namespace AudioCore::AudioRenderer { + +void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer, + [[maybe_unused]] const u64 node_buffer_size, const u32 count_) { + count = count_; + edges.buffer.resize(count_ * count_); + edges.size = count_ * count_; + edges.reset(); +} + +bool EdgeMatrix::Connected(const u32 id, const u32 destination_id) const { + return edges.buffer[count * id + destination_id]; +} + +void EdgeMatrix::Connect(const u32 id, const u32 destination_id) { + edges.buffer[count * id + destination_id] = true; +} + +void EdgeMatrix::Disconnect(const u32 id, const u32 destination_id) { + edges.buffer[count * id + destination_id] = false; +} + +void EdgeMatrix::RemoveEdges(const u32 id) { + for (u32 dest = 0; dest < count; dest++) { + Disconnect(id, dest); + } +} + +u32 EdgeMatrix::GetNodeCount() const { + return count; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/nodes/edge_matrix.h b/src/audio_core/renderer/nodes/edge_matrix.h new file mode 100644 index 000000000..27a20e43e --- /dev/null +++ b/src/audio_core/renderer/nodes/edge_matrix.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/nodes/bit_array.h" +#include "common/alignment.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * An edge matrix, holding the connections for each node to every other node in the graph. + */ +class EdgeMatrix { +public: + /** + * Calculate the size required for its workbuffer. + * + * @param count - The number of nodes in the graph. + * @return The required workbuffer size. + */ + static u64 GetWorkBufferSize(u32 count) { + return Common::AlignUp(count * count, 0x40) / sizeof(u64); + } + + /** + * Initialize this edge matrix. + * + * @param buffer - The workbuffer to use. Unused. + * @param node_buffer_size - The size of the workbuffer. Unused. + * @param count - The number of nodes in the graph. + */ + void Initialize(std::span<u8> buffer, u64 node_buffer_size, u32 count); + + /** + * Check if a node is connected to another. + * + * @param id - The node id to check. + * @param destination_id - Node id to check connection with. + */ + bool Connected(u32 id, u32 destination_id) const; + + /** + * Connect a node to another. + * + * @param id - The node id to connect. + * @param destination_id - Destination to connect it to. + */ + void Connect(u32 id, u32 destination_id); + + /** + * Disconnect a node from another. + * + * @param id - The node id to disconnect. + * @param destination_id - Destination to disconnect it from. + */ + void Disconnect(u32 id, u32 destination_id); + + /** + * Remove all connections for a given node. + * + * @param id - The node id to disconnect. + */ + void RemoveEdges(u32 id); + + /** + * Get the number of nodes in the graph. + * + * @return Number of nodes. + */ + u32 GetNodeCount() const; + +private: + /// Edges for the current graph + BitArray edges; + /// Number of nodes (not edges) in the graph + u32 count; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/nodes/node_states.cpp b/src/audio_core/renderer/nodes/node_states.cpp new file mode 100644 index 000000000..1821a51e6 --- /dev/null +++ b/src/audio_core/renderer/nodes/node_states.cpp @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/nodes/node_states.h" +#include "common/logging/log.h" + +namespace AudioCore::AudioRenderer { + +void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size, + const u32 count) { + u64 num_blocks{Common::AlignUp(count, 0x40) / sizeof(u64)}; + u64 offset{0}; + + node_count = count; + + nodes_found.buffer.resize(count); + nodes_found.size = count; + nodes_found.reset(); + + offset += num_blocks; + + nodes_complete.buffer.resize(count); + nodes_complete.size = count; + nodes_complete.reset(); + + offset += num_blocks; + + results = {reinterpret_cast<u32*>(&buffer_[offset]), count}; + + offset += count * sizeof(u32); + + stack.stack = {reinterpret_cast<u32*>(&buffer_[offset]), count * count}; + stack.size = count * count; + stack.unk_10 = count * count; + + offset += count * count * sizeof(u32); +} + +bool NodeStates::Tsort(const EdgeMatrix& edge_matrix) { + return DepthFirstSearch(edge_matrix, stack); +} + +bool NodeStates::DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack_) { + ResetState(); + + for (u32 node_id = 0; node_id < node_count; node_id++) { + if (GetState(node_id) == SearchState::Unknown) { + stack_.push(node_id); + } + + while (stack_.Count() > 0) { + auto current_node{stack_.top()}; + switch (GetState(current_node)) { + case SearchState::Unknown: + SetState(current_node, SearchState::Found); + break; + case SearchState::Found: + SetState(current_node, SearchState::Complete); + PushTsortResult(current_node); + stack_.pop(); + continue; + case SearchState::Complete: + stack_.pop(); + continue; + } + + const auto edge_count{edge_matrix.GetNodeCount()}; + for (u32 edge_id = 0; edge_id < edge_count; edge_id++) { + if (!edge_matrix.Connected(current_node, edge_id)) { + continue; + } + + switch (GetState(edge_id)) { + case SearchState::Unknown: + stack_.push(edge_id); + break; + case SearchState::Found: + LOG_ERROR(Service_Audio, + "Cycle detected in the node graph, graph is not a DAG! " + "Bailing to avoid an infinite loop"); + ResetState(); + return false; + case SearchState::Complete: + break; + } + } + } + } + + return true; +} + +NodeStates::SearchState NodeStates::GetState(const u32 id) const { + if (nodes_found.buffer[id]) { + return SearchState::Found; + } else if (nodes_complete.buffer[id]) { + return SearchState::Complete; + } + return SearchState::Unknown; +} + +void NodeStates::PushTsortResult(const u32 id) { + results[result_pos++] = id; +} + +void NodeStates::SetState(const u32 id, const SearchState state) { + switch (state) { + case SearchState::Complete: + nodes_found.buffer[id] = false; + nodes_complete.buffer[id] = true; + break; + case SearchState::Found: + nodes_found.buffer[id] = true; + nodes_complete.buffer[id] = false; + break; + case SearchState::Unknown: + nodes_found.buffer[id] = false; + nodes_complete.buffer[id] = false; + break; + default: + LOG_ERROR(Service_Audio, "Unknown node SearchState {}", static_cast<u32>(state)); + break; + } +} + +void NodeStates::ResetState() { + nodes_found.reset(); + nodes_complete.reset(); + std::fill(results.begin(), results.end(), -1); + result_pos = 0; +} + +u32 NodeStates::GetNodeCount() const { + return node_count; +} + +std::vector<s32> NodeStates::GetSortedResuls() const { + return {results.rbegin(), results.rbegin() + result_pos}; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h new file mode 100644 index 000000000..a1e0958a2 --- /dev/null +++ b/src/audio_core/renderer/nodes/node_states.h @@ -0,0 +1,195 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> +#include <vector> + +#include "audio_core/renderer/nodes/edge_matrix.h" +#include "common/alignment.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Graph utility functions for sorting and getting results from the DAG. + */ +class NodeStates { + /** + * State of a node in the depth first search. + */ + enum class SearchState { + Unknown, + Found, + Complete, + }; + + /** + * Stack used for a depth first search. + */ + struct Stack { + /** + * Calculate the workbuffer size required for this stack. + * + * @param count - Maximum number of nodes for the stack. + * @return Required buffer size. + */ + static u32 CalcBufferSize(u32 count) { + return count * sizeof(u32); + } + + /** + * Reset the stack back to default. + * + * @param buffer_ - The new buffer to use. + * @param size_ - The size of the new buffer. + */ + void Reset(u32* buffer_, u32 size_) { + stack = {buffer_, size_}; + size = size_; + pos = 0; + unk_10 = size_; + } + + /** + * Get the current stack position. + * + * @return The current stack position. + */ + u32 Count() { + return pos; + } + + /** + * Push a new node to the stack. + * + * @param data - The node to push. + */ + void push(u32 data) { + stack[pos++] = data; + } + + /** + * Pop a node from the stack. + * + * @return The node on the top of the stack. + */ + u32 pop() { + return stack[--pos]; + } + + /** + * Get the top of the stack without popping. + * + * @return The node on the top of the stack. + */ + u32 top() { + return stack[pos - 1]; + } + + /// Buffer for the stack + std::span<u32> stack{}; + /// Size of the stack buffer + u32 size{}; + /// Current stack position + u32 pos{}; + /// Unknown + u32 unk_10{}; + }; + +public: + /** + * Calculate the workbuffer size required for the node states. + * + * @param count - The number of nodes. + * @return The required workbuffer size. + */ + static u64 GetWorkBufferSize(u32 count) { + return (Common::AlignUp(count, 0x40) / sizeof(u64)) * 2 + count * sizeof(BitArray) + + count * Stack::CalcBufferSize(count); + } + + /** + * Initialize the node states. + * + * @param buffer - The workbuffer to use. Unused. + * @param node_buffer_size - The size of the workbuffer. Unused. + * @param count - The number of nodes in the graph. + */ + void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count); + + /** + * Sort the graph. Only calls DepthFirstSearch. + * + * @param edge_matrix - The edge matrix used to hold the connections between nodes. + * @return True if the sort was successful, otherwise false. + */ + bool Tsort(const EdgeMatrix& edge_matrix); + + /** + * Sort the graph via depth first search. + * + * @param edge_matrix - The edge matrix used to hold the connections between nodes. + * @param stack - The stack used for pushing and popping nodes. + * @return True if the sort was successful, otherwise false. + */ + bool DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack); + + /** + * Get the search state of a given node. + * + * @param id - The node id to check. + * @return The node's search state. See SearchState + */ + SearchState GetState(u32 id) const; + + /** + * Push a node id to the results buffer when found in the DFS. + * + * @param id - The node id to push. + */ + void PushTsortResult(u32 id); + + /** + * Set the state of a node. + * + * @param id - The node id to alter. + * @param state - The new search state. + */ + void SetState(u32 id, SearchState state); + + /** + * Reset the nodes found, complete and the results. + */ + void ResetState(); + + /** + * Get the number of nodes in the graph. + * + * @return The number of nodes. + */ + u32 GetNodeCount() const; + + /** + * Get the sorted results from the DFS. + * + * @return Vector of nodes in reverse order. + */ + std::vector<s32> GetSortedResuls() const; + +private: + /// Number of nodes in the graph + u32 node_count{}; + /// Position in results buffer + u32 result_pos{}; + /// List of nodes found + BitArray nodes_found{}; + /// List of nodes completed + BitArray nodes_complete{}; + /// List of results from the depth first search + std::span<u32> results{}; + /// Stack used during the depth first search + Stack stack{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/detail_aspect.cpp b/src/audio_core/renderer/performance/detail_aspect.cpp new file mode 100644 index 000000000..f6405937f --- /dev/null +++ b/src/audio_core/renderer/performance/detail_aspect.cpp @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_generator.h" +#include "audio_core/renderer/performance/detail_aspect.h" + +namespace AudioCore::AudioRenderer { + +DetailAspect::DetailAspect(CommandGenerator& command_generator_, + const PerformanceEntryType entry_type, const s32 node_id_, + const PerformanceDetailType detail_type) + : command_generator{command_generator_}, node_id{node_id_} { + auto perf_manager{command_generator.GetPerformanceManager()}; + if (perf_manager != nullptr && perf_manager->IsInitialized() && + perf_manager->IsDetailTarget(node_id) && + perf_manager->GetNextEntry(performance_entry_address, detail_type, entry_type, node_id)) { + command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start, + performance_entry_address); + + initialized = true; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/detail_aspect.h b/src/audio_core/renderer/performance/detail_aspect.h new file mode 100644 index 000000000..ee4ac2f76 --- /dev/null +++ b/src/audio_core/renderer/performance/detail_aspect.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/performance/performance_entry_addresses.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class CommandGenerator; + +/** + * Holds detailed information about performance metrics, filled in by the AudioRenderer during + * Performance commands. + */ +class DetailAspect { +public: + DetailAspect() = default; + DetailAspect(CommandGenerator& command_generator, PerformanceEntryType entry_type, s32 node_id, + PerformanceDetailType detail_type); + + /// Command generator the command will be generated into + CommandGenerator& command_generator; + /// Addresses to be filled by the AudioRenderer + PerformanceEntryAddresses performance_entry_address{}; + /// Is this detail aspect initialized? + bool initialized{}; + /// Node id of this aspect + s32 node_id; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/entry_aspect.cpp b/src/audio_core/renderer/performance/entry_aspect.cpp new file mode 100644 index 000000000..dd4165803 --- /dev/null +++ b/src/audio_core/renderer/performance/entry_aspect.cpp @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_generator.h" +#include "audio_core/renderer/performance/entry_aspect.h" + +namespace AudioCore::AudioRenderer { + +EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type, + const s32 node_id_) + : command_generator{command_generator_}, node_id{node_id_} { + auto perf_manager{command_generator.GetPerformanceManager()}; + if (perf_manager != nullptr && perf_manager->IsInitialized() && + perf_manager->GetNextEntry(performance_entry_address, type, node_id)) { + command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start, + performance_entry_address); + + initialized = true; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/entry_aspect.h b/src/audio_core/renderer/performance/entry_aspect.h new file mode 100644 index 000000000..01c1eb3f1 --- /dev/null +++ b/src/audio_core/renderer/performance/entry_aspect.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/performance/performance_entry_addresses.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class CommandGenerator; + +/** + * Holds entry information about performance metrics, filled in by the AudioRenderer during + * Performance commands. + */ +class EntryAspect { +public: + EntryAspect() = default; + EntryAspect(CommandGenerator& command_generator, PerformanceEntryType type, s32 node_id); + + /// Command generator the command will be generated into + CommandGenerator& command_generator; + /// Addresses to be filled by the AudioRenderer + PerformanceEntryAddresses performance_entry_address{}; + /// Is this detail aspect initialized? + bool initialized{}; + /// Node id of this aspect + s32 node_id; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_detail.h b/src/audio_core/renderer/performance/performance_detail.h new file mode 100644 index 000000000..3a4897e60 --- /dev/null +++ b/src/audio_core/renderer/performance/performance_detail.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/performance/performance_entry.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +enum class PerformanceDetailType : u8 { + Invalid, + Unk1, + Unk2, + Unk3, + Unk4, + Unk5, + Unk6, + Unk7, + Unk8, + Unk9, + Unk10, + Unk11, + Unk12, + Unk13, +}; + +struct PerformanceDetailVersion1 { + /* 0x00 */ u32 node_id; + /* 0x04 */ u32 start_time; + /* 0x08 */ u32 processed_time; + /* 0x0C */ PerformanceDetailType detail_type; + /* 0x0D */ PerformanceEntryType entry_type; +}; +static_assert(sizeof(PerformanceDetailVersion1) == 0x10, + "PerformanceDetailVersion1 has the worng size!"); + +struct PerformanceDetailVersion2 { + /* 0x00 */ u32 node_id; + /* 0x04 */ u32 start_time; + /* 0x08 */ u32 processed_time; + /* 0x0C */ PerformanceDetailType detail_type; + /* 0x0D */ PerformanceEntryType entry_type; + /* 0x10 */ u32 unk_10; + /* 0x14 */ char unk14[0x4]; +}; +static_assert(sizeof(PerformanceDetailVersion2) == 0x18, + "PerformanceDetailVersion2 has the worng size!"); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_entry.h b/src/audio_core/renderer/performance/performance_entry.h new file mode 100644 index 000000000..d1b21406b --- /dev/null +++ b/src/audio_core/renderer/performance/performance_entry.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +enum class PerformanceEntryType : u8 { + Invalid, + Voice, + SubMix, + FinalMix, + Sink, +}; + +struct PerformanceEntryVersion1 { + /* 0x00 */ u32 node_id; + /* 0x04 */ u32 start_time; + /* 0x08 */ u32 processed_time; + /* 0x0C */ PerformanceEntryType entry_type; +}; +static_assert(sizeof(PerformanceEntryVersion1) == 0x10, + "PerformanceEntryVersion1 has the worng size!"); + +struct PerformanceEntryVersion2 { + /* 0x00 */ u32 node_id; + /* 0x04 */ u32 start_time; + /* 0x08 */ u32 processed_time; + /* 0x0C */ PerformanceEntryType entry_type; + /* 0x0D */ char unk0D[0xB]; +}; +static_assert(sizeof(PerformanceEntryVersion2) == 0x18, + "PerformanceEntryVersion2 has the worng size!"); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_entry_addresses.h b/src/audio_core/renderer/performance/performance_entry_addresses.h new file mode 100644 index 000000000..e381d765c --- /dev/null +++ b/src/audio_core/renderer/performance/performance_entry_addresses.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/common/common.h" + +namespace AudioCore::AudioRenderer { + +struct PerformanceEntryAddresses { + CpuAddr translated_address; + CpuAddr entry_start_time_offset; + CpuAddr header_entry_count_offset; + CpuAddr entry_processed_time_offset; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_frame_header.h b/src/audio_core/renderer/performance/performance_frame_header.h new file mode 100644 index 000000000..707cc0afb --- /dev/null +++ b/src/audio_core/renderer/performance/performance_frame_header.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +struct PerformanceFrameHeaderVersion1 { + /* 0x00 */ u32 magic; // "PERF" + /* 0x04 */ u32 entry_count; + /* 0x08 */ u32 detail_count; + /* 0x0C */ u32 next_offset; + /* 0x10 */ u32 total_processing_time; + /* 0x14 */ u32 frame_index; +}; +static_assert(sizeof(PerformanceFrameHeaderVersion1) == 0x18, + "PerformanceFrameHeaderVersion1 has the worng size!"); + +struct PerformanceFrameHeaderVersion2 { + /* 0x00 */ u32 magic; // "PERF" + /* 0x04 */ u32 entry_count; + /* 0x08 */ u32 detail_count; + /* 0x0C */ u32 next_offset; + /* 0x10 */ u32 total_processing_time; + /* 0x14 */ u32 voices_dropped; + /* 0x18 */ u64 start_time; + /* 0x20 */ u32 frame_index; + /* 0x24 */ bool render_time_exceeded; + /* 0x25 */ char unk25[0xB]; +}; +static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30, + "PerformanceFrameHeaderVersion2 has the worng size!"); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_manager.cpp b/src/audio_core/renderer/performance/performance_manager.cpp new file mode 100644 index 000000000..fd5873e1e --- /dev/null +++ b/src/audio_core/renderer/performance/performance_manager.cpp @@ -0,0 +1,645 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_funcs.h" + +namespace AudioCore::AudioRenderer { + +void PerformanceManager::CreateImpl(const size_t version) { + switch (version) { + case 1: + impl = std::make_unique< + PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, + PerformanceEntryVersion1, PerformanceDetailVersion1>>(); + break; + case 2: + impl = std::make_unique< + PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, + PerformanceEntryVersion2, PerformanceDetailVersion2>>(); + break; + default: + LOG_WARNING(Service_Audio, "Invalid PerformanceMetricsDataFormat {}, creating version 1", + static_cast<u32>(version)); + impl = std::make_unique< + PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, + PerformanceEntryVersion1, PerformanceDetailVersion1>>(); + } +} + +void PerformanceManager::Initialize(std::span<u8> workbuffer, const u64 workbuffer_size, + const AudioRendererParameterInternal& params, + const BehaviorInfo& behavior, + const MemoryPoolInfo& memory_pool) { + CreateImpl(behavior.GetPerformanceMetricsDataFormat()); + impl->Initialize(workbuffer, workbuffer_size, params, behavior, memory_pool); +} + +bool PerformanceManager::IsInitialized() const { + if (impl) { + return impl->IsInitialized(); + } + return false; +} + +u32 PerformanceManager::CopyHistories(u8* out_buffer, u64 out_size) { + if (impl) { + return impl->CopyHistories(out_buffer, out_size); + } + return 0; +} + +bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, + const PerformanceSysDetailType sys_detail_type, + const s32 node_id) { + if (impl) { + return impl->GetNextEntry(addresses, unk, sys_detail_type, node_id); + } + return false; +} + +bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceEntryType entry_type, const s32 node_id) { + if (impl) { + return impl->GetNextEntry(addresses, entry_type, node_id); + } + return false; +} + +bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceDetailType detail_type, + const PerformanceEntryType entry_type, const s32 node_id) { + if (impl) { + return impl->GetNextEntry(addresses, detail_type, entry_type, node_id); + } + return false; +} + +void PerformanceManager::TapFrame(const bool dsp_behind, const u32 voices_dropped, + const u64 rendering_start_tick) { + if (impl) { + impl->TapFrame(dsp_behind, voices_dropped, rendering_start_tick); + } +} + +bool PerformanceManager::IsDetailTarget(const u32 target_node_id) const { + if (impl) { + return impl->IsDetailTarget(target_node_id); + } + return false; +} + +void PerformanceManager::SetDetailTarget(const u32 target_node_id) { + if (impl) { + impl->SetDetailTarget(target_node_id); + } +} + +template <> +void PerformanceManagerImpl< + PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, + PerformanceDetailVersion1>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size, + const AudioRendererParameterInternal& params, + const BehaviorInfo& behavior, + const MemoryPoolInfo& memory_pool) { + workbuffer = workbuffer_; + entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1; + max_detail_count = MaxDetailEntries; + frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params); + const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)}; + max_frames = frame_count - 1; + translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size); + + // The first frame is the "current" frame we're writing to. + auto buffer_offset{workbuffer.data()}; + frame_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion1); + entry_buffer = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset), entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1); + detail_buffer = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset), max_detail_count}; + + // After the current, is a ringbuffer of history frames, the current frame will be copied here + // before a new frame is written. + frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size); + + // If there's room for any history frames. + if (frame_count >= 2) { + buffer_offset = frame_history.data(); + frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion1); + frame_history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset), + entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1); + frame_history_details = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset), + max_detail_count}; + } else { + frame_history_header = {}; + frame_history_entries = {}; + frame_history_details = {}; + } + + target_node_id = 0; + version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat()); + entry_count = 0; + detail_count = 0; + frame_header->entry_count = 0; + frame_header->detail_count = 0; + output_frame_index = 0; + last_output_frame_index = 0; + is_initialized = true; +} + +template <> +bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, + PerformanceEntryVersion1, PerformanceDetailVersion1>::IsInitialized() + const { + return is_initialized; +} + +template <> +u32 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, + PerformanceEntryVersion1, + PerformanceDetailVersion1>::CopyHistories(u8* out_buffer, u64 out_size) { + if (out_buffer == nullptr || out_size == 0 || !is_initialized) { + return 0; + } + + // Are there any new frames waiting to be output? + if (last_output_frame_index == output_frame_index) { + return 0; + } + + PerformanceFrameHeaderVersion1* out_header{nullptr}; + u32 out_history_size{0}; + + while (last_output_frame_index != output_frame_index) { + PerformanceFrameHeaderVersion1* history_header{nullptr}; + std::span<PerformanceEntryVersion1> history_entries{}; + std::span<PerformanceDetailVersion1> history_details{}; + + if (max_frames > 0) { + auto frame_offset{&frame_history[last_output_frame_index * frame_size]}; + history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(frame_offset); + frame_offset += sizeof(PerformanceFrameHeaderVersion1); + history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(frame_offset), + history_header->entry_count}; + frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion1); + history_details = {reinterpret_cast<PerformanceDetailVersion1*>(frame_offset), + history_header->detail_count}; + } else { + // Original code does not break here, but will crash when trying to dereference the + // header in the next if, so let's just skip this frame and continue... + // Hopefully this will not happen. + LOG_WARNING(Service_Audio, + "max_frames should not be 0! Skipping frame to avoid a crash"); + last_output_frame_index++; + continue; + } + + if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion1) + + history_header->detail_count * sizeof(PerformanceDetailVersion1) + + 2 * sizeof(PerformanceFrameHeaderVersion1)) { + break; + } + + u32 out_offset{sizeof(PerformanceFrameHeaderVersion1)}; + auto out_entries{std::span<PerformanceEntryVersion1>( + reinterpret_cast<PerformanceEntryVersion1*>(out_buffer + out_offset), + history_header->entry_count)}; + u32 out_entry_count{0}; + u32 total_processing_time{0}; + for (auto& history_entry : history_entries) { + if (history_entry.processed_time > 0 || history_entry.start_time > 0) { + out_entries[out_entry_count++] = history_entry; + total_processing_time += history_entry.processed_time; + } + } + + out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion1)); + auto out_details{std::span<PerformanceDetailVersion1>( + reinterpret_cast<PerformanceDetailVersion1*>(out_buffer + out_offset), + history_header->detail_count)}; + u32 out_detail_count{0}; + for (auto& history_detail : history_details) { + if (history_detail.processed_time > 0 || history_detail.start_time > 0) { + out_details[out_detail_count++] = history_detail; + } + } + + out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion1)); + out_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(out_buffer); + out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F'); + out_header->entry_count = out_entry_count; + out_header->detail_count = out_detail_count; + out_header->next_offset = out_offset; + out_header->total_processing_time = total_processing_time; + out_header->frame_index = history_header->frame_index; + + out_history_size += out_offset; + + out_buffer += out_offset; + out_size -= out_offset; + last_output_frame_index = (last_output_frame_index + 1) % max_frames; + } + + // We're out of frames to output, so if there's enough left in the output buffer for another + // header, and we output at least 1 frame, set the next header to null. + if (out_size > sizeof(PerformanceFrameHeaderVersion1) && out_header != nullptr) { + std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion1)); + } + + return out_history_size; +} + +template <> +bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, + PerformanceEntryVersion1, PerformanceDetailVersion1>:: + GetNextEntry([[maybe_unused]] PerformanceEntryAddresses& addresses, [[maybe_unused]] u32** unk, + [[maybe_unused]] PerformanceSysDetailType sys_detail_type, + [[maybe_unused]] s32 node_id) { + return false; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, + PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceEntryType entry_type, + const s32 node_id) { + if (!is_initialized) { + return false; + } + + addresses.translated_address = translated_buffer; + addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceFrameHeaderVersion1, entry_count); + + auto entry{&entry_buffer[entry_count++]}; + addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceEntryVersion1, start_time); + addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceEntryVersion1, processed_time); + + std::memset(entry, 0, sizeof(PerformanceEntryVersion1)); + entry->node_id = node_id; + entry->entry_type = entry_type; + return true; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, + PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceDetailType detail_type, + const PerformanceEntryType entry_type, + const s32 node_id) { + if (!is_initialized || detail_count > MaxDetailEntries) { + return false; + } + + auto detail{&detail_buffer[detail_count++]}; + + addresses.translated_address = translated_buffer; + addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceFrameHeaderVersion1, detail_count); + addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion1, start_time); + addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion1, processed_time); + + std::memset(detail, 0, sizeof(PerformanceDetailVersion1)); + detail->node_id = node_id; + detail->entry_type = entry_type; + detail->detail_type = detail_type; + return true; +} + +template <> +void PerformanceManagerImpl< + PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, + PerformanceDetailVersion1>::TapFrame([[maybe_unused]] bool dsp_behind, + [[maybe_unused]] u32 voices_dropped, + [[maybe_unused]] u64 rendering_start_tick) { + if (!is_initialized) { + return; + } + + if (max_frames > 0) { + if (!frame_history.empty() && !workbuffer.empty()) { + auto history_frame = reinterpret_cast<PerformanceFrameHeaderVersion1*>( + &frame_history[output_frame_index * frame_size]); + std::memcpy(history_frame, workbuffer.data(), frame_size); + history_frame->frame_index = history_frame_index++; + } + output_frame_index = (output_frame_index + 1) % max_frames; + } + + entry_count = 0; + detail_count = 0; + frame_header->entry_count = 0; + frame_header->detail_count = 0; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, + PerformanceDetailVersion1>::IsDetailTarget(const u32 target_node_id_) const { + return target_node_id == target_node_id_; +} + +template <> +void PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, + PerformanceEntryVersion1, + PerformanceDetailVersion1>::SetDetailTarget(const u32 target_node_id_) { + target_node_id = target_node_id_; +} + +template <> +void PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size, + const AudioRendererParameterInternal& params, + const BehaviorInfo& behavior, + const MemoryPoolInfo& memory_pool) { + workbuffer = workbuffer_; + entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1; + max_detail_count = MaxDetailEntries; + frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params); + const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)}; + max_frames = frame_count - 1; + translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size); + + // The first frame is the "current" frame we're writing to. + auto buffer_offset{workbuffer.data()}; + frame_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion2); + entry_buffer = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset), entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2); + detail_buffer = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset), max_detail_count}; + + // After the current, is a ringbuffer of history frames, the current frame will be copied here + // before a new frame is written. + frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size); + + // If there's room for any history frames. + if (frame_count >= 2) { + buffer_offset = frame_history.data(); + frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion2); + frame_history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset), + entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2); + frame_history_details = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset), + max_detail_count}; + } else { + frame_history_header = {}; + frame_history_entries = {}; + frame_history_details = {}; + } + + target_node_id = 0; + version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat()); + entry_count = 0; + detail_count = 0; + frame_header->entry_count = 0; + frame_header->detail_count = 0; + output_frame_index = 0; + last_output_frame_index = 0; + is_initialized = true; +} + +template <> +bool PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, + PerformanceEntryVersion2, PerformanceDetailVersion2>::IsInitialized() + const { + return is_initialized; +} + +template <> +u32 PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, + PerformanceEntryVersion2, + PerformanceDetailVersion2>::CopyHistories(u8* out_buffer, u64 out_size) { + if (out_buffer == nullptr || out_size == 0 || !is_initialized) { + return 0; + } + + // Are there any new frames waiting to be output? + if (last_output_frame_index == output_frame_index) { + return 0; + } + + PerformanceFrameHeaderVersion2* out_header{nullptr}; + u32 out_history_size{0}; + + while (last_output_frame_index != output_frame_index) { + PerformanceFrameHeaderVersion2* history_header{nullptr}; + std::span<PerformanceEntryVersion2> history_entries{}; + std::span<PerformanceDetailVersion2> history_details{}; + + if (max_frames > 0) { + auto frame_offset{&frame_history[last_output_frame_index * frame_size]}; + history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(frame_offset); + frame_offset += sizeof(PerformanceFrameHeaderVersion2); + history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(frame_offset), + history_header->entry_count}; + frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion2); + history_details = {reinterpret_cast<PerformanceDetailVersion2*>(frame_offset), + history_header->detail_count}; + } else { + // Original code does not break here, but will crash when trying to dereference the + // header in the next if, so let's just skip this frame and continue... + // Hopefully this will not happen. + LOG_WARNING(Service_Audio, + "max_frames should not be 0! Skipping frame to avoid a crash"); + last_output_frame_index++; + continue; + } + + if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion2) + + history_header->detail_count * sizeof(PerformanceDetailVersion2) + + 2 * sizeof(PerformanceFrameHeaderVersion2)) { + break; + } + + u32 out_offset{sizeof(PerformanceFrameHeaderVersion2)}; + auto out_entries{std::span<PerformanceEntryVersion2>( + reinterpret_cast<PerformanceEntryVersion2*>(out_buffer + out_offset), + history_header->entry_count)}; + u32 out_entry_count{0}; + u32 total_processing_time{0}; + for (auto& history_entry : history_entries) { + if (history_entry.processed_time > 0 || history_entry.start_time > 0) { + out_entries[out_entry_count++] = history_entry; + total_processing_time += history_entry.processed_time; + } + } + + out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion2)); + auto out_details{std::span<PerformanceDetailVersion2>( + reinterpret_cast<PerformanceDetailVersion2*>(out_buffer + out_offset), + history_header->detail_count)}; + u32 out_detail_count{0}; + for (auto& history_detail : history_details) { + if (history_detail.processed_time > 0 || history_detail.start_time > 0) { + out_details[out_detail_count++] = history_detail; + } + } + + out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion2)); + out_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(out_buffer); + out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F'); + out_header->entry_count = out_entry_count; + out_header->detail_count = out_detail_count; + out_header->next_offset = out_offset; + out_header->total_processing_time = total_processing_time; + out_header->voices_dropped = history_header->voices_dropped; + out_header->start_time = history_header->start_time; + out_header->frame_index = history_header->frame_index; + out_header->render_time_exceeded = history_header->render_time_exceeded; + + out_history_size += out_offset; + + out_buffer += out_offset; + out_size -= out_offset; + last_output_frame_index = (last_output_frame_index + 1) % max_frames; + } + + // We're out of frames to output, so if there's enough left in the output buffer for another + // header, and we output at least 1 frame, set the next header to null. + if (out_size > sizeof(PerformanceFrameHeaderVersion2) && out_header != nullptr) { + std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion2)); + } + + return out_history_size; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, + const PerformanceSysDetailType sys_detail_type, + const s32 node_id) { + if (!is_initialized || detail_count > MaxDetailEntries) { + return false; + } + + auto detail{&detail_buffer[detail_count++]}; + + addresses.translated_address = translated_buffer; + addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceFrameHeaderVersion2, detail_count); + addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion2, start_time); + addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion2, processed_time); + + std::memset(detail, 0, sizeof(PerformanceDetailVersion2)); + detail->node_id = node_id; + detail->detail_type = static_cast<PerformanceDetailType>(sys_detail_type); + + if (unk) { + *unk = &detail->unk_10; + } + return true; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceEntryType entry_type, + const s32 node_id) { + if (!is_initialized) { + return false; + } + + auto entry{&entry_buffer[entry_count++]}; + + addresses.translated_address = translated_buffer; + addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceFrameHeaderVersion2, entry_count); + addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceEntryVersion2, start_time); + addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceEntryVersion2, processed_time); + + std::memset(entry, 0, sizeof(PerformanceEntryVersion2)); + entry->node_id = node_id; + entry->entry_type = entry_type; + return true; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceDetailType detail_type, + const PerformanceEntryType entry_type, + const s32 node_id) { + if (!is_initialized || detail_count > MaxDetailEntries) { + return false; + } + + auto detail{&detail_buffer[detail_count++]}; + + addresses.translated_address = translated_buffer; + addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceFrameHeaderVersion2, detail_count); + addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion2, start_time); + addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion2, processed_time); + + std::memset(detail, 0, sizeof(PerformanceDetailVersion2)); + detail->node_id = node_id; + detail->entry_type = entry_type; + detail->detail_type = detail_type; + return true; +} + +template <> +void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, + PerformanceEntryVersion2, + PerformanceDetailVersion2>::TapFrame(const bool dsp_behind, + const u32 voices_dropped, + const u64 rendering_start_tick) { + if (!is_initialized) { + return; + } + + if (max_frames > 0) { + if (!frame_history.empty() && !workbuffer.empty()) { + auto history_frame{reinterpret_cast<PerformanceFrameHeaderVersion2*>( + &frame_history[output_frame_index * frame_size])}; + std::memcpy(history_frame, workbuffer.data(), frame_size); + history_frame->render_time_exceeded = dsp_behind; + history_frame->voices_dropped = voices_dropped; + history_frame->start_time = rendering_start_tick; + history_frame->frame_index = history_frame_index++; + } + output_frame_index = (output_frame_index + 1) % max_frames; + } + + entry_count = 0; + detail_count = 0; + frame_header->entry_count = 0; + frame_header->detail_count = 0; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::IsDetailTarget(const u32 target_node_id_) const { + return target_node_id == target_node_id_; +} + +template <> +void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, + PerformanceEntryVersion2, + PerformanceDetailVersion2>::SetDetailTarget(const u32 target_node_id_) { + target_node_id = target_node_id_; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h new file mode 100644 index 000000000..b82176bef --- /dev/null +++ b/src/audio_core/renderer/performance/performance_manager.h @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <chrono> +#include <memory> +#include <span> + +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/renderer/performance/performance_detail.h" +#include "audio_core/renderer/performance/performance_entry.h" +#include "audio_core/renderer/performance/performance_entry_addresses.h" +#include "audio_core/renderer/performance/performance_frame_header.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class BehaviorInfo; +class MemoryPoolInfo; + +enum class PerformanceVersion { + Version1, + Version2, +}; + +enum class PerformanceSysDetailType { + PcmInt16 = 15, + PcmFloat = 16, + Adpcm = 17, + LightLimiter = 37, +}; + +enum class PerformanceState { + Invalid, + Start, + Stop, +}; + +/** + * Manages performance information. + * + * The performance buffer is split into frames, each comprised of: + * Frame header - Information about the number of entries/details and some others + * Entries - Created when starting to generate types of commands, such as voice + * commands, mix commands, sink commands etc. Details - Created for specific commands + * within each group. Up to MaxDetailEntries per frame. + * + * A current frame is written to by the AudioRenderer, and before it processes the next command + * list, the current frame is copied to a ringbuffer of history frames. These frames are then + * output back to the game if it supplies a performance buffer to RequestUpdate. + * + * Two versions currently exist, version 2 adds a few extra fields to the header, and a new + * SysDetail type which is seemingly unused. + */ +class PerformanceManager { +public: + static constexpr size_t MaxDetailEntries = 100; + + struct InParameter { + /* 0x00 */ s32 target_node_id; + /* 0x04 */ char unk04[0xC]; + }; + static_assert(sizeof(InParameter) == 0x10, + "PerformanceManager::InParameter has the wrong size!"); + + struct OutStatus { + /* 0x00 */ s32 history_size; + /* 0x04 */ char unk04[0xC]; + }; + static_assert(sizeof(OutStatus) == 0x10, "PerformanceManager::OutStatus has the wrong size!"); + + /** + * Calculate the required size for the performance workbuffer. + * + * @param behavior - Check which version is supported. + * @param params - Input parameters. + * @return Required workbuffer size. + */ + static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( + const BehaviorInfo& behavior, const AudioRendererParameterInternal& params) { + u64 entry_count{params.voices + params.effects + params.sub_mixes + params.sinks + 1}; + switch (behavior.GetPerformanceMetricsDataFormat()) { + case 1: + return sizeof(PerformanceFrameHeaderVersion1) + + PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) + + entry_count * sizeof(PerformanceEntryVersion1); + case 2: + return sizeof(PerformanceFrameHeaderVersion2) + + PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion2) + + entry_count * sizeof(PerformanceEntryVersion2); + } + + LOG_WARNING(Service_Audio, "Invalid PerformanceMetrics version, assuming version 1"); + return sizeof(PerformanceFrameHeaderVersion1) + + PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) + + entry_count * sizeof(PerformanceEntryVersion1); + } + + virtual ~PerformanceManager() = default; + + /** + * Initialize the performance manager. + * + * @param workbuffer - Workbuffer to use for performance frames. + * @param workbuffer_size - Size of the workbuffer. + * @param params - Input parameters. + * @param behavior - Behaviour to check version and data format. + * @param memory_pool - Used to translate the workbuffer address for the DSP. + */ + virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, + const AudioRendererParameterInternal& params, + const BehaviorInfo& behavior, const MemoryPoolInfo& memory_pool); + + /** + * Check if the manager is initialized. + * + * @return True if initialized, otherwise false. + */ + virtual bool IsInitialized() const; + + /** + * Copy the waiting performance frames to the output buffer. + * + * @param out_buffer - Output buffer to store performance frames. + * @param out_size - Size of the output buffer. + * @return Size in bytes that were written to the buffer. + */ + virtual u32 CopyHistories(u8* out_buffer, u64 out_size); + + /** + * Setup a new sys detail in the current frame, filling in addresses with offsets to the + * current workbuffer, to be written by the AudioRenderer. Note: This version is + * unused/incomplete. + * + * @param addresses - Filled with pointers to the new entry, which should be passed to + * the AudioRenderer with Performance commands to be written. + * @param unk - Unknown. + * @param sys_detail_type - Sys detail type. + * @param node_id - Node id for this entry. + * @return True if a new entry was created and the offsets are valid, otherwise false. + */ + virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, + PerformanceSysDetailType sys_detail_type, s32 node_id); + + /** + * Setup a new entry in the current frame, filling in addresses with offsets to the current + * workbuffer, to be written by the AudioRenderer. + * + * @param addresses - Filled with pointers to the new entry, which should be passed to + * the AudioRenderer with Performance commands to be written. + * @param entry_type - The type of this entry. See PerformanceEntryType + * @param node_id - Node id for this entry. + * @return True if a new entry was created and the offsets are valid, otherwise false. + */ + virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type, + s32 node_id); + + /** + * Setup a new detail in the current frame, filling in addresses with offsets to the current + * workbuffer, to be written by the AudioRenderer. + * + * @param addresses - Filled with pointers to the new detail, which should be passed + * to the AudioRenderer with Performance commands to be written. + * @param entry_type - The type of this detail. See PerformanceEntryType + * @param node_id - Node id for this detail. + * @return True if a new detail was created and the offsets are valid, otherwise false. + */ + virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, + PerformanceDetailType detail_type, PerformanceEntryType entry_type, + s32 node_id); + + /** + * Save the current frame to the ring buffer. + * + * @param dsp_behind - Did the AudioRenderer fall behind and not + * finish processing the command list? + * @param voices_dropped - The number of voices that were dropped. + * @param rendering_start_tick - The tick rendering started. + */ + virtual void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick); + + /** + * Check if the node id is a detail type. + * + * @return True if the node is a detail type, otherwise false. + */ + virtual bool IsDetailTarget(u32 target_node_id) const; + + /** + * Set the given node to be a detail type. + * + * @param target_node_id - Node to set. + */ + virtual void SetDetailTarget(u32 target_node_id); + +private: + /** + * Create the performance manager. + * + * @param version - Performance version to create. + */ + void CreateImpl(size_t version); + + std::unique_ptr<PerformanceManager> + /// Impl for the performance manager, may be version 1 or 2. + impl; +}; + +template <PerformanceVersion Version, typename FrameHeaderVersion, typename EntryVersion, + typename DetailVersion> +class PerformanceManagerImpl : public PerformanceManager { +public: + void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, + const AudioRendererParameterInternal& params, const BehaviorInfo& behavior, + const MemoryPoolInfo& memory_pool) override; + bool IsInitialized() const override; + u32 CopyHistories(u8* out_buffer, u64 out_size) override; + bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, + PerformanceSysDetailType sys_detail_type, s32 node_id) override; + bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type, + s32 node_id) override; + bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceDetailType detail_type, + PerformanceEntryType entry_type, s32 node_id) override; + void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick) override; + bool IsDetailTarget(u32 target_node_id) const override; + void SetDetailTarget(u32 target_node_id) override; + +private: + /// Workbuffer used to store the current performance frame + std::span<u8> workbuffer{}; + /// DSP address of the workbuffer, used by the AudioRenderer + CpuAddr translated_buffer{}; + /// Current frame index + u32 history_frame_index{}; + /// Current frame header + FrameHeaderVersion* frame_header{}; + /// Current frame entry buffer + std::span<EntryVersion> entry_buffer{}; + /// Current frame detail buffer + std::span<DetailVersion> detail_buffer{}; + /// Current frame entry count + u32 entry_count{}; + /// Current frame detail count + u32 detail_count{}; + /// Ringbuffer of previous frames + std::span<u8> frame_history{}; + /// Current history frame header + FrameHeaderVersion* frame_history_header{}; + /// Current history entry buffer + std::span<EntryVersion> frame_history_entries{}; + /// Current history detail buffer + std::span<DetailVersion> frame_history_details{}; + /// Current history ringbuffer write index + u32 output_frame_index{}; + /// Last history frame index that was written back to the game + u32 last_output_frame_index{}; + /// Maximum number of history frames in the ringbuffer + u32 max_frames{}; + /// Number of entries per frame + u32 entries_per_frame{}; + /// Maximum number of details per frame + u32 max_detail_count{}; + /// Frame size in bytes + u64 frame_size{}; + /// Is the performance manager initialized? + bool is_initialized{}; + /// Target node id + u32 target_node_id{}; + /// Performance version in use + PerformanceVersion version{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp new file mode 100644 index 000000000..d91f10402 --- /dev/null +++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/memory/pool_mapper.h" +#include "audio_core/renderer/sink/circular_buffer_sink_info.h" +#include "audio_core/renderer/upsampler/upsampler_manager.h" + +namespace AudioCore::AudioRenderer { + +CircularBufferSinkInfo::CircularBufferSinkInfo() { + state.fill(0); + parameter.fill(0); + type = Type::CircularBufferSink; + + auto state_{reinterpret_cast<CircularBufferState*>(state.data())}; + state_->address_info.Setup(0, 0); +} + +void CircularBufferSinkInfo::CleanUp() { + auto state_{reinterpret_cast<DeviceState*>(state.data())}; + + if (state_->upsampler_info) { + state_->upsampler_info->manager->Free(state_->upsampler_info); + state_->upsampler_info = nullptr; + } + + parameter.fill(0); + type = Type::Invalid; +} + +void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + const InParameter& in_params, const PoolMapper& pool_mapper) { + const auto buffer_params{ + reinterpret_cast<const CircularBufferInParameter*>(&in_params.circular_buffer)}; + auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())}; + auto current_state{reinterpret_cast<CircularBufferState*>(state.data())}; + + if (in_use == buffer_params->in_use && !buffer_unmapped) { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + out_status.writeOffset = current_state->last_pos2; + return; + } + + node_id = in_params.node_id; + in_use = in_params.in_use; + + if (in_use) { + buffer_unmapped = + !pool_mapper.TryAttachBuffer(error_info, current_state->address_info, + buffer_params->cpu_address, buffer_params->size); + *current_params = *buffer_params; + } else { + *current_params = *buffer_params; + } + out_status.writeOffset = current_state->last_pos2; +} + +void CircularBufferSinkInfo::UpdateForCommandGeneration() { + if (in_use) { + auto params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())}; + auto state_{reinterpret_cast<CircularBufferState*>(state.data())}; + + const auto pos{state_->current_pos}; + state_->last_pos2 = state_->last_pos; + state_->last_pos = pos; + + state_->current_pos += static_cast<s32>(params->input_count * params->sample_count * + GetSampleFormatByteSize(SampleFormat::PcmInt16)); + if (params->size > 0) { + state_->current_pos %= params->size; + } + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.h b/src/audio_core/renderer/sink/circular_buffer_sink_info.h new file mode 100644 index 000000000..3356213ea --- /dev/null +++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.h @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/sink/sink_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Info for a circular buffer sink. + */ +class CircularBufferSinkInfo : public SinkInfoBase { +public: + CircularBufferSinkInfo(); + + /** + * Clean up for info, resetting it to a default state. + */ + void CleanUp() override; + + /** + * Update the info according to parameters, and write the current state to out_status. + * + * @param error_info - Output error code. + * @param out_status - Output status. + * @param in_params - Input parameters. + * @param pool_mapper - Used to map the circular buffer. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + const InParameter& in_params, const PoolMapper& pool_mapper) override; + + /** + * Update the circular buffer on command generation, incrementing its current offsets. + */ + void UpdateForCommandGeneration() override; +}; +static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase), + "CircularBufferSinkInfo is too large!"); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/device_sink_info.cpp b/src/audio_core/renderer/sink/device_sink_info.cpp new file mode 100644 index 000000000..b7b3d6f1d --- /dev/null +++ b/src/audio_core/renderer/sink/device_sink_info.cpp @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/sink/device_sink_info.h" +#include "audio_core/renderer/upsampler/upsampler_manager.h" + +namespace AudioCore::AudioRenderer { + +DeviceSinkInfo::DeviceSinkInfo() { + state.fill(0); + parameter.fill(0); + type = Type::DeviceSink; +} + +void DeviceSinkInfo::CleanUp() { + auto state_{reinterpret_cast<DeviceState*>(state.data())}; + + if (state_->upsampler_info) { + state_->upsampler_info->manager->Free(state_->upsampler_info); + state_->upsampler_info = nullptr; + } + + parameter.fill(0); + type = Type::Invalid; +} + +void DeviceSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + const InParameter& in_params, + [[maybe_unused]] const PoolMapper& pool_mapper) { + + const auto device_params{reinterpret_cast<const DeviceInParameter*>(&in_params.device)}; + auto current_params{reinterpret_cast<DeviceInParameter*>(parameter.data())}; + + if (in_use == in_params.in_use) { + current_params->downmix_enabled = device_params->downmix_enabled; + current_params->downmix_coeff = device_params->downmix_coeff; + } else { + type = in_params.type; + in_use = in_params.in_use; + node_id = in_params.node_id; + *current_params = *device_params; + } + + auto current_state{reinterpret_cast<DeviceState*>(state.data())}; + + for (size_t i = 0; i < current_state->downmix_coeff.size(); i++) { + current_state->downmix_coeff[i] = current_params->downmix_coeff[i]; + } + + std::memset(&out_status, 0, sizeof(OutStatus)); + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void DeviceSinkInfo::UpdateForCommandGeneration() {} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/device_sink_info.h b/src/audio_core/renderer/sink/device_sink_info.h new file mode 100644 index 000000000..a1c441454 --- /dev/null +++ b/src/audio_core/renderer/sink/device_sink_info.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/sink/sink_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Info for a device sink. + */ +class DeviceSinkInfo : public SinkInfoBase { +public: + DeviceSinkInfo(); + + /** + * Clean up for info, resetting it to a default state. + */ + void CleanUp() override; + + /** + * Update the info according to parameters, and write the current state to out_status. + * + * @param error_info - Output error code. + * @param out_status - Output status. + * @param in_params - Input parameters. + * @param pool_mapper - Unused. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + const InParameter& in_params, const PoolMapper& pool_mapper) override; + + /** + * Update the device sink on command generation, unused. + */ + void UpdateForCommandGeneration() override; +}; +static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!"); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/sink_context.cpp b/src/audio_core/renderer/sink/sink_context.cpp new file mode 100644 index 000000000..634bc1cf9 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_context.cpp @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/sink/sink_context.h" + +namespace AudioCore::AudioRenderer { + +void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) { + sink_infos = sink_infos_; + sink_count = sink_count_; +} + +SinkInfoBase* SinkContext::GetInfo(const u32 index) { + return &sink_infos[index]; +} + +u32 SinkContext::GetCount() const { + return sink_count; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/sink_context.h b/src/audio_core/renderer/sink/sink_context.h new file mode 100644 index 000000000..185572e29 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_context.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/sink/sink_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Manages output sinks. + */ +class SinkContext { +public: + /** + * Initialize the sink context. + * + * @param sink_infos - Workbuffer for the sinks. + * @param sink_count - Number of sinks in the buffer. + */ + void Initialize(std::span<SinkInfoBase> sink_infos, u32 sink_count); + + /** + * Get a given index's info. + * + * @param index - Sink index to get. + * @return The sink info base for the given index. + */ + SinkInfoBase* GetInfo(u32 index); + + /** + * Get the current number of sinks. + * + * @return The number of sinks. + */ + u32 GetCount() const; + +private: + /// Buffer of sink infos + std::span<SinkInfoBase> sink_infos{}; + /// Number of sinks in the buffer + u32 sink_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/sink_info_base.cpp b/src/audio_core/renderer/sink/sink_info_base.cpp new file mode 100644 index 000000000..4279beaa0 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_info_base.cpp @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/memory/pool_mapper.h" +#include "audio_core/renderer/sink/sink_info_base.h" + +namespace AudioCore::AudioRenderer { + +void SinkInfoBase::CleanUp() { + type = Type::Invalid; +} + +void SinkInfoBase::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + [[maybe_unused]] const InParameter& in_params, + [[maybe_unused]] const PoolMapper& pool_mapper) { + std::memset(&out_status, 0, sizeof(OutStatus)); + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void SinkInfoBase::UpdateForCommandGeneration() {} + +SinkInfoBase::DeviceState* SinkInfoBase::GetDeviceState() { + return reinterpret_cast<DeviceState*>(state.data()); +} + +SinkInfoBase::Type SinkInfoBase::GetType() const { + return type; +} + +bool SinkInfoBase::IsUsed() const { + return in_use; +} + +bool SinkInfoBase::ShouldSkip() const { + return buffer_unmapped; +} + +u32 SinkInfoBase::GetNodeId() const { + return node_id; +} + +u8* SinkInfoBase::GetState() { + return state.data(); +} + +u8* SinkInfoBase::GetParameter() { + return parameter.data(); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/sink_info_base.h b/src/audio_core/renderer/sink/sink_info_base.h new file mode 100644 index 000000000..a1b855f20 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_info_base.h @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/memory/address_info.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +struct UpsamplerInfo; +class PoolMapper; + +/** + * Base for the circular buffer and device sinks, holding their states for the AudioRenderer and + * their parametetrs for generating sink commands. + */ +class SinkInfoBase { +public: + enum class Type : u8 { + Invalid, + DeviceSink, + CircularBufferSink, + }; + + struct DeviceInParameter { + /* 0x000 */ char name[0x100]; + /* 0x100 */ u32 input_count; + /* 0x104 */ std::array<s8, MaxChannels> inputs; + /* 0x10A */ char unk10A[0x1]; + /* 0x10B */ bool downmix_enabled; + /* 0x10C */ std::array<f32, 4> downmix_coeff; + }; + static_assert(sizeof(DeviceInParameter) == 0x11C, "DeviceInParameter has the wrong size!"); + + struct DeviceState { + /* 0x00 */ UpsamplerInfo* upsampler_info; + /* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff; + /* 0x18 */ char unk18[0x18]; + }; + static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!"); + + struct CircularBufferInParameter { + /* 0x00 */ u64 cpu_address; + /* 0x08 */ u32 size; + /* 0x0C */ u32 input_count; + /* 0x10 */ u32 sample_count; + /* 0x14 */ u32 previous_pos; + /* 0x18 */ SampleFormat format; + /* 0x1C */ std::array<s8, MaxChannels> inputs; + /* 0x22 */ bool in_use; + /* 0x23 */ char unk23[0x5]; + }; + static_assert(sizeof(CircularBufferInParameter) == 0x28, + "CircularBufferInParameter has the wrong size!"); + + struct CircularBufferState { + /* 0x00 */ u32 last_pos2; + /* 0x04 */ s32 current_pos; + /* 0x08 */ u32 last_pos; + /* 0x0C */ char unk0C[0x4]; + /* 0x10 */ AddressInfo address_info; + }; + static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!"); + + struct InParameter { + /* 0x000 */ Type type; + /* 0x001 */ bool in_use; + /* 0x004 */ u32 node_id; + /* 0x008 */ char unk08[0x18]; + union { + /* 0x020 */ DeviceInParameter device; + /* 0x020 */ CircularBufferInParameter circular_buffer; + }; + }; + static_assert(sizeof(InParameter) == 0x140, "SinkInfoBase::InParameter has the wrong size!"); + + struct OutStatus { + /* 0x00 */ u32 writeOffset; + /* 0x04 */ char unk04[0x1C]; + }; // size == 0x20 + static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!"); + + virtual ~SinkInfoBase() = default; + + /** + * Clean up for info, resetting it to a default state. + */ + virtual void CleanUp(); + + /** + * Update the info according to parameters, and write the current state to out_status. + * + * @param error_info - Output error code. + * @param out_status - Output status. + * @param in_params - Input parameters. + * @param pool_mapper - Used to map the circular buffer. + */ + virtual void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + [[maybe_unused]] const InParameter& in_params, + [[maybe_unused]] const PoolMapper& pool_mapper); + + /** + * Update the circular buffer on command generation, incrementing its current offsets. + */ + virtual void UpdateForCommandGeneration(); + + /** + * Get the state as a device sink. + * + * @return Device state. + */ + DeviceState* GetDeviceState(); + + /** + * Get the type of this sink. + * + * @return Either Device, Circular, or Invalid. + */ + Type GetType() const; + + /** + * Check if this sink is in use. + * + * @return True if used, otherwise false. + */ + bool IsUsed() const; + + /** + * Check if this sink should be skipped for updates. + * + * @return True if it should be skipped, otherwise false. + */ + bool ShouldSkip() const; + + /** + * Get the node if of this sink. + * + * @return Node id for this sink. + */ + u32 GetNodeId() const; + + /** + * Get the state of this sink. + * + * @return Pointer to the state, must be cast to the correct type. + */ + u8* GetState(); + + /** + * Get the parameters of this sink. + * + * @return Pointer to the parameters, must be cast to the correct type. + */ + u8* GetParameter(); + +protected: + /// Type of this sink + Type type{Type::Invalid}; + /// Is this sink in use? + bool in_use{}; + /// Is this sink's buffer unmapped? Circular only + bool buffer_unmapped{}; + /// Node id for this sink + u32 node_id{}; + /// State buffer for this sink + std::array<u8, std::max(sizeof(DeviceState), sizeof(CircularBufferState))> state{}; + /// Parameter buffer for this sink + std::array<u8, std::max(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> + parameter{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp new file mode 100644 index 000000000..7a23ba43f --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_context.cpp @@ -0,0 +1,217 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/common/workbuffer_allocator.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/splitter/splitter_context.h" +#include "common/alignment.h" + +namespace AudioCore::AudioRenderer { + +SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id, + const s32 destination_id) { + return splitter_infos[splitter_id].GetData(destination_id); +} + +SplitterInfo& SplitterContext::GetInfo(const s32 splitter_id) { + return splitter_infos[splitter_id]; +} + +u32 SplitterContext::GetDataCount() const { + return destinations_count; +} + +u32 SplitterContext::GetInfoCount() const { + return info_count; +} + +SplitterDestinationData& SplitterContext::GetData(const u32 index) { + return splitter_destinations[index]; +} + +void SplitterContext::Setup(std::span<SplitterInfo> splitter_infos_, const u32 splitter_info_count_, + SplitterDestinationData* splitter_destinations_, + const u32 destination_count_, const bool splitter_bug_fixed_) { + splitter_infos = splitter_infos_; + info_count = splitter_info_count_; + splitter_destinations = splitter_destinations_; + destinations_count = destination_count_; + splitter_bug_fixed = splitter_bug_fixed_; +} + +bool SplitterContext::UsingSplitter() const { + return splitter_infos.size() > 0 && info_count > 0 && splitter_destinations != nullptr && + destinations_count > 0; +} + +void SplitterContext::ClearAllNewConnectionFlag() { + for (s32 i = 0; i < info_count; i++) { + splitter_infos[i].SetNewConnectionFlag(); + } +} + +bool SplitterContext::Initialize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params, + WorkbufferAllocator& allocator) { + if (behavior.IsSplitterSupported() && params.splitter_infos > 0 && + params.splitter_destinations > 0) { + splitter_infos = allocator.Allocate<SplitterInfo>(params.splitter_infos, 0x10); + + for (u32 i = 0; i < params.splitter_infos; i++) { + std::construct_at<SplitterInfo>(&splitter_infos[i], static_cast<s32>(i)); + } + + if (splitter_infos.size() == 0) { + splitter_infos = {}; + return false; + } + + splitter_destinations = + allocator.Allocate<SplitterDestinationData>(params.splitter_destinations, 0x10).data(); + + for (s32 i = 0; i < params.splitter_destinations; i++) { + std::construct_at<SplitterDestinationData>(&splitter_destinations[i], i); + } + + if (params.splitter_destinations <= 0) { + splitter_infos = {}; + splitter_destinations = nullptr; + return false; + } + + Setup(splitter_infos, params.splitter_infos, splitter_destinations, + params.splitter_destinations, behavior.IsSplitterBugFixed()); + } + return true; +} + +bool SplitterContext::Update(const u8* input, u32& consumed_size) { + auto in_params{reinterpret_cast<const InParameterHeader*>(input)}; + + if (destinations_count == 0 || info_count == 0) { + consumed_size = 0; + return true; + } + + if (in_params->magic != GetSplitterInParamHeaderMagic()) { + consumed_size = 0; + return false; + } + + for (auto& splitter_info : splitter_infos) { + splitter_info.ClearNewConnectionFlag(); + } + + u32 offset{sizeof(InParameterHeader)}; + offset = UpdateInfo(input, offset, in_params->info_count); + offset = UpdateData(input, offset, in_params->destination_count); + + consumed_size = Common::AlignUp(offset, 0x10); + return true; +} + +u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_count) { + for (u32 i = 0; i < splitter_count; i++) { + auto info_header{reinterpret_cast<const SplitterInfo::InParameter*>(input + offset)}; + + if (info_header->magic != GetSplitterInfoMagic()) { + continue; + } + + if (info_header->id < 0 || info_header->id > info_count) { + break; + } + + auto& info{splitter_infos[info_header->id]}; + RecomposeDestination(info, info_header); + + offset += info.Update(info_header); + } + + return offset; +} + +u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) { + for (u32 i = 0; i < count; i++) { + auto data_header{ + reinterpret_cast<const SplitterDestinationData::InParameter*>(input + offset)}; + + if (data_header->magic != GetSplitterSendDataMagic()) { + continue; + } + + if (data_header->id < 0 || data_header->id > destinations_count) { + continue; + } + + splitter_destinations[data_header->id].Update(*data_header); + offset += sizeof(SplitterDestinationData::InParameter); + } + + return offset; +} + +void SplitterContext::UpdateInternalState() { + for (s32 i = 0; i < info_count; i++) { + splitter_infos[i].UpdateInternalState(); + } +} + +void SplitterContext::RecomposeDestination(SplitterInfo& out_info, + const SplitterInfo::InParameter* info_header) { + auto destination{out_info.GetData(0)}; + while (destination != nullptr) { + auto dest{destination->GetNext()}; + destination->SetNext(nullptr); + destination = dest; + } + out_info.SetDestinations(nullptr); + + auto dest_count{info_header->destination_count}; + if (!splitter_bug_fixed) { + dest_count = std::min(dest_count, GetDestCountPerInfoForCompat()); + } + + if (dest_count == 0) { + return; + } + + std::span<const u32> destination_ids{reinterpret_cast<const u32*>(&info_header[1]), dest_count}; + + auto head{&splitter_destinations[destination_ids[0]]}; + auto current_destination{head}; + for (u32 i = 1; i < dest_count; i++) { + auto next_destination{&splitter_destinations[destination_ids[i]]}; + current_destination->SetNext(next_destination); + current_destination = next_destination; + } + + out_info.SetDestinations(head); + out_info.SetDestinationCount(dest_count); +} + +u32 SplitterContext::GetDestCountPerInfoForCompat() const { + if (info_count <= 0) { + return 0; + } + return static_cast<u32>(destinations_count / info_count); +} + +u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params) { + u64 size{0}; + if (!behavior.IsSplitterSupported()) { + return size; + } + + size += params.splitter_destinations * sizeof(SplitterDestinationData) + + params.splitter_infos * sizeof(SplitterInfo); + + if (behavior.IsSplitterBugFixed()) { + size += Common::AlignUp(params.splitter_destinations * sizeof(u32), 0x10); + } + return size; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h new file mode 100644 index 000000000..cfd092b4f --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_context.h @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/splitter/splitter_destinations_data.h" +#include "audio_core/renderer/splitter/splitter_info.h" +#include "common/common_types.h" + +namespace AudioCore { +struct AudioRendererParameterInternal; +class WorkbufferAllocator; + +namespace AudioRenderer { +class BehaviorInfo; + +/** + * The splitter allows much more control over how sound is mixed together. + * Previously, one mix can only connect to one other, and you may need + * more mixes (and duplicate processing) to achieve the same result. + * With the splitter, many-to-one and one-to-many mixing is possible. + * This was added in revision 2. + * Had a bug with incorrect numbers of destinations, fixed in revision 5. + */ +class SplitterContext { + struct InParameterHeader { + /* 0x00 */ u32 magic; // 'SNDH' + /* 0x04 */ s32 info_count; + /* 0x08 */ s32 destination_count; + /* 0x0C */ char unk0C[0x14]; + }; + static_assert(sizeof(InParameterHeader) == 0x20, + "SplitterContext::InParameterHeader has the wrong size!"); + +public: + /** + * Get a destination mix from the given splitter and destination index. + * + * @param splitter_id - Splitter index to get from. + * @param destination_id - Destination index within the splitter. + * @return Pointer to the found destination. May be nullptr. + */ + SplitterDestinationData* GetDesintationData(s32 splitter_id, s32 destination_id); + + /** + * Get a splitter from the given index. + * + * @param index - Index of the desired splitter. + * @return Splitter requested. + */ + SplitterInfo& GetInfo(s32 index); + + /** + * Get the total number of splitter destinations. + * + * @return Number of destiantions. + */ + u32 GetDataCount() const; + + /** + * Get the total number of splitters. + * + * @return Number of splitters. + */ + u32 GetInfoCount() const; + + /** + * Get a specific global destination. + * + * @param index - Index of the desired destination. + * @return The requested destination. + */ + SplitterDestinationData& GetData(u32 index); + + /** + * Check if the splitter is in use. + * + * @return True if any splitter or destination is in use, otherwise false. + */ + bool UsingSplitter() const; + + /** + * Mark all splitters as having new connections. + */ + void ClearAllNewConnectionFlag(); + + /** + * Initialize the context. + * + * @param behavior - Used to check for splitter support. + * @param params - Input parameters. + * @param allocator - Allocator used to allocate workbuffer memory. + */ + bool Initialize(const BehaviorInfo& behavior, const AudioRendererParameterInternal& params, + WorkbufferAllocator& allocator); + + /** + * Update the context. + * + * @param input - Input buffer with the new info, + * expected to point to a InParameterHeader. + * @param consumed_size - Output with the number of bytes consumed from input. + */ + bool Update(const u8* input, u32& consumed_size); + + /** + * Update the splitters. + * + * @param input - Input buffer with the new info. + * @param offset - Current offset within the input buffer, + * input + offset should point to a SplitterInfo::InParameter. + * @param splitter_count - Number of splitters in the input buffer. + * @return Number of bytes consumed in input. + */ + u32 UpdateInfo(const u8* input, u32 offset, u32 splitter_count); + + /** + * Update the splitters. + * + * @param input - Input buffer with the new info. + * @param offset - Current offset within the input buffer, + * input + offset should point to a + * SplitterDestinationData::InParameter. + * @param destination_count - Number of destinations in the input buffer. + * @return Number of bytes consumed in input. + */ + u32 UpdateData(const u8* input, u32 offset, u32 destination_count); + + /** + * Update the state of all destinations in all splitters. + */ + void UpdateInternalState(); + + /** + * Replace the given splitter's destinations with new ones. + * + * @param out_info - Splitter to recompose. + * @param info_header - Input parameters containing new destination ids. + */ + void RecomposeDestination(SplitterInfo& out_info, const SplitterInfo::InParameter* info_header); + + /** + * Old calculation for destinations, this is the thing the splitter bug fixes. + * Left for compatibility, and now min'd with the actual count to not bug. + * + * @return Number of splitter destinations. + */ + u32 GetDestCountPerInfoForCompat() const; + + /** + * Calculate the size of the required workbuffer for splitters and destinations. + * + * @param behavior - Used to check splitter features. + * @param params - Input parameters with splitter/destination counts. + * @return Required buffer size. + */ + static u64 CalcWorkBufferSize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params); + +private: + /** + * Setup the context. + * + * @param splitter_infos - Workbuffer for splitters. + * @param splitter_info_count - Number of splitters in the workbuffer. + * @param splitter_destinations - Workbuffer for splitter destinations. + * @param destination_count - Number of destinations in the workbuffer. + * @param splitter_bug_fixed - Is the splitter bug fixed? + */ + void Setup(std::span<SplitterInfo> splitter_infos, u32 splitter_info_count, + SplitterDestinationData* splitter_destinations, u32 destination_count, + bool splitter_bug_fixed); + + /// Workbuffer for splitters + std::span<SplitterInfo> splitter_infos{}; + /// Number of splitters in buffer + s32 info_count{}; + /// Workbuffer for destinations + SplitterDestinationData* splitter_destinations{}; + /// Number of destinations in buffer + s32 destinations_count{}; + /// Is the splitter bug fixed? + bool splitter_bug_fixed{}; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp new file mode 100644 index 000000000..b27d44896 --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/splitter/splitter_destinations_data.h" + +namespace AudioCore::AudioRenderer { + +SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {} + +void SplitterDestinationData::ClearMixVolume() { + mix_volumes.fill(0.0f); + prev_mix_volumes.fill(0.0f); +} + +s32 SplitterDestinationData::GetId() const { + return id; +} + +bool SplitterDestinationData::IsConfigured() const { + return in_use && destination_id != UnusedMixId; +} + +s32 SplitterDestinationData::GetMixId() const { + return destination_id; +} + +f32 SplitterDestinationData::GetMixVolume(const u32 index) const { + if (index >= mix_volumes.size()) { + LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolume Invalid index {}", index); + return 0.0f; + } + return mix_volumes[index]; +} + +std::span<f32> SplitterDestinationData::GetMixVolume() { + return mix_volumes; +} + +f32 SplitterDestinationData::GetMixVolumePrev(const u32 index) const { + if (index >= prev_mix_volumes.size()) { + LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolumePrev Invalid index {}", + index); + return 0.0f; + } + return prev_mix_volumes[index]; +} + +std::span<f32> SplitterDestinationData::GetMixVolumePrev() { + return prev_mix_volumes; +} + +void SplitterDestinationData::Update(const InParameter& params) { + if (params.id != id || params.magic != GetSplitterSendDataMagic()) { + return; + } + + destination_id = params.mix_id; + mix_volumes = params.mix_volumes; + + if (!in_use && params.in_use) { + prev_mix_volumes = mix_volumes; + need_update = false; + } + + in_use = params.in_use; +} + +void SplitterDestinationData::MarkAsNeedToUpdateInternalState() { + need_update = true; +} + +void SplitterDestinationData::UpdateInternalState() { + if (in_use && need_update) { + prev_mix_volumes = mix_volumes; + } + need_update = false; +} + +SplitterDestinationData* SplitterDestinationData::GetNext() const { + return next; +} + +void SplitterDestinationData::SetNext(SplitterDestinationData* next_) { + next = next_; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h new file mode 100644 index 000000000..bd3d55748 --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Represents a mixing node, can be connected to a previous and next destination forming a chain + * that a certain mix buffer will pass through to output. + */ +class SplitterDestinationData { +public: + struct InParameter { + /* 0x00 */ u32 magic; // 'SNDD' + /* 0x04 */ s32 id; + /* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes; + /* 0x68 */ u32 mix_id; + /* 0x6C */ bool in_use; + }; + static_assert(sizeof(InParameter) == 0x70, + "SplitterDestinationData::InParameter has the wrong size!"); + + SplitterDestinationData(s32 id); + + /** + * Reset the mix volumes for this destination. + */ + void ClearMixVolume(); + + /** + * Get the id of this destination. + * + * @return Id for this destination. + */ + s32 GetId() const; + + /** + * Check if this destination is correctly configured. + * + * @return True if configured, otherwise false. + */ + bool IsConfigured() const; + + /** + * Get the mix id for this destination. + * + * @return Mix id for this destination. + */ + s32 GetMixId() const; + + /** + * Get the current mix volume of a given index in this destination. + * + * @param index - Mix buffer index to get the volume for. + * @return Current volume of the specified mix. + */ + f32 GetMixVolume(u32 index) const; + + /** + * Get the current mix volumes for all mix buffers in this destination. + * + * @return Span of current mix buffer volumes. + */ + std::span<f32> GetMixVolume(); + + /** + * Get the previous mix volume of a given index in this destination. + * + * @param index - Mix buffer index to get the volume for. + * @return Previous volume of the specified mix. + */ + f32 GetMixVolumePrev(u32 index) const; + + /** + * Get the previous mix volumes for all mix buffers in this destination. + * + * @return Span of previous mix buffer volumes. + */ + std::span<f32> GetMixVolumePrev(); + + /** + * Update this destination. + * + * @param params - Inpout parameters to update the destination. + */ + void Update(const InParameter& params); + + /** + * Mark this destination as needing its volumes updated. + */ + void MarkAsNeedToUpdateInternalState(); + + /** + * Copy current volumes to previous if an update is required. + */ + void UpdateInternalState(); + + /** + * Get the next destination in the mix chain. + * + * @return The next splitter destination, may be nullptr if this is the last in the chain. + */ + SplitterDestinationData* GetNext() const; + + /** + * Set the next destination in the mix chain. + * + * @param next - Destination this one is to be connected to. + */ + void SetNext(SplitterDestinationData* next); + +private: + /// Id of this destination + const s32 id; + /// Mix id this destination represents + s32 destination_id{UnusedMixId}; + /// Current mix volumes + std::array<f32, MaxMixBuffers> mix_volumes{0.0f}; + /// Previous mix volumes + std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f}; + /// Next destination in the mix chain + SplitterDestinationData* next{}; + /// Is this destiantion in use? + bool in_use{}; + /// Does this destiantion need its volumes updated? + bool need_update{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_info.cpp b/src/audio_core/renderer/splitter/splitter_info.cpp new file mode 100644 index 000000000..1aee6720b --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_info.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/splitter/splitter_info.h" + +namespace AudioCore::AudioRenderer { + +SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {} + +void SplitterInfo::InitializeInfos(SplitterInfo* splitters, const u32 count) { + if (splitters == nullptr) { + return; + } + + for (u32 i = 0; i < count; i++) { + auto& splitter{splitters[i]}; + splitter.destinations = nullptr; + splitter.destination_count = 0; + splitter.has_new_connection = true; + } +} + +u32 SplitterInfo::Update(const InParameter* params) { + if (params->id != id) { + return 0; + } + sample_rate = params->sample_rate; + has_new_connection = true; + return static_cast<u32>((sizeof(InParameter) + 3 * sizeof(s32)) + + params->destination_count * sizeof(s32)); +} + +SplitterDestinationData* SplitterInfo::GetData(const u32 destination_id) { + auto out_destination{destinations}; + u32 i{0}; + while (i < destination_id) { + if (out_destination == nullptr) { + break; + } + out_destination = out_destination->GetNext(); + i++; + } + + return out_destination; +} + +u32 SplitterInfo::GetDestinationCount() const { + return destination_count; +} + +void SplitterInfo::SetDestinationCount(const u32 count) { + destination_count = count; +} + +bool SplitterInfo::HasNewConnection() const { + return has_new_connection; +} + +void SplitterInfo::ClearNewConnectionFlag() { + has_new_connection = false; +} + +void SplitterInfo::SetNewConnectionFlag() { + has_new_connection = true; +} + +void SplitterInfo::UpdateInternalState() { + auto destination{destinations}; + while (destination != nullptr) { + destination->UpdateInternalState(); + destination = destination->GetNext(); + } +} + +void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) { + destinations = destinations_; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_info.h b/src/audio_core/renderer/splitter/splitter_info.h new file mode 100644 index 000000000..d1d75064c --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_info.h @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/splitter/splitter_destinations_data.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Represents a splitter, wraps multiple output destinations to split an input mix into. + */ +class SplitterInfo { +public: + struct InParameter { + /* 0x00 */ u32 magic; // 'SNDI' + /* 0x04 */ s32 id; + /* 0x08 */ u32 sample_rate; + /* 0x0C */ u32 destination_count; + }; + static_assert(sizeof(InParameter) == 0x10, "SplitterInfo::InParameter has the wrong size!"); + + explicit SplitterInfo(s32 id); + + /** + * Initialize the given splitters. + * + * @param splitters - Splitters to initialize. + * @param count - Number of splitters given. + */ + static void InitializeInfos(SplitterInfo* splitters, u32 count); + + /** + * Update this splitter. + * + * @param params - Input parameters to update with. + * @return The size in bytes of this splitter. + */ + u32 Update(const InParameter* params); + + /** + * Get a destination in this splitter. + * + * @param id - Destination id to get. + * @return Pointer to the destination, may be nullptr. + */ + SplitterDestinationData* GetData(u32 id); + + /** + * Get the number of destinations in this splitter. + * + * @return The number of destiantions. + */ + u32 GetDestinationCount() const; + + /** + * Set the number of destinations in this splitter. + * + * @param count - The new number of destiantions. + */ + void SetDestinationCount(u32 count); + + /** + * Check if the splitter has a new connection. + * + * @return True if there is a new connection, otherwise false. + */ + bool HasNewConnection() const; + + /** + * Reset the new connection flag. + */ + void ClearNewConnectionFlag(); + + /** + * Mark as having a new connection. + */ + void SetNewConnectionFlag(); + + /** + * Update the state of all destinations. + */ + void UpdateInternalState(); + + /** + * Set this splitter's destinations. + * + * @param destinations - The new destination list for this splitter. + */ + void SetDestinations(SplitterDestinationData* destinations); + +private: + /// Id of this splitter + s32 id; + /// Sample rate of this splitter + u32 sample_rate{}; + /// Number of destinations in this splitter + u32 destination_count{}; + /// Does this splitter have a new connection? + bool has_new_connection{true}; + /// Pointer to the destinations of this splitter + SplitterDestinationData* destinations{}; + /// Number of channels this splitter manages + u32 channel_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp new file mode 100644 index 000000000..7a217969e --- /dev/null +++ b/src/audio_core/renderer/system.cpp @@ -0,0 +1,802 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <chrono> +#include <span> + +#include "audio_core/audio_core.h" +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/common/common.h" +#include "audio_core/common/feature_support.h" +#include "audio_core/common/workbuffer_allocator.h" +#include "audio_core/renderer/adsp/adsp.h" +#include "audio_core/renderer/behavior/info_updater.h" +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_generator.h" +#include "audio_core/renderer/command/command_list_header.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "audio_core/renderer/effect/effect_result_state.h" +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "audio_core/renderer/memory/pool_mapper.h" +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/nodes/edge_matrix.h" +#include "audio_core/renderer/nodes/node_states.h" +#include "audio_core/renderer/sink/sink_info_base.h" +#include "audio_core/renderer/system.h" +#include "audio_core/renderer/upsampler/upsampler_info.h" +#include "audio_core/renderer/voice/voice_channel_resource.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "audio_core/renderer/voice/voice_state.h" +#include "common/alignment.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { + +u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) { + BehaviorInfo behavior; + behavior.SetUserLibRevision(params.revision); + + u64 size{0}; + + size += Common::AlignUp(params.mixes * sizeof(s32), 0x40); + size += params.sub_mixes * MaxEffects * sizeof(s32); + size += (params.sub_mixes + 1) * sizeof(MixInfo); + size += params.voices * (sizeof(VoiceInfo) + sizeof(VoiceChannelResource) + sizeof(VoiceState)); + size += Common::AlignUp((params.sub_mixes + 1) * sizeof(MixInfo*), 0x10); + size += Common::AlignUp(params.voices * sizeof(VoiceInfo*), 0x10); + size += Common::AlignUp(((params.sinks + params.sub_mixes) * TargetSampleCount * sizeof(s32) + + params.sample_count * sizeof(s32)) * + (params.mixes + MaxChannels), + 0x40); + + if (behavior.IsSplitterSupported()) { + const auto node_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)}; + const auto edge_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)}; + size += Common::AlignUp(node_size + edge_size, 0x10); + } + + size += SplitterContext::CalcWorkBufferSize(behavior, params); + size += (params.effects + params.voices * MaxWaveBuffers) * sizeof(MemoryPoolInfo); + + if (behavior.IsEffectInfoVersion2Supported()) { + size += params.effects * sizeof(EffectResultState); + } + size += 0x50; + + size = Common::AlignUp(size, 0x40); + + size += (params.sinks + params.sub_mixes) * sizeof(UpsamplerInfo); + size += params.effects * sizeof(EffectInfoBase); + size += Common::AlignUp(params.voices * sizeof(VoiceState), 0x40); + size += params.sinks * sizeof(SinkInfoBase); + + if (behavior.IsEffectInfoVersion2Supported()) { + size += params.effects * sizeof(EffectResultState); + } + + if (params.perf_frames > 0) { + auto perf_size{PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame( + behavior, params)}; + size += Common::AlignUp(perf_size * (params.perf_frames + 1) + 0xC0, 0x100); + } + + if (behavior.IsVariadicCommandBufferSizeSupported()) { + size += CommandGenerator::CalculateCommandBufferSize(behavior, params) + (0x40 - 1) * 2; + } else { + size += 0x18000 + (0x40 - 1) * 2; + } + + size = Common::AlignUp(size, 0x1000); + return size; +} + +System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_) + : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {} + +Result System::Initialize(const AudioRendererParameterInternal& params, + Kernel::KTransferMemory* transfer_memory, const u64 transfer_memory_size, + const u32 process_handle_, const u64 applet_resource_user_id_, + const s32 session_id_) { + if (!CheckValidRevision(params.revision)) { + return Service::Audio::ERR_INVALID_REVISION; + } + + if (GetWorkBufferSize(params) > transfer_memory_size) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + if (process_handle_ == 0) { + return Service::Audio::ERR_INVALID_PROCESS_HANDLE; + } + + behavior.SetUserLibRevision(params.revision); + + process_handle = process_handle_; + applet_resource_user_id = applet_resource_user_id_; + session_id = session_id_; + + sample_rate = params.sample_rate; + sample_count = params.sample_count; + mix_buffer_count = static_cast<s16>(params.mixes); + voice_channels = MaxChannels; + upsampler_count = params.sinks + params.sub_mixes; + memory_pool_count = params.effects + params.voices * MaxWaveBuffers; + render_device = params.rendering_device; + execution_mode = params.execution_mode; + + core.Memory().ZeroBlock(*core.Kernel().CurrentProcess(), transfer_memory->GetSourceAddress(), + transfer_memory_size); + + // Note: We're not actually using the transfer memory because it's a pain to code for. + // Allocate the memory normally instead and hope the game doesn't try to read anything back + workbuffer = std::make_unique<u8[]>(transfer_memory_size); + workbuffer_size = transfer_memory_size; + + PoolMapper pool_mapper(process_handle, false); + pool_mapper.InitializeSystemPool(memory_pool_info, workbuffer.get(), workbuffer_size); + + WorkbufferAllocator allocator({workbuffer.get(), workbuffer_size}, workbuffer_size); + + samples_workbuffer = + allocator.Allocate<s32>((voice_channels + mix_buffer_count) * sample_count, 0x10); + if (samples_workbuffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + auto upsampler_workbuffer{allocator.Allocate<s32>( + (voice_channels + mix_buffer_count) * TargetSampleCount * upsampler_count, 0x10)}; + if (upsampler_workbuffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + depop_buffer = + allocator.Allocate<s32>(Common::AlignUp(static_cast<u32>(mix_buffer_count), 0x40), 0x40); + if (depop_buffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + // invalidate samples_workbuffer DSP cache + + auto voice_infos{allocator.Allocate<VoiceInfo>(params.voices, 0x10)}; + for (auto& voice_info : voice_infos) { + std::construct_at<VoiceInfo>(&voice_info); + } + + if (voice_infos.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + auto sorted_voice_infos{allocator.Allocate<VoiceInfo*>(params.voices, 0x10)}; + if (sorted_voice_infos.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + std::memset(sorted_voice_infos.data(), 0, sorted_voice_infos.size_bytes()); + + auto voice_channel_resources{allocator.Allocate<VoiceChannelResource>(params.voices, 0x10)}; + u32 i{0}; + for (auto& voice_channel_resource : voice_channel_resources) { + std::construct_at<VoiceChannelResource>(&voice_channel_resource, i++); + } + + if (voice_channel_resources.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + auto voice_cpu_states{allocator.Allocate<VoiceState>(params.voices, 0x10)}; + if (voice_cpu_states.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + for (auto& voice_state : voice_cpu_states) { + voice_state = {}; + } + + auto mix_infos{allocator.Allocate<MixInfo>(params.sub_mixes + 1, 0x10)}; + + if (mix_infos.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + u32 effect_process_order_count{0}; + std::span<s32> effect_process_order_buffer{}; + + if (params.effects > 0) { + effect_process_order_count = params.effects * (params.sub_mixes + 1); + effect_process_order_buffer = allocator.Allocate<s32>(effect_process_order_count, 0x10); + if (effect_process_order_buffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + } + + i = 0; + for (auto& mix_info : mix_infos) { + std::construct_at<MixInfo>( + &mix_info, effect_process_order_buffer.subspan(i * params.effects, params.effects), + params.effects, this->behavior); + i++; + } + + auto sorted_mix_infos{allocator.Allocate<MixInfo*>(params.sub_mixes + 1, 0x10)}; + if (sorted_mix_infos.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + std::memset(sorted_mix_infos.data(), 0, sorted_mix_infos.size_bytes()); + + if (behavior.IsSplitterSupported()) { + u64 node_state_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)}; + u64 edge_matrix_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)}; + + auto node_states_workbuffer{allocator.Allocate<u8>(node_state_size, 1)}; + auto edge_matrix_workbuffer{allocator.Allocate<u8>(edge_matrix_size, 1)}; + + if (node_states_workbuffer.empty() || edge_matrix_workbuffer.size() == 0) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1, + effect_process_order_buffer, effect_process_order_count, + node_states_workbuffer, node_state_size, edge_matrix_workbuffer, + edge_matrix_size); + } else { + mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1, + effect_process_order_buffer, effect_process_order_count, {}, 0, {}, + 0); + } + + upsampler_manager = allocator.Allocate<UpsamplerManager>(1, 0x10).data(); + if (upsampler_manager == nullptr) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + memory_pool_workbuffer = allocator.Allocate<MemoryPoolInfo>(memory_pool_count, 0x10); + for (auto& memory_pool : memory_pool_workbuffer) { + std::construct_at<MemoryPoolInfo>(&memory_pool, MemoryPoolInfo::Location::DSP); + } + + if (memory_pool_workbuffer.empty() && memory_pool_count > 0) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + if (!splitter_context.Initialize(behavior, params, allocator)) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + std::span<EffectResultState> effect_result_states_cpu{}; + if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) { + effect_result_states_cpu = allocator.Allocate<EffectResultState>(params.effects, 0x10); + if (effect_result_states_cpu.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + std::memset(effect_result_states_cpu.data(), 0, effect_result_states_cpu.size_bytes()); + } + + allocator.Align(0x40); + + unk_2B0 = allocator.GetSize() - allocator.GetCurrentOffset(); + unk_2A8 = {&workbuffer[allocator.GetCurrentOffset()], unk_2B0}; + + upsampler_infos = allocator.Allocate<UpsamplerInfo>(upsampler_count, 0x40); + for (auto& upsampler_info : upsampler_infos) { + std::construct_at<UpsamplerInfo>(&upsampler_info); + } + + std::construct_at<UpsamplerManager>(upsampler_manager, upsampler_count, upsampler_infos, + upsampler_workbuffer); + + if (upsampler_infos.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + auto effect_infos{allocator.Allocate<EffectInfoBase>(params.effects, 0x40)}; + for (auto& effect_info : effect_infos) { + std::construct_at<EffectInfoBase>(&effect_info); + } + + if (effect_infos.empty() && params.effects > 0) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + std::span<EffectResultState> effect_result_states_dsp{}; + if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) { + effect_result_states_dsp = allocator.Allocate<EffectResultState>(params.effects, 0x40); + if (effect_result_states_dsp.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + std::memset(effect_result_states_dsp.data(), 0, effect_result_states_dsp.size_bytes()); + } + + effect_context.Initialize(effect_infos, params.effects, effect_result_states_cpu, + effect_result_states_dsp, effect_result_states_dsp.size()); + + auto sinks{allocator.Allocate<SinkInfoBase>(params.sinks, 0x10)}; + for (auto& sink : sinks) { + std::construct_at<SinkInfoBase>(&sink); + } + + if (sinks.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + sink_context.Initialize(sinks, params.sinks); + + auto voice_dsp_states{allocator.Allocate<VoiceState>(params.voices, 0x40)}; + if (voice_dsp_states.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + for (auto& voice_state : voice_dsp_states) { + voice_state = {}; + } + + voice_context.Initialize(sorted_voice_infos, voice_infos, voice_channel_resources, + voice_cpu_states, voice_dsp_states, params.voices); + + if (params.perf_frames > 0) { + const auto perf_workbuffer_size{ + PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, + params) * + (params.perf_frames + 1) + + 0xC}; + performance_workbuffer = allocator.Allocate<u8>(perf_workbuffer_size, 0x40); + if (performance_workbuffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + std::memset(performance_workbuffer.data(), 0, performance_workbuffer.size_bytes()); + performance_manager.Initialize(performance_workbuffer, performance_workbuffer.size_bytes(), + params, behavior, memory_pool_info); + } + + render_time_limit_percent = 100; + drop_voice = params.voice_drop_enabled && params.execution_mode == ExecutionMode::Auto; + + allocator.Align(0x40); + command_workbuffer_size = allocator.GetRemainingSize(); + command_workbuffer = allocator.Allocate<u8>(command_workbuffer_size, 0x40); + if (command_workbuffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + command_buffer_size = 0; + reset_command_buffers = true; + + // nn::audio::dsp::FlushDataCache(transferMemory, transferMemorySize); + + if (behavior.IsCommandProcessingTimeEstimatorVersion5Supported()) { + command_processing_time_estimator = + std::make_unique<CommandProcessingTimeEstimatorVersion5>(sample_count, + mix_buffer_count); + } else if (behavior.IsCommandProcessingTimeEstimatorVersion4Supported()) { + command_processing_time_estimator = + std::make_unique<CommandProcessingTimeEstimatorVersion4>(sample_count, + mix_buffer_count); + } else if (behavior.IsCommandProcessingTimeEstimatorVersion3Supported()) { + command_processing_time_estimator = + std::make_unique<CommandProcessingTimeEstimatorVersion3>(sample_count, + mix_buffer_count); + } else if (behavior.IsCommandProcessingTimeEstimatorVersion2Supported()) { + command_processing_time_estimator = + std::make_unique<CommandProcessingTimeEstimatorVersion2>(sample_count, + mix_buffer_count); + } else { + command_processing_time_estimator = + std::make_unique<CommandProcessingTimeEstimatorVersion1>(sample_count, + mix_buffer_count); + } + + initialized = true; + return ResultSuccess; +} + +void System::Finalize() { + if (!initialized) { + return; + } + + if (active) { + Stop(); + } + + applet_resource_user_id = 0; + + PoolMapper pool_mapper(process_handle, false); + pool_mapper.Unmap(memory_pool_info); + + if (process_handle) { + pool_mapper.ClearUseState(memory_pool_workbuffer, memory_pool_count); + for (auto& memory_pool : memory_pool_workbuffer) { + if (memory_pool.IsMapped()) { + pool_mapper.Unmap(memory_pool); + } + } + + // dsp::ProcessCleanup + // close handle + } + initialized = false; +} + +void System::Start() { + std::scoped_lock l{lock}; + frames_elapsed = 0; + state = State::Started; + active = true; +} + +void System::Stop() { + { + std::scoped_lock l{lock}; + state = State::Stopped; + active = false; + } + + if (execution_mode == ExecutionMode::Auto) { + // Should wait for the system to terminate here, but core timing (should have) already + // stopped, so this isn't needed. Find a way to make this definite. + + // terminate_event.Wait(); + } +} + +Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) { + std::scoped_lock l{lock}; + + const auto start_time{core.CoreTiming().GetClockTicks()}; + + InfoUpdater info_updater(input, output, process_handle, behavior); + + auto result{info_updater.UpdateBehaviorInfo(behavior)}; + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update BehaviorInfo!"); + return result; + } + + result = info_updater.UpdateMemoryPools(memory_pool_workbuffer, memory_pool_count); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update MemoryPools!"); + return result; + } + + result = info_updater.UpdateVoiceChannelResources(voice_context); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update VoiceChannelResources!"); + return result; + } + + result = info_updater.UpdateVoices(voice_context, memory_pool_workbuffer, memory_pool_count); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update Voices!"); + return result; + } + + result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer, + memory_pool_count); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update Effects!"); + return result; + } + + if (behavior.IsSplitterSupported()) { + result = info_updater.UpdateSplitterInfo(splitter_context); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update SplitterInfo!"); + return result; + } + } + + result = + info_updater.UpdateMixes(mix_context, mix_buffer_count, effect_context, splitter_context); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update Mixes!"); + return result; + } + + result = info_updater.UpdateSinks(sink_context, memory_pool_workbuffer, memory_pool_count); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update Sinks!"); + return result; + } + + PerformanceManager* perf_manager{nullptr}; + if (performance_manager.IsInitialized()) { + perf_manager = &performance_manager; + } + + result = + info_updater.UpdatePerformanceBuffer(performance, performance.size_bytes(), perf_manager); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update PerformanceBuffer!"); + return result; + } + + result = info_updater.UpdateErrorInfo(behavior); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update ErrorInfo!"); + return result; + } + + if (behavior.IsElapsedFrameCountSupported()) { + result = info_updater.UpdateRendererInfo(frames_elapsed); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update RendererInfo!"); + return result; + } + } + + result = info_updater.CheckConsumedSize(); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Invalid consume size!"); + return result; + } + + adsp_rendered_event->GetWritableEvent().Clear(); + num_times_updated++; + + const auto end_time{core.CoreTiming().GetClockTicks()}; + ticks_spent_updating += end_time - start_time; + + return ResultSuccess; +} + +u32 System::GetRenderingTimeLimit() const { + return render_time_limit_percent; +} + +void System::SetRenderingTimeLimit(const u32 limit) { + render_time_limit_percent = limit; +} + +u32 System::GetSessionId() const { + return session_id; +} + +u32 System::GetSampleRate() const { + return sample_rate; +} + +u32 System::GetSampleCount() const { + return sample_count; +} + +u32 System::GetMixBufferCount() const { + return mix_buffer_count; +} + +ExecutionMode System::GetExecutionMode() const { + return execution_mode; +} + +u32 System::GetRenderingDevice() const { + return render_device; +} + +bool System::IsActive() const { + return active; +} + +void System::SendCommandToDsp() { + std::scoped_lock l{lock}; + + if (initialized) { + if (active) { + terminate_event.Reset(); + const auto remaining_command_count{adsp.GetRemainCommandCount(session_id)}; + u64 command_size{0}; + + if (remaining_command_count) { + adsp_behind = true; + command_size = command_buffer_size; + } else { + command_size = GenerateCommand(command_workbuffer, command_workbuffer_size); + } + + auto translated_addr{ + memory_pool_info.Translate(CpuAddr(command_workbuffer.data()), command_size)}; + + auto time_limit_percent{70.0f}; + if (behavior.IsAudioRendererProcessingTimeLimit80PercentSupported()) { + time_limit_percent = 80.0f; + } else if (behavior.IsAudioRendererProcessingTimeLimit75PercentSupported()) { + time_limit_percent = 75.0f; + } else { + // result ignored and 70 is used anyway + behavior.IsAudioRendererProcessingTimeLimit70PercentSupported(); + time_limit_percent = 70.0f; + } + + ADSP::CommandBuffer command_buffer{ + .buffer{translated_addr}, + .size{command_size}, + .time_limit{ + static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * + (static_cast<f32>(render_time_limit_percent) / 100.0f))}, + .remaining_command_count{remaining_command_count}, + .reset_buffers{reset_command_buffers}, + .applet_resource_user_id{applet_resource_user_id}, + .render_time_taken{adsp.GetRenderTimeTaken(session_id)}, + }; + + adsp.SendCommandBuffer(session_id, command_buffer); + reset_command_buffers = false; + command_buffer_size = command_size; + if (remaining_command_count == 0) { + adsp_rendered_event->GetWritableEvent().Signal(); + } + } else { + adsp.ClearRemainCount(session_id); + terminate_event.Set(); + } + } +} + +u64 System::GenerateCommand(std::span<u8> in_command_buffer, + [[maybe_unused]] const u64 command_buffer_size_) { + PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count); + const auto start_time{core.CoreTiming().GetClockTicks()}; + + auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())}; + + command_list_header->buffer_count = static_cast<s16>(voice_channels + mix_buffer_count); + command_list_header->sample_count = sample_count; + command_list_header->sample_rate = sample_rate; + command_list_header->samples_buffer = samples_workbuffer; + + const auto performance_initialized{performance_manager.IsInitialized()}; + if (performance_initialized) { + performance_manager.TapFrame(adsp_behind, num_voices_dropped, render_start_tick); + adsp_behind = false; + num_voices_dropped = 0; + render_start_tick = 0; + } + + s8 channel_count{2}; + if (execution_mode == ExecutionMode::Auto) { + const auto& sink{core.AudioCore().GetOutputSink()}; + channel_count = static_cast<s8>(sink.GetDeviceChannels()); + } + + AudioRendererSystemContext render_context{ + .session_id{session_id}, + .channels{channel_count}, + .mix_buffer_count{mix_buffer_count}, + .behavior{&behavior}, + .depop_buffer{depop_buffer}, + .upsampler_manager{upsampler_manager}, + .memory_pool_info{&memory_pool_info}, + }; + + CommandBuffer command_buffer{ + .command_list{in_command_buffer}, + .sample_count{sample_count}, + .sample_rate{sample_rate}, + .size{sizeof(CommandListHeader)}, + .count{0}, + .estimated_process_time{0}, + .memory_pool{&memory_pool_info}, + .time_estimator{command_processing_time_estimator.get()}, + .behavior{&behavior}, + }; + + PerformanceManager* perf_manager{nullptr}; + if (performance_initialized) { + perf_manager = &performance_manager; + } + + CommandGenerator command_generator{command_buffer, *command_list_header, render_context, + voice_context, mix_context, effect_context, + sink_context, splitter_context, perf_manager}; + + voice_context.SortInfo(); + + const auto start_estimated_time{command_buffer.estimated_process_time}; + + command_generator.GenerateVoiceCommands(); + command_generator.GenerateSubMixCommands(); + command_generator.GenerateFinalMixCommands(); + command_generator.GenerateSinkCommands(); + + if (drop_voice) { + f32 time_limit_percent{70.0f}; + if (render_context.behavior->IsAudioRendererProcessingTimeLimit80PercentSupported()) { + time_limit_percent = 80.0f; + } else if (render_context.behavior + ->IsAudioRendererProcessingTimeLimit75PercentSupported()) { + time_limit_percent = 75.0f; + } else { + // result is ignored + render_context.behavior->IsAudioRendererProcessingTimeLimit70PercentSupported(); + time_limit_percent = 70.0f; + } + const auto time_limit{static_cast<u32>( + static_cast<f32>(start_estimated_time - command_buffer.estimated_process_time) + + (((time_limit_percent / 100.0f) * 2'880'000.0) * + (static_cast<f32>(render_time_limit_percent) / 100.0f)))}; + num_voices_dropped = DropVoices(command_buffer, start_estimated_time, time_limit); + } + + command_list_header->buffer_size = command_buffer.size; + command_list_header->command_count = command_buffer.count; + + voice_context.UpdateStateByDspShared(); + + if (render_context.behavior->IsEffectInfoVersion2Supported()) { + effect_context.UpdateStateByDspShared(); + } + + const auto end_time{core.CoreTiming().GetClockTicks()}; + total_ticks_elapsed += end_time - start_time; + num_command_lists_generated++; + render_start_tick = adsp.GetRenderingStartTick(session_id); + frames_elapsed++; + + return command_buffer.size; +} + +u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_process_time, + const u32 time_limit) { + u32 i{0}; + auto command_list{command_buffer.command_list.data() + sizeof(CommandListHeader)}; + ICommand* cmd{}; + + for (; i < command_buffer.count; i++) { + cmd = reinterpret_cast<ICommand*>(command_list); + if (cmd->type != CommandId::Performance && + cmd->type != CommandId::DataSourcePcmInt16Version1 && + cmd->type != CommandId::DataSourcePcmInt16Version2 && + cmd->type != CommandId::DataSourcePcmFloatVersion1 && + cmd->type != CommandId::DataSourcePcmFloatVersion2 && + cmd->type != CommandId::DataSourceAdpcmVersion1 && + cmd->type != CommandId::DataSourceAdpcmVersion2) { + break; + } + command_list += cmd->size; + } + + if (cmd == nullptr || command_buffer.count == 0 || i >= command_buffer.count) { + return 0; + } + + auto voices_dropped{0}; + while (i < command_buffer.count) { + const auto node_id{cmd->node_id}; + const auto node_id_type{cmd->node_id >> 28}; + const auto node_id_base{cmd->node_id & 0xFFF}; + + if (estimated_process_time <= time_limit) { + break; + } + + if (node_id_type != 1) { + break; + } + + auto& voice_info{voice_context.GetInfo(node_id_base)}; + if (voice_info.priority == HighestVoicePriority) { + break; + } + + voices_dropped++; + voice_info.voice_dropped = true; + + if (i < command_buffer.count) { + while (cmd->node_id == node_id) { + if (cmd->type == CommandId::DepopPrepare) { + cmd->enabled = true; + } else if (cmd->type == CommandId::Performance || !cmd->enabled) { + cmd->enabled = false; + } + i++; + command_list += cmd->size; + cmd = reinterpret_cast<ICommand*>(command_list); + } + } + } + return voices_dropped; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/system.h b/src/audio_core/renderer/system.h new file mode 100644 index 000000000..bcbe65b07 --- /dev/null +++ b/src/audio_core/renderer/system.h @@ -0,0 +1,307 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <memory> +#include <mutex> +#include <span> + +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/command/command_processing_time_estimator.h" +#include "audio_core/renderer/effect/effect_context.h" +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "audio_core/renderer/mix/mix_context.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "audio_core/renderer/sink/sink_context.h" +#include "audio_core/renderer/splitter/splitter_context.h" +#include "audio_core/renderer/upsampler/upsampler_manager.h" +#include "audio_core/renderer/voice/voice_context.h" +#include "common/thread.h" +#include "core/hle/service/audio/errors.h" + +namespace Core { +namespace Memory { +class Memory; +} +class System; +} // namespace Core + +namespace Kernel { +class KEvent; +class KTransferMemory; +} // namespace Kernel + +namespace AudioCore { +struct AudioRendererParameterInternal; + +namespace AudioRenderer { +class CommandBuffer; +namespace ADSP { +class ADSP; +} + +/** + * Audio Renderer System, the main worker for audio rendering. + */ +class System { + enum class State { + Started = 0, + Stopped = 2, + }; + +public: + explicit System(Core::System& core, Kernel::KEvent* adsp_rendered_event); + + /** + * Calculate the total size required for all audio render workbuffers. + * + * @param params - Input parameters with the numbers of voices/mixes/sinks/etc. + * @return Size (in bytes) required for the audio renderer. + */ + static u64 GetWorkBufferSize(const AudioRendererParameterInternal& params); + + /** + * Initialize the renderer system. + * Allocates workbuffers and initializes everything to a default state, ready to receive a + * RequestUpdate. + * + * @param params - Input parameters to initialize the system with. + * @param transfer_memory - Game-supplied memory for all workbuffers. Unused. + * @param transfer_memory_size - Size of the transfer memory. Unused. + * @param process_handle - Process handle, also used for memory. Unused. + * @param applet_resource_user_id - Applet id for this renderer. Unused. + * @param session_id - Session id of this renderer. + * @return Result code. + */ + Result Initialize(const AudioRendererParameterInternal& params, + Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, + u32 process_handle, u64 applet_resource_user_id, s32 session_id); + + /** + * Finalize the system. + */ + void Finalize(); + + /** + * Start the system. + */ + void Start(); + + /** + * Stop the system. + */ + void Stop(); + + /** + * Update the system. + * + * @param input - Inout buffer containing the update data. + * @param performance - Optional buffer for writing back performance metrics. + * @param output - Output information from rendering. + * @return Result code. + */ + Result Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output); + + /** + * Get the time limit (percent) for rendering + * + * @return Time limit as a percent. + */ + u32 GetRenderingTimeLimit() const; + + /** + * Set the time limit (percent) for rendering + * + * @param limit - New time limit. + */ + void SetRenderingTimeLimit(u32 limit); + + /** + * Get the session id for this system. + * + * @return Session id of this system. + */ + u32 GetSessionId() const; + + /** + * Get the sample rate of this system. + * + * @return Sample rate of this system. + */ + u32 GetSampleRate() const; + + /** + * Get the sample count of this system. + * + * @return Sample count of this system. + */ + u32 GetSampleCount() const; + + /** + * Get the number of mix buffers for this system. + * + * @return Number of mix buffers in the system. + */ + u32 GetMixBufferCount() const; + + /** + * Get the execution mode of this system. + * Note: Only Auto is implemented. + * + * @return Execution mode for this system. + */ + ExecutionMode GetExecutionMode() const; + + /** + * Get the rendering deivce for this system. + * This is unused. + * + * @return Rendering device for this system. + */ + u32 GetRenderingDevice() const; + + /** + * Check if this system is currently active. + * + * @return True if active, otherwise false. + */ + bool IsActive() const; + + /** + * Prepare and generate a list of commands for the AudioRenderer based on current state, + * signalling the buffer event when all processed. + */ + void SendCommandToDsp(); + + /** + * Generate a list of commands for the AudioRenderer based on current state. + * + * @param command_buffer - Buffer for commands to be written to. + * @param command_buffer_size - Size of the command_buffer. + * + * @return Number of bytes written. + */ + u64 GenerateCommand(std::span<u8> command_buffer, u64 command_buffer_size); + + /** + * Try to drop some voices if the AudioRenderer fell behind. + * + * @param command_buffer - Command buffer to drop voices from. + * @param estimated_process_time - Current estimated processing time of all commands. + * @param time_limit - Time limit for rendering, voices are dropped if estimated + * exceeds this. + * + * @return Number of voices dropped. + */ + u32 DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time, u32 time_limit); + +private: + /// Core system + Core::System& core; + /// Reference to the ADSP for communication + ADSP::ADSP& adsp; + /// Is this system initialized? + bool initialized{}; + /// Is this system currently active? + std::atomic<bool> active{}; + /// State of the system + State state{State::Stopped}; + /// Sample rate for the system + u32 sample_rate{}; + /// Sample count of the system + u32 sample_count{}; + /// Number of mix buffers in use by the system + s16 mix_buffer_count{}; + /// Workbuffer for mix buffers, used by the AudioRenderer + std::span<s32> samples_workbuffer{}; + /// Depop samples for depopping commands + std::span<s32> depop_buffer{}; + /// Number of memory pools in the buffer + u32 memory_pool_count{}; + /// Workbuffer for memory pools + std::span<MemoryPoolInfo> memory_pool_workbuffer{}; + /// System memory pool info + MemoryPoolInfo memory_pool_info{}; + /// Workbuffer that commands will be generated into + std::span<u8> command_workbuffer{}; + /// Size of command workbuffer + u64 command_workbuffer_size{}; + /// Numebr of commands in the workbuffer + u64 command_buffer_size{}; + /// Manager for upsamplers + UpsamplerManager* upsampler_manager{}; + /// Upsampler workbuffer + std::span<UpsamplerInfo> upsampler_infos{}; + /// Number of upsamplers in the workbuffer + u32 upsampler_count{}; + /// Holds and controls all voices + VoiceContext voice_context{}; + /// Holds and controls all mixes + MixContext mix_context{}; + /// Holds and controls all effects + EffectContext effect_context{}; + /// Holds and controls all sinks + SinkContext sink_context{}; + /// Holds and controls all splitters + SplitterContext splitter_context{}; + /// Estimates the time taken for each command + std::unique_ptr<ICommandProcessingTimeEstimator> command_processing_time_estimator{}; + /// Session id of this system + s32 session_id{}; + /// Number of channels in use by voices + s32 voice_channels{}; + /// Event to be called when the AudioRenderer processes a command list + Kernel::KEvent* adsp_rendered_event{}; + /// Event signalled on system terminate + Common::Event terminate_event{}; + /// Does what locks do + std::mutex lock{}; + /// Handle for the process for this system, unused + u32 process_handle{}; + /// Applet resource id for this system, unused + u64 applet_resource_user_id{}; + /// Controls performance input and output + PerformanceManager performance_manager{}; + /// Workbuffer for performance metrics + std::span<u8> performance_workbuffer{}; + /// Main workbuffer, from which all other workbuffers here allocate into + std::unique_ptr<u8[]> workbuffer{}; + /// Size of the main workbuffer + u64 workbuffer_size{}; + /// Unknown buffer/marker + std::span<u8> unk_2A8{}; + /// Size of the above unknown buffer/marker + u64 unk_2B0{}; + /// Rendering time limit (percent) + u32 render_time_limit_percent{}; + /// Should any voices be dropped? + bool drop_voice{}; + /// Should the backend stream have its buffers flushed? + bool reset_command_buffers{}; + /// Execution mode of this system, only Auto is supported + ExecutionMode execution_mode{ExecutionMode::Auto}; + /// Render device, unused + u32 render_device{}; + /// Behaviour to check which features are supported by the user revision + BehaviorInfo behavior{}; + /// Total ticks the audio system has been running + u64 total_ticks_elapsed{}; + /// Ticks the system has spent in updates + u64 ticks_spent_updating{}; + /// Number of times a command list was generated + u64 num_command_lists_generated{}; + /// Number of times the system has updated + u64 num_times_updated{}; + /// Number of frames generated, written back to the game + std::atomic<u64> frames_elapsed{}; + /// Is the AudioRenderer running too slow? + bool adsp_behind{}; + /// Number of voices dropped + u32 num_voices_dropped{}; + /// Tick that rendering started + u64 render_start_tick{}; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp new file mode 100644 index 000000000..b326819ed --- /dev/null +++ b/src/audio_core/renderer/system_manager.cpp @@ -0,0 +1,162 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <chrono> + +#include "audio_core/audio_core.h" +#include "audio_core/renderer/adsp/adsp.h" +#include "audio_core/renderer/system_manager.h" +#include "common/microprofile.h" +#include "common/thread.h" +#include "core/core.h" +#include "core/core_timing.h" + +MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager", + MP_RGB(60, 19, 97)); + +namespace AudioCore::AudioRenderer { +constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; +constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL}; + +SystemManager::SystemManager(Core::System& core_) + : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, + thread_event{Core::Timing::CreateEvent( + "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { + return ThreadFunc2(time); + })} { + core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); }); +} + +SystemManager::~SystemManager() { + Stop(); +} + +bool SystemManager::InitializeUnsafe() { + if (!active) { + if (adsp.Start()) { + active = true; + thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); + core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), + BaseRenderTime - RenderTimeOffset, thread_event); + } + } + + return adsp.GetState() == ADSP::State::Started; +} + +void SystemManager::Stop() { + if (!active) { + return; + } + core.CoreTiming().UnscheduleEvent(thread_event, {}); + active = false; + update.store(true); + update.notify_all(); + thread.join(); + adsp.Stop(); +} + +bool SystemManager::Add(System& system_) { + std::scoped_lock l2{mutex2}; + + if (systems.size() + 1 > MaxRendererSessions) { + LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!"); + return false; + } + + { + std::scoped_lock l{mutex1}; + if (systems.empty()) { + if (!InitializeUnsafe()) { + LOG_ERROR(Service_Audio, "Failed to start the AudioRenderer SystemManager"); + return false; + } + } + } + + systems.push_back(&system_); + return true; +} + +bool SystemManager::Remove(System& system_) { + std::scoped_lock l2{mutex2}; + + { + std::scoped_lock l{mutex1}; + if (systems.remove(&system_) == 0) { + LOG_ERROR(Service_Audio, + "Failed to remove a render system, it was not found in the list!"); + return false; + } + } + + if (systems.empty()) { + Stop(); + } + return true; +} + +void SystemManager::ThreadFunc() { + constexpr char name[]{"yuzu:AudioRenderSystemManager"}; + MicroProfileOnThreadCreate(name); + Common::SetCurrentThreadName(name); + Common::SetCurrentThreadPriority(Common::ThreadPriority::High); + while (active) { + { + std::scoped_lock l{mutex1}; + + MICROPROFILE_SCOPE(Audio_RenderSystemManager); + + for (auto system : systems) { + system->SendCommandToDsp(); + } + } + + adsp.Signal(); + adsp.Wait(); + + update.wait(false); + update.store(false); + } +} + +std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { + std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt}; + const auto queue_size{core.AudioCore().GetStreamQueue()}; + switch (state) { + case StreamState::Filling: + if (queue_size >= 5) { + new_schedule_time = BaseRenderTime; + state = StreamState::Steady; + } + break; + case StreamState::Steady: + if (queue_size <= 2) { + new_schedule_time = BaseRenderTime - RenderTimeOffset; + state = StreamState::Filling; + } else if (queue_size > 5) { + new_schedule_time = BaseRenderTime + RenderTimeOffset; + state = StreamState::Draining; + } + break; + case StreamState::Draining: + if (queue_size <= 5) { + new_schedule_time = BaseRenderTime; + state = StreamState::Steady; + } + break; + } + + update.store(true); + update.notify_all(); + return new_schedule_time; +} + +void SystemManager::PauseCallback(bool paused) { + if (paused && core.IsPoweredOn() && core.IsShuttingDown()) { + update.store(true); + update.notify_all(); + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h new file mode 100644 index 000000000..1291e9e0e --- /dev/null +++ b/src/audio_core/renderer/system_manager.h @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <list> +#include <memory> +#include <mutex> +#include <optional> +#include <thread> + +#include "audio_core/renderer/system.h" + +namespace Core { +namespace Timing { +struct EventType; +} +class System; +} // namespace Core + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class ADSP; +class AudioRenderer_Mailbox; +} // namespace ADSP + +/** + * Manages all audio renderers, responsible for triggering command list generation and signalling + * the ADSP. + */ +class SystemManager { +public: + explicit SystemManager(Core::System& core); + ~SystemManager(); + + /** + * Initialize the system manager, called when any system is registered. + * + * @return True if sucessfully initialized, otherwise false. + */ + bool InitializeUnsafe(); + + /** + * Stop the system manager. + */ + void Stop(); + + /** + * Add an audio render system to the manager. + * The manager does not own the system, so do not free it without calling Remove. + * + * @param system - The system to add. + * @return True if succesfully added, otherwise false. + */ + bool Add(System& system); + + /** + * Remove an audio render system from the manager. + * + * @param system - The system to remove. + * @return True if succesfully removed, otherwise false. + */ + bool Remove(System& system); + +private: + /** + * Main thread responsible for command generation. + */ + void ThreadFunc(); + + /** + * Signalling core timing thread to run ThreadFunc. + */ + std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time); + + /** + * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc. + * + * @param paused - Are we pausing or resuming? + */ + void PauseCallback(bool paused); + + enum class StreamState { + Filling, + Steady, + Draining, + }; + + /// Core system + Core::System& core; + /// List of pointers to managed systems + std::list<System*> systems{}; + /// Main worker thread for generating command lists + std::jthread thread; + /// Mutex for the systems + std::mutex mutex1{}; + /// Mutex for adding/removing systems + std::mutex mutex2{}; + /// Is the system manager thread active? + std::atomic<bool> active{}; + /// Reference to the ADSP for communication + ADSP::ADSP& adsp; + /// AudioRenderer mailbox for communication + ADSP::AudioRenderer_Mailbox* mailbox{}; + /// Core timing event to signal main thread + std::shared_ptr<Core::Timing::EventType> thread_event; + /// Atomic for main thread to wait on + std::atomic<bool> update{}; + /// Current state of the streams + StreamState state{StreamState::Filling}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_info.cpp b/src/audio_core/renderer/upsampler/upsampler_info.cpp new file mode 100644 index 000000000..e3d2f7db0 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_info.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/upsampler/upsampler_info.h" + +namespace AudioCore::AudioRenderer {} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_info.h b/src/audio_core/renderer/upsampler/upsampler_info.h new file mode 100644 index 000000000..a43c15af3 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_info.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "audio_core/common/common.h" +#include "audio_core/renderer/upsampler/upsampler_state.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class UpsamplerManager; + +/** + * Manages information needed to upsample a mix buffer. + */ +struct UpsamplerInfo { + /// States used by the AudioRenderer across calls. + std::array<UpsamplerState, MaxChannels> states{}; + /// Pointer to the manager + UpsamplerManager* manager{}; + /// Pointer to the samples to be upsampled + CpuAddr samples_pos{}; + /// Target number of samples to upsample to + u32 sample_count{}; + /// Number of channels to upsample + u32 input_count{}; + /// Is this upsampler enabled? + bool enabled{}; + /// Mix buffer indexes to be upsampled + std::array<s16, MaxChannels> inputs{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.cpp b/src/audio_core/renderer/upsampler/upsampler_manager.cpp new file mode 100644 index 000000000..4c76a5066 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_manager.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/upsampler/upsampler_manager.h" + +namespace AudioCore::AudioRenderer { + +UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_, + std::span<s32> workbuffer_) + : count{count_}, upsampler_infos{infos_}, workbuffer{workbuffer_} {} + +UpsamplerInfo* UpsamplerManager::Allocate() { + std::scoped_lock l{lock}; + + if (count == 0) { + return nullptr; + } + + u32 free_index{0}; + for (auto& upsampler : upsampler_infos) { + if (!upsampler.enabled) { + break; + } + free_index++; + } + + if (free_index >= count) { + return nullptr; + } + + auto& upsampler{upsampler_infos[free_index]}; + upsampler.manager = this; + upsampler.sample_count = TargetSampleCount; + upsampler.samples_pos = CpuAddr(&workbuffer[upsampler.sample_count * MaxChannels]); + upsampler.enabled = true; + return &upsampler; +} + +void UpsamplerManager::Free(UpsamplerInfo* info) { + std::scoped_lock l{lock}; + info->enabled = false; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h new file mode 100644 index 000000000..70cd42b08 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_manager.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <mutex> +#include <span> + +#include "audio_core/renderer/upsampler/upsampler_info.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Manages and has utility functions for upsampler infos. + */ +class UpsamplerManager { +public: + UpsamplerManager(u32 count, std::span<UpsamplerInfo> infos, std::span<s32> workbuffer); + + /** + * Allocate a new UpsamplerInfo. + * + * @return The allocated upsampler, may be nullptr if alloc failed. + */ + UpsamplerInfo* Allocate(); + + /** + * Free the given upsampler. + * + * @param The upsampler to be freed. + */ + void Free(UpsamplerInfo* info); + +private: + /// Maximum number of upsamplers in the buffer + const u32 count; + /// Upsamplers buffer + std::span<UpsamplerInfo> upsampler_infos; + /// Workbuffer for upsampling samples + std::span<s32> workbuffer; + /// Lock for allocate/free + std::mutex lock{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_state.h b/src/audio_core/renderer/upsampler/upsampler_state.h new file mode 100644 index 000000000..28cebe200 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_state.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Upsampling state used by the AudioRenderer across calls. + */ +struct UpsamplerState { + static constexpr u16 HistorySize = 20; + + /// Source data to target data ratio. E.g 48'000/32'000 = 1.5 + Common::FixedPoint<16, 16> ratio; + /// Sample history + std::array<Common::FixedPoint<24, 8>, HistorySize> history; + /// Size of the sinc coefficient window + u16 window_size; + /// Read index for the history + u16 history_output_index; + /// Write index for the history + u16 history_input_index; + /// Start offset within the history, fixed to 0 + u16 history_start_index; + /// Ebd offset within the history, fixed to HistorySize + u16 history_end_index; + /// Is this state initialized? + bool initialized; + /// Index of the current sample. + /// E.g 16K -> 48K has a ratio of 3, so this will be 0-2. + /// See the Upsample command in the AudioRenderer for more information. + u8 sample_index; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_channel_resource.h b/src/audio_core/renderer/voice/voice_channel_resource.h new file mode 100644 index 000000000..26ab4ccce --- /dev/null +++ b/src/audio_core/renderer/voice/voice_channel_resource.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Represents one channel for mixing a voice. + */ +class VoiceChannelResource { +public: + struct InParameter { + /* 0x00 */ u32 id; + /* 0x04 */ std::array<f32, MaxMixBuffers> mix_volumes; + /* 0x64 */ bool in_use; + /* 0x65 */ char unk65[0xB]; + }; + static_assert(sizeof(InParameter) == 0x70, + "VoiceChannelResource::InParameter has the wrong size!"); + + explicit VoiceChannelResource(u32 id_) : id{id_} {} + + /// Current volume for each mix buffer + std::array<f32, MaxMixBuffers> mix_volumes{}; + /// Previous volume for each mix buffer + std::array<f32, MaxMixBuffers> prev_mix_volumes{}; + /// Id of this resource + const u32 id; + /// Is this resource in use? + bool in_use{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_context.cpp b/src/audio_core/renderer/voice/voice_context.cpp new file mode 100644 index 000000000..eafb51b01 --- /dev/null +++ b/src/audio_core/renderer/voice/voice_context.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <ranges> + +#include "audio_core/renderer/voice/voice_context.h" + +namespace AudioCore::AudioRenderer { + +VoiceState& VoiceContext::GetDspSharedState(const u32 index) { + if (index >= dsp_states.size()) { + LOG_ERROR(Service_Audio, "Invalid voice dsp state index {:04X}", index); + } + return dsp_states[index]; +} + +VoiceChannelResource& VoiceContext::GetChannelResource(const u32 index) { + if (index >= channel_resources.size()) { + LOG_ERROR(Service_Audio, "Invalid voice channel resource index {:04X}", index); + } + return channel_resources[index]; +} + +void VoiceContext::Initialize(std::span<VoiceInfo*> sorted_voice_infos_, + std::span<VoiceInfo> voice_infos_, + std::span<VoiceChannelResource> voice_channel_resources_, + std::span<VoiceState> cpu_states_, std::span<VoiceState> dsp_states_, + const u32 voice_count_) { + sorted_voice_info = sorted_voice_infos_; + voices = voice_infos_; + channel_resources = voice_channel_resources_; + cpu_states = cpu_states_; + dsp_states = dsp_states_; + voice_count = voice_count_; + active_count = 0; +} + +VoiceInfo* VoiceContext::GetSortedInfo(const u32 index) { + if (index >= sorted_voice_info.size()) { + LOG_ERROR(Service_Audio, "Invalid voice sorted info index {:04X}", index); + } + return sorted_voice_info[index]; +} + +VoiceInfo& VoiceContext::GetInfo(const u32 index) { + if (index >= voices.size()) { + LOG_ERROR(Service_Audio, "Invalid voice info index {:04X}", index); + } + return voices[index]; +} + +VoiceState& VoiceContext::GetState(const u32 index) { + if (index >= cpu_states.size()) { + LOG_ERROR(Service_Audio, "Invalid voice cpu state index {:04X}", index); + } + return cpu_states[index]; +} + +u32 VoiceContext::GetCount() const { + return voice_count; +} + +u32 VoiceContext::GetActiveCount() const { + return active_count; +} + +void VoiceContext::SetActiveCount(const u32 active_count_) { + active_count = active_count_; +} + +void VoiceContext::SortInfo() { + for (u32 i = 0; i < voice_count; i++) { + sorted_voice_info[i] = &voices[i]; + } + + std::ranges::sort(sorted_voice_info, [](const VoiceInfo* a, const VoiceInfo* b) { + return a->priority != b->priority ? a->priority < b->priority + : a->sort_order < b->sort_order; + }); +} + +void VoiceContext::UpdateStateByDspShared() { + std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState)); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_context.h b/src/audio_core/renderer/voice/voice_context.h new file mode 100644 index 000000000..43b677154 --- /dev/null +++ b/src/audio_core/renderer/voice/voice_context.h @@ -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/renderer/voice/voice_channel_resource.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "audio_core/renderer/voice/voice_state.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Contains all voices, with utility functions for managing them. + */ +class VoiceContext { +public: + /** + * Get the AudioRenderer state for a given index + * + * @param index - State index to get. + * @return The requested voice state. + */ + VoiceState& GetDspSharedState(u32 index); + + /** + * Get the channel resource for a given index + * + * @param index - Resource index to get. + * @return The requested voice resource. + */ + VoiceChannelResource& GetChannelResource(u32 index); + + /** + * Initialize the voice context. + * + * @param sorted_voice_infos - Workbuffer for the sorted voices. + * @param voice_infos - Workbuffer for the voices. + * @param voice_channel_resources - Workbuffer for the voice channel resources. + * @param cpu_states - Workbuffer for the host-side voice states. + * @param dsp_states - Workbuffer for the AudioRenderer-side voice states. + * @param voice_count - The number of voices in each workbuffer. + */ + void Initialize(std::span<VoiceInfo*> sorted_voice_infos, std::span<VoiceInfo> voice_infos, + std::span<VoiceChannelResource> voice_channel_resources, + std::span<VoiceState> cpu_states, std::span<VoiceState> dsp_states, + u32 voice_count); + + /** + * Get a sorted voice with the given index. + * + * @param index - The sorted voice index to get. + * @return The sorted voice. + */ + VoiceInfo* GetSortedInfo(u32 index); + + /** + * Get a voice with the given index. + * + * @param index - The voice index to get. + * @return The voice. + */ + VoiceInfo& GetInfo(u32 index); + + /** + * Get a host voice state with the given index. + * + * @param index - The host voice state index to get. + * @return The voice state. + */ + VoiceState& GetState(u32 index); + + /** + * Get the maximum number of voices. + * Not all voices in the buffers may be in use, see GetActiveCount. + * + * @return The maximum number of voices. + */ + u32 GetCount() const; + + /** + * Get the number of active voices. + * Can be less than or equal to the maximum number of voices. + * + * @return The number of active voices. + */ + u32 GetActiveCount() const; + + /** + * Set the number of active voices. + * Can be less than or equal to the maximum number of voices. + * + * @param active_count - The new number of active voices. + */ + void SetActiveCount(u32 active_count); + + /** + * Sort all voices. Results are available via GetSortedInfo. + * Voices are sorted descendingly, according to priority, and then sort order. + */ + void SortInfo(); + + /** + * Update all voice states, copying AudioRenderer-side states to host-side states. + */ + void UpdateStateByDspShared(); + +private: + /// Sorted voices + std::span<VoiceInfo*> sorted_voice_info{}; + /// Voices + std::span<VoiceInfo> voices{}; + /// Channel resources + std::span<VoiceChannelResource> channel_resources{}; + /// Host-side voice states + std::span<VoiceState> cpu_states{}; + /// AudioRenderer-side voice states + std::span<VoiceState> dsp_states{}; + /// Maximum number of voices + u32 voice_count{}; + /// Number of active voices + u32 active_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_info.cpp b/src/audio_core/renderer/voice/voice_info.cpp new file mode 100644 index 000000000..1849eeb57 --- /dev/null +++ b/src/audio_core/renderer/voice/voice_info.cpp @@ -0,0 +1,408 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/memory/pool_mapper.h" +#include "audio_core/renderer/voice/voice_context.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "audio_core/renderer/voice/voice_state.h" + +namespace AudioCore::AudioRenderer { + +VoiceInfo::VoiceInfo() { + Initialize(); +} + +void VoiceInfo::Initialize() { + in_use = false; + is_new = false; + id = 0; + node_id = 0; + current_play_state = ServerPlayState::Stopped; + src_quality = SrcQuality::Medium; + priority = LowestVoicePriority; + sample_format = SampleFormat::Invalid; + sample_rate = 0; + channel_count = 0; + wave_buffer_count = 0; + wave_buffer_index = 0; + pitch = 0.0f; + volume = 0.0f; + prev_volume = 0.0f; + mix_id = UnusedMixId; + splitter_id = UnusedSplitterId; + biquads = {}; + biquad_initialized = {}; + voice_dropped = false; + data_unmapped = false; + buffer_unmapped = false; + flush_buffer_count = 0; + + data_address.Setup(0, 0); + for (auto& wavebuffer : wavebuffers) { + wavebuffer.Initialize(); + } +} + +bool VoiceInfo::ShouldUpdateParameters(const InParameter& params) const { + return data_address.GetCpuAddr() != params.src_data_address || + data_address.GetSize() != params.src_data_size || data_unmapped; +} + +void VoiceInfo::UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, + const PoolMapper& pool_mapper, const BehaviorInfo& behavior) { + in_use = params.in_use; + id = params.id; + node_id = params.node_id; + UpdatePlayState(params.play_state); + UpdateSrcQuality(params.src_quality); + priority = params.priority; + sort_order = params.sort_order; + sample_rate = params.sample_rate; + sample_format = params.sample_format; + channel_count = static_cast<s8>(params.channel_count); + pitch = params.pitch; + volume = params.volume; + biquads = params.biquads; + wave_buffer_count = params.wave_buffer_count; + wave_buffer_index = params.wave_buffer_index; + + if (behavior.IsFlushVoiceWaveBuffersSupported()) { + flush_buffer_count += params.flush_buffer_count; + } + + mix_id = params.mix_id; + + if (behavior.IsSplitterSupported()) { + splitter_id = params.splitter_id; + } else { + splitter_id = UnusedSplitterId; + } + + channel_resource_ids = params.channel_resource_ids; + + flags &= u16(~0b11); + if (behavior.IsVoicePlayedSampleCountResetAtLoopPointSupported()) { + flags |= u16(params.flags.IsVoicePlayedSampleCountResetAtLoopPointSupported); + } + + if (behavior.IsVoicePitchAndSrcSkippedSupported()) { + flags |= u16(params.flags.IsVoicePitchAndSrcSkippedSupported); + } + + if (params.clear_voice_drop) { + voice_dropped = false; + } + + if (ShouldUpdateParameters(params)) { + data_unmapped = !pool_mapper.TryAttachBuffer(error_info, data_address, + params.src_data_address, params.src_data_size); + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void VoiceInfo::UpdatePlayState(const PlayState state) { + last_play_state = current_play_state; + + switch (state) { + case PlayState::Started: + current_play_state = ServerPlayState::Started; + break; + case PlayState::Stopped: + if (current_play_state != ServerPlayState::Stopped) { + current_play_state = ServerPlayState::RequestStop; + } + break; + case PlayState::Paused: + current_play_state = ServerPlayState::Paused; + break; + default: + LOG_ERROR(Service_Audio, "Invalid input play state {}", static_cast<u32>(state)); + break; + } +} + +void VoiceInfo::UpdateSrcQuality(const SrcQuality quality) { + switch (quality) { + case SrcQuality::Medium: + src_quality = quality; + break; + case SrcQuality::High: + src_quality = quality; + break; + case SrcQuality::Low: + src_quality = quality; + break; + default: + LOG_ERROR(Service_Audio, "Invalid input src quality {}", static_cast<u32>(quality)); + break; + } +} + +void VoiceInfo::UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, + [[maybe_unused]] u32 error_count, const InParameter& params, + std::span<VoiceState*> voice_states, + const PoolMapper& pool_mapper, const BehaviorInfo& behavior) { + if (params.is_new) { + for (size_t i = 0; i < wavebuffers.size(); i++) { + wavebuffers[i].Initialize(); + } + + for (s8 channel = 0; channel < static_cast<s8>(params.channel_count); channel++) { + voice_states[channel]->wave_buffer_valid.fill(false); + } + } + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + UpdateWaveBuffer(error_infos[i], wavebuffers[i], params.wave_buffer_internal[i], + params.sample_format, voice_states[0]->wave_buffer_valid[i], pool_mapper, + behavior); + } +} + +void VoiceInfo::UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, + WaveBuffer& wave_buffer, + const WaveBufferInternal& wave_buffer_internal, + const SampleFormat sample_format_, const bool valid, + const PoolMapper& pool_mapper, const BehaviorInfo& behavior) { + if (!valid && wave_buffer.sent_to_DSP && wave_buffer.buffer_address.GetCpuAddr() != 0) { + pool_mapper.ForceUnmapPointer(wave_buffer.buffer_address); + wave_buffer.buffer_address.Setup(0, 0); + } + + if (!ShouldUpdateWaveBuffer(wave_buffer_internal)) { + return; + } + + switch (sample_format_) { + case SampleFormat::PcmInt16: { + constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmInt16)}; + if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size || + wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) { + LOG_ERROR(Service_Audio, "Invalid PCM16 start/end wavebuffer sizes!"); + error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; + error_info[0].address = wave_buffer_internal.address; + return; + } + } break; + + case SampleFormat::PcmFloat: { + constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmFloat)}; + if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size || + wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) { + LOG_ERROR(Service_Audio, "Invalid PCMFloat start/end wavebuffer sizes!"); + error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; + error_info[0].address = wave_buffer_internal.address; + return; + } + } break; + + case SampleFormat::Adpcm: { + const auto start_frame{wave_buffer_internal.start_offset / 14}; + auto start_extra{wave_buffer_internal.start_offset % 14 == 0 + ? 0 + : (wave_buffer_internal.start_offset % 14) / 2 + 1 + + ((wave_buffer_internal.start_offset % 14) % 2)}; + const auto start{start_frame * 8 + start_extra}; + + const auto end_frame{wave_buffer_internal.end_offset / 14}; + const auto end_extra{wave_buffer_internal.end_offset % 14 == 0 + ? 0 + : (wave_buffer_internal.end_offset % 14) / 2 + 1 + + ((wave_buffer_internal.end_offset % 14) % 2)}; + const auto end{end_frame * 8 + end_extra}; + + if (start > static_cast<s64>(wave_buffer_internal.size) || + end > static_cast<s64>(wave_buffer_internal.size)) { + LOG_ERROR(Service_Audio, "Invalid ADPCM start/end wavebuffer sizes!"); + error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; + error_info[0].address = wave_buffer_internal.address; + return; + } + } break; + + default: + break; + } + + if (wave_buffer_internal.start_offset < 0 || wave_buffer_internal.end_offset < 0) { + LOG_ERROR(Service_Audio, "Invalid input start/end wavebuffer sizes!"); + error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; + error_info[0].address = wave_buffer_internal.address; + return; + } + + wave_buffer.start_offset = wave_buffer_internal.start_offset; + wave_buffer.end_offset = wave_buffer_internal.end_offset; + wave_buffer.loop = wave_buffer_internal.loop; + wave_buffer.stream_ended = wave_buffer_internal.stream_ended; + wave_buffer.sent_to_DSP = false; + wave_buffer.loop_start_offset = wave_buffer_internal.loop_start; + wave_buffer.loop_end_offset = wave_buffer_internal.loop_end; + wave_buffer.loop_count = wave_buffer_internal.loop_count; + + buffer_unmapped = + !pool_mapper.TryAttachBuffer(error_info[0], wave_buffer.buffer_address, + wave_buffer_internal.address, wave_buffer_internal.size); + + if (sample_format_ == SampleFormat::Adpcm && behavior.IsAdpcmLoopContextBugFixed() && + wave_buffer_internal.context_address != 0) { + buffer_unmapped = !pool_mapper.TryAttachBuffer(error_info[1], wave_buffer.context_address, + wave_buffer_internal.context_address, + wave_buffer_internal.context_size) || + data_unmapped; + } else { + wave_buffer.context_address.Setup(0, 0); + } +} + +bool VoiceInfo::ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const { + return !wave_buffer_internal.sent_to_DSP || buffer_unmapped; +} + +void VoiceInfo::WriteOutStatus(OutStatus& out_status, const InParameter& params, + std::span<VoiceState*> voice_states) { + if (params.is_new) { + is_new = true; + } + + if (params.is_new || is_new) { + out_status.played_sample_count = 0; + out_status.wave_buffers_consumed = 0; + out_status.voice_dropped = false; + } else { + out_status.played_sample_count = voice_states[0]->played_sample_count; + out_status.wave_buffers_consumed = voice_states[0]->wave_buffers_consumed; + out_status.voice_dropped = voice_dropped; + } +} + +bool VoiceInfo::ShouldSkip() const { + return !in_use || wave_buffer_count == 0 || data_unmapped || buffer_unmapped || voice_dropped; +} + +bool VoiceInfo::HasAnyConnection() const { + return mix_id != UnusedMixId || splitter_id != UnusedSplitterId; +} + +void VoiceInfo::FlushWaveBuffers(const u32 flush_count, std::span<VoiceState*> voice_states, + const s8 channel_count_) { + auto wave_index{wave_buffer_index}; + + for (size_t i = 0; i < flush_count; i++) { + wavebuffers[wave_index].sent_to_DSP = true; + + for (s8 j = 0; j < channel_count_; j++) { + auto voice_state{voice_states[j]}; + if (voice_state->wave_buffer_index == wave_index) { + voice_state->wave_buffer_index = + (voice_state->wave_buffer_index + 1) % MaxWaveBuffers; + voice_state->wave_buffers_consumed++; + } + voice_state->wave_buffer_valid[wave_index] = false; + } + + wave_index = (wave_index + 1) % MaxWaveBuffers; + } +} + +bool VoiceInfo::UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states) { + if (flush_buffer_count > 0) { + FlushWaveBuffers(flush_buffer_count, voice_states, channel_count); + flush_buffer_count = 0; + } + + switch (current_play_state) { + case ServerPlayState::Started: + for (u32 i = 0; i < MaxWaveBuffers; i++) { + if (!wavebuffers[i].sent_to_DSP) { + for (s8 channel = 0; channel < channel_count; channel++) { + voice_states[channel]->wave_buffer_valid[i] = true; + } + wavebuffers[i].sent_to_DSP = true; + } + } + + was_playing = false; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + if (voice_states[0]->wave_buffer_valid[i]) { + return true; + } + } + break; + + case ServerPlayState::Stopped: + case ServerPlayState::Paused: + for (auto& wavebuffer : wavebuffers) { + if (!wavebuffer.sent_to_DSP) { + wavebuffer.buffer_address.GetReference(true); + wavebuffer.context_address.GetReference(true); + } + } + + if (sample_format == SampleFormat::Adpcm && data_address.GetCpuAddr() != 0) { + data_address.GetReference(true); + } + + was_playing = last_play_state == ServerPlayState::Started; + break; + + case ServerPlayState::RequestStop: + for (u32 i = 0; i < MaxWaveBuffers; i++) { + wavebuffers[i].sent_to_DSP = true; + + for (s8 channel = 0; channel < channel_count; channel++) { + if (voice_states[channel]->wave_buffer_valid[i]) { + voice_states[channel]->wave_buffer_index = + (voice_states[channel]->wave_buffer_index + 1) % MaxWaveBuffers; + voice_states[channel]->wave_buffers_consumed++; + } + voice_states[channel]->wave_buffer_valid[i] = false; + } + } + + for (s8 channel = 0; channel < channel_count; channel++) { + voice_states[channel]->offset = 0; + voice_states[channel]->played_sample_count = 0; + voice_states[channel]->adpcm_context = {}; + voice_states[channel]->sample_history.fill(0); + voice_states[channel]->fraction = 0; + } + + current_play_state = ServerPlayState::Stopped; + was_playing = last_play_state == ServerPlayState::Started; + break; + } + + return was_playing; +} + +bool VoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { + std::array<VoiceState*, MaxChannels> voice_states{}; + + if (is_new) { + ResetResources(voice_context); + prev_volume = volume; + is_new = false; + } + + for (s8 channel = 0; channel < channel_count; channel++) { + voice_states[channel] = &voice_context.GetDspSharedState(channel_resource_ids[channel]); + } + + return UpdateParametersForCommandGeneration(voice_states); +} + +void VoiceInfo::ResetResources(VoiceContext& voice_context) const { + for (s8 channel = 0; channel < channel_count; channel++) { + auto& state{voice_context.GetDspSharedState(channel_resource_ids[channel])}; + state = {}; + + auto& channel_resource{voice_context.GetChannelResource(channel_resource_ids[channel])}; + channel_resource.prev_mix_volumes = channel_resource.mix_volumes; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h new file mode 100644 index 000000000..896723e0c --- /dev/null +++ b/src/audio_core/renderer/voice/voice_info.h @@ -0,0 +1,378 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <bitset> + +#include "audio_core/common/common.h" +#include "audio_core/common/wave_buffer.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/memory/address_info.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class PoolMapper; +class VoiceContext; +struct VoiceState; + +/** + * Represents one voice. Voices are essentially noises, and they can be further mixed and have + * effects applied to them, but voices are the basis of all sounds. + */ +class VoiceInfo { +public: + enum class ServerPlayState { + Started, + Stopped, + RequestStop, + Paused, + }; + + struct Flags { + u8 IsVoicePlayedSampleCountResetAtLoopPointSupported : 1; + u8 IsVoicePitchAndSrcSkippedSupported : 1; + }; + + /** + * A wavebuffer contains information on the data source buffers. + */ + struct WaveBuffer { + void Copy(WaveBufferVersion1& other) { + other.buffer = buffer_address.GetReference(true); + other.buffer_size = buffer_address.GetSize(); + other.start_offset = start_offset; + other.end_offset = end_offset; + other.loop = loop; + other.stream_ended = stream_ended; + + if (context_address.GetCpuAddr()) { + other.context = context_address.GetReference(true); + other.context_size = context_address.GetSize(); + } else { + other.context = CpuAddr(0); + other.context_size = 0; + } + } + + void Copy(WaveBufferVersion2& other) { + other.buffer = buffer_address.GetReference(true); + other.buffer_size = buffer_address.GetSize(); + other.start_offset = start_offset; + other.end_offset = end_offset; + other.loop_start_offset = loop_start_offset; + other.loop_end_offset = loop_end_offset; + other.loop = loop; + other.loop_count = loop_count; + other.stream_ended = stream_ended; + + if (context_address.GetCpuAddr()) { + other.context = context_address.GetReference(true); + other.context_size = context_address.GetSize(); + } else { + other.context = CpuAddr(0); + other.context_size = 0; + } + } + + void Initialize() { + buffer_address.Setup(0, 0); + context_address.Setup(0, 0); + start_offset = 0; + end_offset = 0; + loop = false; + stream_ended = false; + sent_to_DSP = true; + loop_start_offset = 0; + loop_end_offset = 0; + loop_count = 0; + } + /// Game memory address of the wavebuffer data + AddressInfo buffer_address{0, 0}; + /// Context for decoding, used for ADPCM + AddressInfo context_address{0, 0}; + /// Starting offset for the wavebuffer + u32 start_offset{}; + /// Ending offset the wavebuffer + u32 end_offset{}; + /// Should this wavebuffer loop? + bool loop{}; + /// Has this wavebuffer ended? + bool stream_ended{}; + /// Has this wavebuffer been sent to the AudioRenderer? + bool sent_to_DSP{true}; + /// Starting offset when looping, can differ from start_offset + u32 loop_start_offset{}; + /// Ending offset when looping, can differ from end_offset + u32 loop_end_offset{}; + /// Number of times to loop this wavebuffer + s32 loop_count{}; + }; + + struct WaveBufferInternal { + /* 0x00 */ CpuAddr address; + /* 0x08 */ u64 size; + /* 0x10 */ s32 start_offset; + /* 0x14 */ s32 end_offset; + /* 0x18 */ bool loop; + /* 0x19 */ bool stream_ended; + /* 0x1A */ bool sent_to_DSP; + /* 0x1C */ s32 loop_count; + /* 0x20 */ CpuAddr context_address; + /* 0x28 */ u64 context_size; + /* 0x30 */ u32 loop_start; + /* 0x34 */ u32 loop_end; + }; + static_assert(sizeof(WaveBufferInternal) == 0x38, + "VoiceInfo::WaveBufferInternal has the wrong size!"); + + struct BiquadFilterParameter { + /* 0x00 */ bool enabled; + /* 0x02 */ std::array<s16, 3> b; + /* 0x08 */ std::array<s16, 2> a; + }; + static_assert(sizeof(BiquadFilterParameter) == 0xC, + "VoiceInfo::BiquadFilterParameter has the wrong size!"); + + struct InParameter { + /* 0x000 */ u32 id; + /* 0x004 */ u32 node_id; + /* 0x008 */ bool is_new; + /* 0x009 */ bool in_use; + /* 0x00A */ PlayState play_state; + /* 0x00B */ SampleFormat sample_format; + /* 0x00C */ u32 sample_rate; + /* 0x010 */ s32 priority; + /* 0x014 */ s32 sort_order; + /* 0x018 */ u32 channel_count; + /* 0x01C */ f32 pitch; + /* 0x020 */ f32 volume; + /* 0x024 */ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads; + /* 0x03C */ u32 wave_buffer_count; + /* 0x040 */ u16 wave_buffer_index; + /* 0x042 */ char unk042[0x6]; + /* 0x048 */ CpuAddr src_data_address; + /* 0x050 */ u64 src_data_size; + /* 0x058 */ u32 mix_id; + /* 0x05C */ u32 splitter_id; + /* 0x060 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal; + /* 0x140 */ std::array<u32, MaxChannels> channel_resource_ids; + /* 0x158 */ bool clear_voice_drop; + /* 0x159 */ u8 flush_buffer_count; + /* 0x15A */ char unk15A[0x2]; + /* 0x15C */ Flags flags; + /* 0x15D */ char unk15D[0x1]; + /* 0x15E */ SrcQuality src_quality; + /* 0x15F */ char unk15F[0x11]; + }; + static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!"); + + struct OutStatus { + /* 0x00 */ u64 played_sample_count; + /* 0x08 */ u32 wave_buffers_consumed; + /* 0x0C */ bool voice_dropped; + }; + static_assert(sizeof(OutStatus) == 0x10, "OutStatus::InParameter has the wrong size!"); + + VoiceInfo(); + + /** + * Initialize this voice. + */ + void Initialize(); + + /** + * Does this voice ned an update? + * + * @param params - Input parametetrs to check matching. + * @return True if this voice needs an update, otherwise false. + */ + bool ShouldUpdateParameters(const InParameter& params) const; + + /** + * Update the parameters of this voice. + * + * @param error_info - Output error code. + * @param params - Input parametters to udpate from. + * @param pool_mapper - Used to map buffers. + * @param behavior - behavior to check supported features. + */ + void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, + const PoolMapper& pool_mapper, const BehaviorInfo& behavior); + + /** + * Update the current play state. + * + * @param state - New play state for this voice. + */ + void UpdatePlayState(PlayState state); + + /** + * Update the current sample rate conversion quality. + * + * @param quality - New quality. + */ + void UpdateSrcQuality(SrcQuality quality); + + /** + * Update all wavebuffers. + * + * @param error_infos - Output 2D array of errors, 2 per wavebuffer. + * @param error_count - Number of errors provided. Unused. + * @param params - Input parametters to be used for the update. + * @param voice_states - The voice states for each channel in this voice to be updated. + * @param pool_mapper - Used to map the wavebuffers. + * @param behavior - Used to check for supported features. + */ + void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, + u32 error_count, const InParameter& params, + std::span<VoiceState*> voice_states, const PoolMapper& pool_mapper, + const BehaviorInfo& behavior); + + /** + * Update a wavebuffer. + * + * @param error_infos - Output array of errors. + * @param wave_buffer - The wavebuffer to be updated. + * @param wave_buffer_internal - Input parametters to be used for the update. + * @param sample_format - Sample format of the wavebuffer. + * @param valid - Is this wavebuffer valid? + * @param pool_mapper - Used to map the wavebuffers. + * @param behavior - Used to check for supported features. + */ + void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer, + const WaveBufferInternal& wave_buffer_internal, + SampleFormat sample_format, bool valid, const PoolMapper& pool_mapper, + const BehaviorInfo& behavior); + + /** + * Check if the input wavebuffer needs an update. + * + * @param wave_buffer_internal - Input wavebuffer parameters to check. + * @return True if the given wavebuffer needs an update, otherwise false. + */ + bool ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const; + + /** + * Write the number of played samples, number of consumed wavebuffers and if this voice was + * dropped, to the given out_status. + * + * @param out_status - Output status to be written to. + * @param in_params - Input parameters to check if the wavebuffer is new. + * @param voice_states - Current host voice states for this voice, source of the output. + */ + void WriteOutStatus(OutStatus& out_status, const InParameter& in_params, + std::span<VoiceState*> voice_states); + + /** + * Check if this voice should be skipped for command generation. + * Checks various things such as usage state, whether data is mapped etc. + * + * @return True if this voice should not be generated, otherwise false. + */ + bool ShouldSkip() const; + + /** + * Check if this voice has any mixing connections. + * + * @return True if this voice participes in mixing, otherwise false. + */ + bool HasAnyConnection() const; + + /** + * Flush flush_count wavebuffers, marking them as consumed. + * + * @param flush_count - Number of wavebuffers to flush. + * @param voice_states - Voice states for these wavebuffers. + * @param channel_count - Number of active channels. + */ + void FlushWaveBuffers(u32 flush_count, std::span<VoiceState*> voice_states, s8 channel_count); + + /** + * Update this voice's parameters on command generation, + * updating voice states and flushing if needed. + * + * @param voice_states - Voice states for these wavebuffers. + * @return True if this voice should be generated, otherwise false. + */ + bool UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states); + + /** + * Update this voice on command generation. + * + * @param voice_states - Voice states for these wavebuffers. + * @return True if this voice should be generated, otherwise false. + */ + bool UpdateForCommandGeneration(VoiceContext& voice_context); + + /** + * Reset the AudioRenderer-side voice states, and the channel resources for this voice. + * + * @param voice_context - Context from which to get the resources. + */ + void ResetResources(VoiceContext& voice_context) const; + + /// Is this voice in use? + bool in_use{}; + /// Is this voice new? + bool is_new{}; + /// Was this voice last playing? Used for depopping + bool was_playing{}; + /// Sample format of the wavebuffers in this voice + SampleFormat sample_format{}; + /// Sample rate of the wavebuffers in this voice + u32 sample_rate{}; + /// Number of channels in this voice + s8 channel_count{}; + /// Id of this voice + u32 id{}; + /// Node id of this voice + u32 node_id{}; + /// Mix id this voice is mixed to + u32 mix_id{}; + /// Play state of this voice + ServerPlayState current_play_state{ServerPlayState::Stopped}; + /// Last play state of this voice + ServerPlayState last_play_state{ServerPlayState::Started}; + /// Priority of this voice, lower is higher + s32 priority{}; + /// Sort order of this voice, used when same priority + s32 sort_order{}; + /// Pitch of this voice (for sample rate conversion) + f32 pitch{}; + /// Current volume of this voice + f32 volume{}; + /// Previous volume of this voice + f32 prev_volume{}; + /// Biquad filters for generating filter commands on this voice + std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{}; + /// Number of active wavebuffers + u32 wave_buffer_count{}; + /// Current playing wavebuffer index + u16 wave_buffer_index{}; + /// Flags controlling decode behavior + u16 flags{}; + /// Game memory for ADPCM coefficients + AddressInfo data_address{0, 0}; + /// Wavebuffers + std::array<WaveBuffer, MaxWaveBuffers> wavebuffers{}; + /// Channel resources for this voice + std::array<u32, MaxChannels> channel_resource_ids{}; + /// Splitter id this voice is connected with + s32 splitter_id{UnusedSplitterId}; + /// Sample rate conversion quality + SrcQuality src_quality{SrcQuality::Medium}; + /// Was this voice dropped due to limited time? + bool voice_dropped{}; + /// Is this voice's coefficient (data_address) unmapped? + bool data_unmapped{}; + /// Is this voice's buffers (wavebuffer data and ADPCM context) unmapped? + bool buffer_unmapped{}; + /// Initialisation state of the biquads + std::array<bool, MaxBiquadFilters> biquad_initialized{}; + /// Number of wavebuffers to flush + u8 flush_buffer_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_state.h b/src/audio_core/renderer/voice/voice_state.h new file mode 100644 index 000000000..d5497e2fb --- /dev/null +++ b/src/audio_core/renderer/voice/voice_state.h @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "audio_core/common/common.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer, + * host-side is updated on the next iteration. + */ +struct VoiceState { + /** + * State of the voice's biquad filter. + */ + struct BiquadFilterState { + Common::FixedPoint<50, 14> s0; + Common::FixedPoint<50, 14> s1; + Common::FixedPoint<50, 14> s2; + Common::FixedPoint<50, 14> s3; + }; + + /** + * Context for ADPCM decoding. + */ + struct AdpcmContext { + u16 header; + s16 yn0; + s16 yn1; + }; + + /// Number of samples played + u64 played_sample_count; + /// Current offset from the starting offset + u32 offset; + /// Currently active wavebuffer index + u32 wave_buffer_index; + /// Array of which wavebuffers are currently valid + + std::array<bool, MaxWaveBuffers> wave_buffer_valid; + /// Number of wavebuffers consumed, given back to the game + u32 wave_buffers_consumed; + /// History of samples, used for rate conversion + + std::array<s16, MaxWaveBuffers * 2> sample_history; + /// Current read fraction, used for resampling + Common::FixedPoint<49, 15> fraction; + /// Current adpcm context + AdpcmContext adpcm_context; + /// Current biquad states, used when filtering + + std::array<std::array<BiquadFilterState, MaxBiquadFilters>, MaxBiquadFilters> biquad_states; + /// Previous samples + std::array<s32, MaxMixBuffers> previous_samples; + /// Unused + u32 external_context_size; + /// Unused + bool external_context_enabled; + /// Was this voice dropped? + bool voice_dropped; + /// Number of times the wavebuffer has looped + s32 loop_count; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp deleted file mode 100644 index a10ba4044..000000000 --- a/src/audio_core/sdl2_sink.cpp +++ /dev/null @@ -1,160 +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/sdl2_sink.h" -#include "audio_core/stream.h" -#include "common/assert.h" -#include "common/logging/log.h" -//#include "common/settings.h" - -// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wimplicit-fallthrough" -#endif -#include <SDL.h> -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -namespace AudioCore { - -class SDLSinkStream final : public SinkStream { -public: - SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device) - : num_channels{std::min(num_channels_, 6u)} { - - SDL_AudioSpec spec; - spec.freq = sample_rate; - spec.channels = static_cast<u8>(num_channels); - spec.format = AUDIO_S16SYS; - spec.samples = 4096; - spec.callback = nullptr; - - SDL_AudioSpec obtained; - if (output_device.empty()) { - dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0); - } else { - dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0); - } - - if (dev == 0) { - LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError()); - return; - } - - SDL_PauseAudioDevice(dev, 0); - } - - ~SDLSinkStream() override { - if (dev == 0) { - return; - } - - SDL_CloseAudioDevice(dev); - } - - 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))); - } - int ret = SDL_QueueAudio(dev, static_cast<const void*>(buf.data()), - static_cast<u32>(buf.size() * sizeof(s16))); - if (ret < 0) - LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); - return; - } - - int ret = SDL_QueueAudio(dev, static_cast<const void*>(samples.data()), - static_cast<u32>(samples.size() * sizeof(s16))); - if (ret < 0) - LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); - } - - std::size_t SamplesInQueue(u32 channel_count) const override { - if (dev == 0) - return 0; - - return SDL_GetQueuedAudioSize(dev) / (channel_count * sizeof(s16)); - } - - void Flush() override { - should_flush = true; - } - - u32 GetNumChannels() const { - return num_channels; - } - -private: - SDL_AudioDeviceID dev = 0; - u32 num_channels{}; - std::atomic<bool> should_flush{}; -}; - -SDLSink::SDLSink(std::string_view target_device_name) { - if (!SDL_WasInit(SDL_INIT_AUDIO)) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); - return; - } - } - - if (target_device_name != auto_device_name && !target_device_name.empty()) { - output_device = target_device_name; - } else { - output_device.clear(); - } -} - -SDLSink::~SDLSink() = default; - -SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) { - sink_streams.push_back( - std::make_unique<SDLSinkStream>(sample_rate, num_channels, output_device)); - return *sink_streams.back(); -} - -std::vector<std::string> ListSDLSinkDevices() { - std::vector<std::string> device_list; - - if (!SDL_WasInit(SDL_INIT_AUDIO)) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); - return {}; - } - } - - const int device_count = SDL_GetNumAudioDevices(0); - for (int i = 0; i < device_count; ++i) { - device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); - } - - return device_list; -} - -} // namespace AudioCore diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h deleted file mode 100644 index f1dd1d677..000000000 --- a/src/audio_core/sdl2_sink.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <string> -#include <vector> - -#include "audio_core/sink.h" - -namespace AudioCore { - -class SDLSink final : public Sink { -public: - explicit SDLSink(std::string_view device_id); - ~SDLSink() override; - - SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, - const std::string& name) override; - -private: - std::string output_device; - std::vector<SinkStreamPtr> sink_streams; -}; - -std::vector<std::string> ListSDLSinkDevices(); - -} // namespace AudioCore diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h deleted file mode 100644 index 3c03554fa..000000000 --- a/src/audio_core/sink.h +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <memory> -#include <string> - -#include "audio_core/sink_stream.h" -#include "common/common_types.h" - -namespace AudioCore { - -constexpr char auto_device_name[] = "auto"; - -/** - * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed - * PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate. - * They are dumb outputs. - */ -class Sink { -public: - virtual ~Sink() = default; - virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, - const std::string& name) = 0; -}; - -using SinkPtr = std::unique_ptr<Sink>; - -} // namespace AudioCore diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp new file mode 100644 index 000000000..a4e28de6d --- /dev/null +++ b/src/audio_core/sink/cubeb_sink.cpp @@ -0,0 +1,651 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <atomic> +#include <span> + +#include "audio_core/audio_core.h" +#include "audio_core/audio_event.h" +#include "audio_core/audio_manager.h" +#include "audio_core/sink/cubeb_sink.h" +#include "audio_core/sink/sink_stream.h" +#include "common/assert.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" +#include "common/reader_writer_queue.h" +#include "common/ring_buffer.h" +#include "common/settings.h" +#include "core/core.h" + +#ifdef _WIN32 +#include <objbase.h> +#undef CreateEvent +#endif + +namespace AudioCore::Sink { +/** + * Cubeb sink stream, responsible for sinking samples to hardware. + */ +class CubebSinkStream final : public SinkStream { +public: + /** + * Create a new sink stream. + * + * @param ctx_ - Cubeb context to create this stream with. + * @param device_channels_ - Number of channels supported by the hardware. + * @param system_channels_ - Number of channels the audio systems expect. + * @param output_device - Cubeb output device id. + * @param input_device - Cubeb input device id. + * @param name_ - Name of this stream. + * @param type_ - Type of this stream. + * @param system_ - Core system. + * @param event - Event used only for audio renderer, signalled on buffer consume. + */ + CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, + cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, + const StreamType type_, Core::System& system_) + : ctx{ctx_}, type{type_}, system{system_} { +#ifdef _WIN32 + CoInitializeEx(nullptr, COINIT_MULTITHREADED); +#endif + name = name_; + device_channels = device_channels_; + system_channels = system_channels_; + + cubeb_stream_params params{}; + params.rate = TargetSampleRate; + params.channels = device_channels; + params.format = CUBEB_SAMPLE_S16LE; + params.prefs = CUBEB_STREAM_PREF_NONE; + switch (params.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{0}; + const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &minimum_latency); + if (latency_error != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error); + minimum_latency = 256U; + } + + minimum_latency = std::max(minimum_latency, 256u); + + playing_buffer.consumed = true; + + LOG_DEBUG(Service_Audio, + "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " + "latency {}", + name, type, params.rate, params.channels, system_channels, minimum_latency); + + auto init_error{0}; + if (type == StreamType::In) { + init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device, + ¶ms, output_device, nullptr, minimum_latency, + &CubebSinkStream::DataCallback, + &CubebSinkStream::StateCallback, this); + } else { + init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device, + nullptr, output_device, ¶ms, minimum_latency, + &CubebSinkStream::DataCallback, + &CubebSinkStream::StateCallback, this); + } + + if (init_error != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream, error: {}", init_error); + return; + } + } + + /** + * Destroy the sink stream. + */ + ~CubebSinkStream() override { + LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); + + if (!ctx) { + return; + } + + Finalize(); + +#ifdef _WIN32 + CoUninitialize(); +#endif + } + + /** + * Finalize the sink stream. + */ + void Finalize() override { + Stop(); + cubeb_stream_destroy(stream_backend); + } + + /** + * Start the sink stream. + * + * @param resume - Set to true if this is resuming the stream a previously-active stream. + * Default false. + */ + void Start(const bool resume = false) override { + if (!ctx) { + return; + } + + if (resume && was_playing) { + if (cubeb_stream_start(stream_backend) != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); + } + paused = false; + } else if (!resume) { + if (cubeb_stream_start(stream_backend) != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); + } + paused = false; + } + } + + /** + * Stop the sink stream. + */ + void Stop() override { + if (!ctx) { + return; + } + + if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); + } + + was_playing.store(!paused); + paused = true; + } + + /** + * Append a new buffer and its samples to a waiting queue to play. + * + * @param buffer - Audio buffer information to be queued. + * @param samples - The s16 samples to be queue for playback. + */ + void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override { + if (type == StreamType::In) { + queue.enqueue(buffer); + queued_buffers++; + } else { + constexpr s32 min{std::numeric_limits<s16>::min()}; + constexpr s32 max{std::numeric_limits<s16>::max()}; + + auto yuzu_volume{Settings::Volume()}; + auto volume{system_volume * device_volume * yuzu_volume}; + + if (system_channels == 6 && device_channels == 2) { + // We're given 6 channels, but our device only outputs 2, so downmix. + constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * + down_mix_coeff[0] + + samples[read_index + static_cast<u32>(Channels::Center)] * + down_mix_coeff[1] + + samples[read_index + static_cast<u32>(Channels::LFE)] * + down_mix_coeff[2] + + samples[read_index + static_cast<u32>(Channels::BackLeft)] * + down_mix_coeff[3]) * + volume) + .to_int()}; + + const auto right_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast<u32>(Channels::FrontRight)]) * + down_mix_coeff[0] + + samples[read_index + static_cast<u32>(Channels::Center)] * + down_mix_coeff[1] + + samples[read_index + static_cast<u32>(Channels::LFE)] * + down_mix_coeff[2] + + samples[read_index + static_cast<u32>(Channels::BackRight)] * + down_mix_coeff[3]) * + volume) + .to_int()}; + + samples[write_index + static_cast<u32>(Channels::FrontLeft)] = + static_cast<s16>(std::clamp(left_sample, min, max)); + samples[write_index + static_cast<u32>(Channels::FrontRight)] = + static_cast<s16>(std::clamp(right_sample, min, max)); + } + + samples.resize(samples.size() / system_channels * device_channels); + + } else if (system_channels == 2 && device_channels == 6) { + // We need moar samples! Not all games will provide 6 channel audio. + // TODO: Implement some upmixing here. Currently just passthrough, with other + // channels left as silence. + std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{static_cast<s16>(std::clamp( + static_cast<s32>( + static_cast<f32>( + samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; + + const auto right_sample{static_cast<s16>(std::clamp( + static_cast<s32>( + static_cast<f32>( + samples[read_index + static_cast<u32>(Channels::FrontRight)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = + right_sample; + } + samples = std::move(new_samples); + + } else if (volume != 1.0f) { + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast<s16>(std::clamp( + static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); + } + } + + samples_buffer.Push(samples); + queue.enqueue(buffer); + queued_buffers++; + } + } + + /** + * Release a buffer. Audio In only, will fill a buffer with recorded samples. + * + * @param num_samples - Maximum number of samples to receive. + * @return Vector of recorded samples. May have fewer than num_samples. + */ + std::vector<s16> ReleaseBuffer(const u64 num_samples) override { + static constexpr s32 min = std::numeric_limits<s16>::min(); + static constexpr s32 max = std::numeric_limits<s16>::max(); + + auto samples{samples_buffer.Pop(num_samples)}; + + // TODO: Up-mix to 6 channels if the game expects it. + // For audio input this is unlikely to ever be the case though. + + // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. + // TODO: Play with this and find something that works better. + auto volume{system_volume * device_volume * 8}; + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast<s16>( + std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); + } + + if (samples.size() < num_samples) { + samples.resize(num_samples, 0); + } + return samples; + } + + /** + * Check if a certain buffer has been consumed (fully played). + * + * @param tag - Unique tag of a buffer to check for. + * @return True if the buffer has been played, otherwise false. + */ + bool IsBufferConsumed(const u64 tag) override { + if (released_buffer.tag == 0) { + if (!released_buffers.try_dequeue(released_buffer)) { + return false; + } + } + + if (released_buffer.tag == tag) { + released_buffer.tag = 0; + return true; + } + return false; + } + + /** + * Empty out the buffer queue. + */ + void ClearQueue() override { + samples_buffer.Pop(); + while (queue.pop()) { + } + while (released_buffers.pop()) { + } + queued_buffers = 0; + released_buffer = {}; + playing_buffer = {}; + playing_buffer.consumed = true; + } + +private: + /** + * Signal events back to the audio system that a buffer was played/can be filled. + * + * @param buffer - Consumed audio buffer to be released. + */ + void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { + auto& manager{system.AudioCore().GetAudioManager()}; + switch (type) { + case StreamType::Out: + released_buffers.enqueue(buffer); + manager.SetEvent(Event::Type::AudioOutManager, true); + break; + case StreamType::In: + released_buffers.enqueue(buffer); + manager.SetEvent(Event::Type::AudioInManager, true); + break; + case StreamType::Render: + break; + } + } + + /** + * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will + * provide samples to be copied (audio in). + * + * @param stream - Cubeb-specific data about the stream. + * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. + * @param in_buff - Input buffer to be used if the stream is an input type. + * @param out_buff - Output buffer to be used if the stream is an output type. + * @param num_frames_ - Number of frames of audio in the buffers. Note: Not number of samples. + */ + static long DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data, + [[maybe_unused]] const void* in_buff, void* out_buff, + long num_frames_) { + auto* impl = static_cast<CubebSinkStream*>(user_data); + if (!impl) { + return -1; + } + + const std::size_t num_channels = impl->GetDeviceChannels(); + const std::size_t frame_size = num_channels; + const std::size_t frame_size_bytes = frame_size * sizeof(s16); + const std::size_t num_frames{static_cast<size_t>(num_frames_)}; + size_t frames_written{0}; + [[maybe_unused]] bool underrun{false}; + + if (impl->type == StreamType::In) { + // INPUT + std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff), + num_frames * frame_size}; + + while (frames_written < num_frames) { + auto& playing_buffer{impl->playing_buffer}; + + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!impl->queue.try_dequeue(impl->playing_buffer)) { + // If no buffer was available we've underrun, just push the samples and + // continue. + underrun = true; + impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], + (num_frames - frames_written) * frame_size); + frames_written = num_frames; + continue; + } else { + // Successfully got a new buffer, mark the old one as consumed and signal. + impl->queued_buffers--; + impl->SignalEvent(impl->playing_buffer); + } + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{ + std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); + impl->playing_buffer.consumed = true; + } + } + + std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], + frame_size_bytes); + } else { + // OUTPUT + std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size}; + + while (frames_written < num_frames) { + auto& playing_buffer{impl->playing_buffer}; + + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!impl->queue.try_dequeue(impl->playing_buffer)) { + // If no buffer was available we've underrun, fill the remaining buffer with + // the last written frame and continue. + underrun = true; + for (size_t i = frames_written; i < num_frames; i++) { + std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], + frame_size_bytes); + } + frames_written = num_frames; + continue; + } else { + // Successfully got a new buffer, mark the old one as consumed and signal. + impl->queued_buffers--; + impl->SignalEvent(impl->playing_buffer); + } + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{ + std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); + impl->playing_buffer.consumed = true; + } + } + + std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], + frame_size_bytes); + } + + return num_frames_; + } + + /** + * Cubeb callback for if a device state changes. Unused currently. + * + * @param stream - Cubeb-specific data about the stream. + * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. + * @param state - New state of the device. + */ + static void StateCallback([[maybe_unused]] cubeb_stream* stream, + [[maybe_unused]] void* user_data, + [[maybe_unused]] cubeb_state state) {} + + /// Main Cubeb context + cubeb* ctx{}; + /// Cubeb stream backend + cubeb_stream* stream_backend{}; + /// Name of this stream + std::string name{}; + /// Type of this stream + StreamType type; + /// Core system + Core::System& system; + /// Ring buffer of the samples waiting to be played or consumed + Common::RingBuffer<s16, 0x10000> samples_buffer; + /// Audio buffers queued and waiting to play + Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; + /// The currently-playing audio buffer + ::AudioCore::Sink::SinkBuffer playing_buffer{}; + /// Audio buffers which have been played and are in queue to be released by the audio system + Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; + /// Currently released buffer waiting to be taken by the audio system + ::AudioCore::Sink::SinkBuffer released_buffer{}; + /// The last played (or received) frame of audio, used when the callback underruns + std::array<s16, MaxChannels> last_frame{}; +}; + +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 == std::string(info.friendly_name); + })}; + if (device != collection_end) { + output_device = device->devid; + } + cubeb_device_collection_destroy(ctx, &collection); + } + } + + cubeb_get_max_channel_count(ctx, &device_channels); + device_channels = device_channels >= 6U ? 6U : 2U; +} + +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(Core::System& system, const u32 system_channels, + const std::string& name, const StreamType type) { + SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( + ctx, device_channels, system_channels, output_device, input_device, name, type, system)); + + return stream.get(); +} + +void CubebSink::CloseStream(const SinkStream* stream) { + for (size_t i = 0; i < sink_streams.size(); i++) { + if (sink_streams[i].get() == stream) { + sink_streams[i].reset(); + sink_streams.erase(sink_streams.begin() + i); + break; + } + } +} + +void CubebSink::CloseStreams() { + sink_streams.clear(); +} + +void CubebSink::PauseStreams() { + for (auto& stream : sink_streams) { + stream->Stop(); + } +} + +void CubebSink::UnpauseStreams() { + for (auto& stream : sink_streams) { + stream->Start(true); + } +} + +f32 CubebSink::GetDeviceVolume() const { + if (sink_streams.empty()) { + return 1.0f; + } + + return sink_streams[0]->GetDeviceVolume(); +} + +void CubebSink::SetDeviceVolume(const f32 volume) { + for (auto& stream : sink_streams) { + stream->SetDeviceVolume(volume); + } +} + +void CubebSink::SetSystemVolume(const f32 volume) { + for (auto& stream : sink_streams) { + stream->SetSystemVolume(volume); + } +} + +std::vector<std::string> ListCubebSinkDevices(const bool capture) { + 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 {}; + } + + auto type{capture ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT}; + cubeb_device_collection collection; + if (cubeb_enumerate_devices(ctx, type, &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.friendly_name[0] != '\0' && + device.state == CUBEB_DEVICE_STATE_ENABLED) { + device_list.emplace_back(device.friendly_name); + } + } + cubeb_device_collection_destroy(ctx, &collection); + } + + cubeb_destroy(ctx); + return device_list; +} + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h new file mode 100644 index 000000000..f0f43dfa1 --- /dev/null +++ b/src/audio_core/sink/cubeb_sink.h @@ -0,0 +1,110 @@ +// 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/sink.h" + +namespace Core { +class System; +} + +namespace AudioCore::Sink { +class SinkStream; + +/** + * Cubeb backend sink, holds multiple output streams and is responsible for sinking samples to + * hardware. Used by Audio Render, Audio In and Audio Out. + */ +class CubebSink final : public Sink { +public: + explicit CubebSink(std::string_view device_id); + ~CubebSink() override; + + /** + * Create a new sink stream. + * + * @param system - Core system. + * @param system_channels - Number of channels the audio system expects. + * May differ from the device's channel count. + * @param name - Name of this stream. + * @param type - Type of this stream, render/in/out. + * @param event - Audio render only, a signal used to prevent the renderer running too + * fast. + * @return A pointer to the created SinkStream + */ + SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string& name, StreamType type) override; + + /** + * Close a given stream. + * + * @param stream - The stream to close. + */ + void CloseStream(const SinkStream* stream) override; + + /** + * Close all streams. + */ + void CloseStreams() override; + + /** + * Pause all streams. + */ + void PauseStreams() override; + + /** + * Unpause all streams. + */ + void UnpauseStreams() override; + + /** + * Get the device volume. Set from calls to the IAudioDevice service. + * + * @return Volume of the device. + */ + f32 GetDeviceVolume() const override; + + /** + * Set the device volume. Set from calls to the IAudioDevice service. + * + * @param volume - New volume of the device. + */ + void SetDeviceVolume(f32 volume) override; + + /** + * Set the system volume. Comes from the audio system using this stream. + * + * @param volume - New volume of the system. + */ + void SetSystemVolume(f32 volume) override; + +private: + /// Backend Cubeb context + cubeb* ctx{}; + /// Cubeb id of the actual hardware output device + cubeb_devid output_device{}; + /// Cubeb id of the actual hardware input device + cubeb_devid input_device{}; + /// Vector of streams managed by this sink + std::vector<SinkStreamPtr> sink_streams{}; + +#ifdef _WIN32 + /// Cubeb required COM to be initialized multi-threaded on Windows + u32 com_init_result = 0; +#endif +}; + +/** + * Get a list of conencted devices from Cubeb. + * + * @param capture - Return input (capture) devices if true, otherwise output devices. + */ +std::vector<std::string> ListCubebSinkDevices(bool capture); + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h new file mode 100644 index 000000000..47a342171 --- /dev/null +++ b/src/audio_core/sink/null_sink.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/sink/sink.h" +#include "audio_core/sink/sink_stream.h" + +namespace AudioCore::Sink { +/** + * A no-op sink for when no audio out is wanted. + */ +class NullSink final : public Sink { +public: + explicit NullSink(std::string_view) {} + ~NullSink() override = default; + + SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, + [[maybe_unused]] u32 system_channels, + [[maybe_unused]] const std::string& name, + [[maybe_unused]] StreamType type) override { + return &null_sink_stream; + } + + void CloseStream([[maybe_unused]] const SinkStream* stream) override {} + void CloseStreams() override {} + void PauseStreams() override {} + void UnpauseStreams() override {} + f32 GetDeviceVolume() const override { + return 1.0f; + } + void SetDeviceVolume(f32 volume) override {} + void SetSystemVolume(f32 volume) override {} + +private: + struct NullSinkStreamImpl final : SinkStream { + void Finalize() override {} + void Start(bool resume = false) override {} + void Stop() override {} + void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer, + [[maybe_unused]] std::vector<s16>& samples) override {} + std::vector<s16> ReleaseBuffer([[maybe_unused]] u64 num_samples) override { + return {}; + } + bool IsBufferConsumed([[maybe_unused]] const u64 tag) { + return true; + } + void ClearQueue() override {} + } null_sink_stream; +}; + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp new file mode 100644 index 000000000..d6c9ec90d --- /dev/null +++ b/src/audio_core/sink/sdl2_sink.cpp @@ -0,0 +1,556 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <atomic> + +#include "audio_core/audio_core.h" +#include "audio_core/audio_event.h" +#include "audio_core/audio_manager.h" +#include "audio_core/sink/sdl2_sink.h" +#include "audio_core/sink/sink_stream.h" +#include "common/assert.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" +#include "common/reader_writer_queue.h" +#include "common/ring_buffer.h" +#include "common/settings.h" +#include "core/core.h" + +// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#endif +#include <SDL.h> +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace AudioCore::Sink { +/** + * SDL sink stream, responsible for sinking samples to hardware. + */ +class SDLSinkStream final : public SinkStream { +public: + /** + * Create a new sink stream. + * + * @param device_channels_ - Number of channels supported by the hardware. + * @param system_channels_ - Number of channels the audio systems expect. + * @param output_device - Name of the output device to use for this stream. + * @param input_device - Name of the input device to use for this stream. + * @param type_ - Type of this stream. + * @param system_ - Core system. + * @param event - Event used only for audio renderer, signalled on buffer consume. + */ + SDLSinkStream(u32 device_channels_, const u32 system_channels_, + const std::string& output_device, const std::string& input_device, + const StreamType type_, Core::System& system_) + : type{type_}, system{system_} { + system_channels = system_channels_; + device_channels = device_channels_; + + SDL_AudioSpec spec; + spec.freq = TargetSampleRate; + spec.channels = static_cast<u8>(device_channels); + spec.format = AUDIO_S16SYS; + if (type == StreamType::Render) { + spec.samples = TargetSampleCount; + } else { + spec.samples = 1024; + } + spec.callback = &SDLSinkStream::DataCallback; + spec.userdata = this; + + playing_buffer.consumed = true; + + std::string device_name{output_device}; + bool capture{false}; + if (type == StreamType::In) { + device_name = input_device; + capture = true; + } + + SDL_AudioSpec obtained; + if (device_name.empty()) { + device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false); + } else { + device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); + } + + if (device == 0) { + LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError()); + return; + } + + LOG_DEBUG(Service_Audio, + "Opening sdl stream {} with: rate {} channels {} (system channels {}) " + " samples {}", + device, obtained.freq, obtained.channels, system_channels, obtained.samples); + } + + /** + * Destroy the sink stream. + */ + ~SDLSinkStream() override { + if (device == 0) { + return; + } + + SDL_CloseAudioDevice(device); + } + + /** + * Finalize the sink stream. + */ + void Finalize() override { + if (device == 0) { + return; + } + + SDL_CloseAudioDevice(device); + } + + /** + * Start the sink stream. + * + * @param resume - Set to true if this is resuming the stream a previously-active stream. + * Default false. + */ + void Start(const bool resume = false) override { + if (device == 0) { + return; + } + + if (resume && was_playing) { + SDL_PauseAudioDevice(device, 0); + paused = false; + } else if (!resume) { + SDL_PauseAudioDevice(device, 0); + paused = false; + } + } + + /** + * Stop the sink stream. + */ + void Stop() { + if (device == 0) { + return; + } + SDL_PauseAudioDevice(device, 1); + paused = true; + } + + /** + * Append a new buffer and its samples to a waiting queue to play. + * + * @param buffer - Audio buffer information to be queued. + * @param samples - The s16 samples to be queue for playback. + */ + void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override { + if (type == StreamType::In) { + queue.enqueue(buffer); + queued_buffers++; + } else { + constexpr s32 min = std::numeric_limits<s16>::min(); + constexpr s32 max = std::numeric_limits<s16>::max(); + + auto yuzu_volume{Settings::Volume()}; + auto volume{system_volume * device_volume * yuzu_volume}; + + if (system_channels == 6 && device_channels == 2) { + // We're given 6 channels, but our device only outputs 2, so downmix. + constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * + down_mix_coeff[0] + + samples[read_index + static_cast<u32>(Channels::Center)] * + down_mix_coeff[1] + + samples[read_index + static_cast<u32>(Channels::LFE)] * + down_mix_coeff[2] + + samples[read_index + static_cast<u32>(Channels::BackLeft)] * + down_mix_coeff[3]) * + volume) + .to_int()}; + + const auto right_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast<u32>(Channels::FrontRight)]) * + down_mix_coeff[0] + + samples[read_index + static_cast<u32>(Channels::Center)] * + down_mix_coeff[1] + + samples[read_index + static_cast<u32>(Channels::LFE)] * + down_mix_coeff[2] + + samples[read_index + static_cast<u32>(Channels::BackRight)] * + down_mix_coeff[3]) * + volume) + .to_int()}; + + samples[write_index + static_cast<u32>(Channels::FrontLeft)] = + static_cast<s16>(std::clamp(left_sample, min, max)); + samples[write_index + static_cast<u32>(Channels::FrontRight)] = + static_cast<s16>(std::clamp(right_sample, min, max)); + } + + samples.resize(samples.size() / system_channels * device_channels); + + } else if (system_channels == 2 && device_channels == 6) { + // We need moar samples! Not all games will provide 6 channel audio. + // TODO: Implement some upmixing here. Currently just passthrough, with other + // channels left as silence. + std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{static_cast<s16>(std::clamp( + static_cast<s32>( + static_cast<f32>( + samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; + + const auto right_sample{static_cast<s16>(std::clamp( + static_cast<s32>( + static_cast<f32>( + samples[read_index + static_cast<u32>(Channels::FrontRight)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = + right_sample; + } + samples = std::move(new_samples); + + } else if (volume != 1.0f) { + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast<s16>(std::clamp( + static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); + } + } + + samples_buffer.Push(samples); + queue.enqueue(buffer); + queued_buffers++; + } + } + + /** + * Release a buffer. Audio In only, will fill a buffer with recorded samples. + * + * @param num_samples - Maximum number of samples to receive. + * @return Vector of recorded samples. May have fewer than num_samples. + */ + std::vector<s16> ReleaseBuffer(const u64 num_samples) override { + static constexpr s32 min = std::numeric_limits<s16>::min(); + static constexpr s32 max = std::numeric_limits<s16>::max(); + + auto samples{samples_buffer.Pop(num_samples)}; + + // TODO: Up-mix to 6 channels if the game expects it. + // For audio input this is unlikely to ever be the case though. + + // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. + // TODO: Play with this and find something that works better. + auto volume{system_volume * device_volume * 8}; + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast<s16>( + std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); + } + + if (samples.size() < num_samples) { + samples.resize(num_samples, 0); + } + return samples; + } + + /** + * Check if a certain buffer has been consumed (fully played). + * + * @param tag - Unique tag of a buffer to check for. + * @return True if the buffer has been played, otherwise false. + */ + bool IsBufferConsumed(const u64 tag) override { + if (released_buffer.tag == 0) { + if (!released_buffers.try_dequeue(released_buffer)) { + return false; + } + } + + if (released_buffer.tag == tag) { + released_buffer.tag = 0; + return true; + } + return false; + } + + /** + * Empty out the buffer queue. + */ + void ClearQueue() override { + samples_buffer.Pop(); + while (queue.pop()) { + } + while (released_buffers.pop()) { + } + released_buffer = {}; + playing_buffer = {}; + playing_buffer.consumed = true; + queued_buffers = 0; + } + +private: + /** + * Signal events back to the audio system that a buffer was played/can be filled. + * + * @param buffer - Consumed audio buffer to be released. + */ + void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { + auto& manager{system.AudioCore().GetAudioManager()}; + switch (type) { + case StreamType::Out: + released_buffers.enqueue(buffer); + manager.SetEvent(Event::Type::AudioOutManager, true); + break; + case StreamType::In: + released_buffers.enqueue(buffer); + manager.SetEvent(Event::Type::AudioInManager, true); + break; + case StreamType::Render: + break; + } + } + + /** + * Main callback from SDL. Either expects samples from us (audio render/audio out), or will + * provide samples to be copied (audio in). + * + * @param userdata - Custom data pointer passed along, points to a SDLSinkStream. + * @param stream - Buffer of samples to be filled or read. + * @param len - Length of the stream in bytes. + */ + static void DataCallback(void* userdata, Uint8* stream, int len) { + auto* impl = static_cast<SDLSinkStream*>(userdata); + + if (!impl) { + return; + } + + const std::size_t num_channels = impl->GetDeviceChannels(); + const std::size_t frame_size = num_channels; + const std::size_t frame_size_bytes = frame_size * sizeof(s16); + const std::size_t num_frames{len / num_channels / sizeof(s16)}; + size_t frames_written{0}; + [[maybe_unused]] bool underrun{false}; + + if (impl->type == StreamType::In) { + std::span<s16> input_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; + + while (frames_written < num_frames) { + auto& playing_buffer{impl->playing_buffer}; + + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!impl->queue.try_dequeue(impl->playing_buffer)) { + // If no buffer was available we've underrun, just push the samples and + // continue. + underrun = true; + impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], + (num_frames - frames_written) * frame_size); + frames_written = num_frames; + continue; + } else { + impl->queued_buffers--; + impl->SignalEvent(impl->playing_buffer); + } + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{ + std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); + impl->playing_buffer.consumed = true; + } + } + + std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], + frame_size_bytes); + } else { + std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; + + while (frames_written < num_frames) { + auto& playing_buffer{impl->playing_buffer}; + + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!impl->queue.try_dequeue(impl->playing_buffer)) { + // If no buffer was available we've underrun, fill the remaining buffer with + // the last written frame and continue. + underrun = true; + for (size_t i = frames_written; i < num_frames; i++) { + std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], + frame_size_bytes); + } + frames_written = num_frames; + continue; + } else { + impl->queued_buffers--; + impl->SignalEvent(impl->playing_buffer); + } + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{ + std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); + impl->playing_buffer.consumed = true; + } + } + + std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], + frame_size_bytes); + } + } + + /// SDL device id of the opened input/output device + SDL_AudioDeviceID device{}; + /// Type of this stream + StreamType type; + /// Core system + Core::System& system; + /// Ring buffer of the samples waiting to be played or consumed + Common::RingBuffer<s16, 0x10000> samples_buffer; + /// Audio buffers queued and waiting to play + Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; + /// The currently-playing audio buffer + ::AudioCore::Sink::SinkBuffer playing_buffer{}; + /// Audio buffers which have been played and are in queue to be released by the audio system + Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; + /// Currently released buffer waiting to be taken by the audio system + ::AudioCore::Sink::SinkBuffer released_buffer{}; + /// The last played (or received) frame of audio, used when the callback underruns + std::array<s16, MaxChannels> last_frame{}; +}; + +SDLSink::SDLSink(std::string_view target_device_name) { + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); + return; + } + } + + if (target_device_name != auto_device_name && !target_device_name.empty()) { + output_device = target_device_name; + } else { + output_device.clear(); + } + + device_channels = 2; +} + +SDLSink::~SDLSink() = default; + +SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, + const std::string&, const StreamType type) { + SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( + device_channels, system_channels, output_device, input_device, type, system)); + return stream.get(); +} + +void SDLSink::CloseStream(const SinkStream* stream) { + for (size_t i = 0; i < sink_streams.size(); i++) { + if (sink_streams[i].get() == stream) { + sink_streams[i].reset(); + sink_streams.erase(sink_streams.begin() + i); + break; + } + } +} + +void SDLSink::CloseStreams() { + sink_streams.clear(); +} + +void SDLSink::PauseStreams() { + for (auto& stream : sink_streams) { + stream->Stop(); + } +} + +void SDLSink::UnpauseStreams() { + for (auto& stream : sink_streams) { + stream->Start(); + } +} + +f32 SDLSink::GetDeviceVolume() const { + if (sink_streams.empty()) { + return 1.0f; + } + + return sink_streams[0]->GetDeviceVolume(); +} + +void SDLSink::SetDeviceVolume(const f32 volume) { + for (auto& stream : sink_streams) { + stream->SetDeviceVolume(volume); + } +} + +void SDLSink::SetSystemVolume(const f32 volume) { + for (auto& stream : sink_streams) { + stream->SetSystemVolume(volume); + } +} + +std::vector<std::string> ListSDLSinkDevices(const bool capture) { + std::vector<std::string> device_list; + + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); + return {}; + } + } + + const int device_count = SDL_GetNumAudioDevices(capture); + for (int i = 0; i < device_count; ++i) { + device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); + } + + return device_list; +} + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h new file mode 100644 index 000000000..186bc2fa3 --- /dev/null +++ b/src/audio_core/sink/sdl2_sink.h @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> +#include <vector> + +#include "audio_core/sink/sink.h" + +namespace Core { +class System; +} + +namespace AudioCore::Sink { +class SinkStream; + +/** + * SDL backend sink, holds multiple output streams and is responsible for sinking samples to + * hardware. Used by Audio Render, Audio In and Audio Out. + */ +class SDLSink final : public Sink { +public: + explicit SDLSink(std::string_view device_id); + ~SDLSink() override; + + /** + * Create a new sink stream. + * + * @param system - Core system. + * @param system_channels - Number of channels the audio system expects. + * May differ from the device's channel count. + * @param name - Name of this stream. + * @param type - Type of this stream, render/in/out. + * @param event - Audio render only, a signal used to prevent the renderer running too + * fast. + * @return A pointer to the created SinkStream + */ + SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string& name, StreamType type) override; + + /** + * Close a given stream. + * + * @param stream - The stream to close. + */ + void CloseStream(const SinkStream* stream) override; + + /** + * Close all streams. + */ + void CloseStreams() override; + + /** + * Pause all streams. + */ + void PauseStreams() override; + + /** + * Unpause all streams. + */ + void UnpauseStreams() override; + + /** + * Get the device volume. Set from calls to the IAudioDevice service. + * + * @return Volume of the device. + */ + f32 GetDeviceVolume() const override; + + /** + * Set the device volume. Set from calls to the IAudioDevice service. + * + * @param volume - New volume of the device. + */ + void SetDeviceVolume(f32 volume) override; + + /** + * Set the system volume. Comes from the audio system using this stream. + * + * @param volume - New volume of the system. + */ + void SetSystemVolume(f32 volume) override; + +private: + /// Name of the output device used by streams + std::string output_device; + /// Name of the input device used by streams + std::string input_device; + /// Vector of streams managed by this sink + std::vector<SinkStreamPtr> sink_streams; +}; + +/** + * Get a list of conencted devices from Cubeb. + * + * @param capture - Return input (capture) devices if true, otherwise output devices. + */ +std::vector<std::string> ListSDLSinkDevices(bool capture); + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h new file mode 100644 index 000000000..91fe455e4 --- /dev/null +++ b/src/audio_core/sink/sink.h @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <memory> +#include <string> + +#include "audio_core/sink/sink_stream.h" +#include "common/common_types.h" + +namespace Common { +class Event; +} +namespace Core { +class System; +} + +namespace AudioCore::Sink { + +constexpr char auto_device_name[] = "auto"; + +/** + * This class is an interface for an audio sink, holds multiple output streams and is responsible + * for sinking samples to hardware. Used by Audio Render, Audio In and Audio Out. + */ +class Sink { +public: + virtual ~Sink() = default; + /** + * Close a given stream. + * + * @param stream - The stream to close. + */ + virtual void CloseStream(const SinkStream* stream) = 0; + + /** + * Close all streams. + */ + virtual void CloseStreams() = 0; + + /** + * Pause all streams. + */ + virtual void PauseStreams() = 0; + + /** + * Unpause all streams. + */ + virtual void UnpauseStreams() = 0; + + /** + * Create a new sink stream, kept within this sink, with a pointer returned for use. + * Do not free the returned pointer. When done with the stream, call CloseStream on the sink. + * + * @param system - Core system. + * @param system_channels - Number of channels the audio system expects. + * May differ from the device's channel count. + * @param name - Name of this stream. + * @param type - Type of this stream, render/in/out. + * @param event - Audio render only, a signal used to prevent the renderer running too + * fast. + * @return A pointer to the created SinkStream + */ + virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string& name, StreamType type) = 0; + + /** + * Get the number of channels the hardware device supports. + * Either 2 or 6. + * + * @return Number of device channels. + */ + u32 GetDeviceChannels() const { + return device_channels; + } + + /** + * Get the device volume. Set from calls to the IAudioDevice service. + * + * @return Volume of the device. + */ + virtual f32 GetDeviceVolume() const = 0; + + /** + * Set the device volume. Set from calls to the IAudioDevice service. + * + * @param volume - New volume of the device. + */ + virtual void SetDeviceVolume(f32 volume) = 0; + + /** + * Set the system volume. Comes from the audio system using this stream. + * + * @param volume - New volume of the system. + */ + virtual void SetSystemVolume(f32 volume) = 0; + +protected: + /// Number of device channels supported by the hardware + u32 device_channels{2}; +}; + +using SinkPtr = std::unique_ptr<Sink>; + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index c4cc66111..253c0fd1e 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp @@ -5,21 +5,21 @@ #include <memory> #include <string> #include <vector> -#include "audio_core/null_sink.h" -#include "audio_core/sink_details.h" +#include "audio_core/sink/null_sink.h" +#include "audio_core/sink/sink_details.h" #ifdef HAVE_CUBEB -#include "audio_core/cubeb_sink.h" +#include "audio_core/sink/cubeb_sink.h" #endif #ifdef HAVE_SDL2 -#include "audio_core/sdl2_sink.h" +#include "audio_core/sink/sdl2_sink.h" #endif #include "common/logging/log.h" -namespace AudioCore { +namespace AudioCore::Sink { namespace { struct SinkDetails { using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); - using ListDevicesFn = std::vector<std::string> (*)(); + using ListDevicesFn = std::vector<std::string> (*)(bool); /// Name for this sink. const char* id; @@ -49,17 +49,18 @@ constexpr SinkDetails sink_details[] = { [](std::string_view device_id) -> std::unique_ptr<Sink> { return std::make_unique<NullSink>(device_id); }, - [] { return std::vector<std::string>{"null"}; }}, + [](bool capture) { return std::vector<std::string>{"null"}; }}, }; -const SinkDetails& GetSinkDetails(std::string_view sink_id) { +const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) { auto iter = std::find_if(std::begin(sink_details), std::end(sink_details), [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); if (sink_id == "auto" || iter == std::end(sink_details)) { if (sink_id != "auto") { - LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); + LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", + sink_id); } // Auto-select. // sink_details is ordered in terms of desirability, with the best choice at the front. @@ -79,12 +80,12 @@ std::vector<const char*> GetSinkIDs() { return sink_ids; } -std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) { - return GetSinkDetails(sink_id).list_devices(); +std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture) { + return GetOutputSinkDetails(sink_id).list_devices(capture); } std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { - return GetSinkDetails(sink_id).factory(device_id); + return GetOutputSinkDetails(sink_id).factory(device_id); } -} // namespace AudioCore +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sink_details.h b/src/audio_core/sink/sink_details.h new file mode 100644 index 000000000..3ebdb1e30 --- /dev/null +++ b/src/audio_core/sink/sink_details.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> +#include <string_view> +#include <vector> + +namespace AudioCore { +class AudioManager; + +namespace Sink { + +class Sink; + +/** + * Retrieves the IDs for all available audio sinks. + * + * @return Vector of available sink names. + */ +std::vector<const char*> GetSinkIDs(); + +/** + * Gets the list of devices for a particular sink identified by the given ID. + * + * @param sink_id - Id of the sink to get devices from. + * @param capture - Get capture (input) devices, or output devices? + * @return Vector of device names. + */ +std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture); + +/** + * Creates an audio sink identified by the given device ID. + * + * @param sink_id - Id of the sink to create. + * @param device_id - Name of the device to create. + * @return Pointer to the created sink. + */ +std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id); + +} // namespace Sink +} // namespace AudioCore diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h new file mode 100644 index 000000000..17ed6593f --- /dev/null +++ b/src/audio_core/sink/sink_stream.h @@ -0,0 +1,224 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <atomic> +#include <memory> +#include <vector> + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::Sink { + +enum class StreamType { + Render, + Out, + In, +}; + +struct SinkBuffer { + u64 frames; + u64 frames_played; + u64 tag; + bool consumed; +}; + +/** + * Contains a real backend stream for outputting samples to hardware, + * created only via a Sink (See Sink::AcquireSinkStream). + * + * Accepts a SinkBuffer and samples in PCM16 format to be output (see AppendBuffer). + * Appended buffers act as a FIFO queue, and will be held until played. + * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer + * has been consumed. + * + * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the + * buffers, skipping a buffer will result in all following buffers to never release. + * + * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this + * is what games do), or call ClearQueue to flush all of the buffers without a full restart. + */ +class SinkStream { +public: + virtual ~SinkStream() = default; + + /** + * Finalize the sink stream. + */ + virtual void Finalize() = 0; + + /** + * Start the sink stream. + * + * @param resume - Set to true if this is resuming the stream a previously-active stream. + * Default false. + */ + virtual void Start(bool resume = false) = 0; + + /** + * Stop the sink stream. + */ + virtual void Stop() = 0; + + /** + * Append a new buffer and its samples to a waiting queue to play. + * + * @param buffer - Audio buffer information to be queued. + * @param samples - The s16 samples to be queue for playback. + */ + virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) = 0; + + /** + * Release a buffer. Audio In only, will fill a buffer with recorded samples. + * + * @param num_samples - Maximum number of samples to receive. + * @return Vector of recorded samples. May have fewer than num_samples. + */ + virtual std::vector<s16> ReleaseBuffer(u64 num_samples) = 0; + + /** + * Check if a certain buffer has been consumed (fully played). + * + * @param tag - Unique tag of a buffer to check for. + * @return True if the buffer has been played, otherwise false. + */ + virtual bool IsBufferConsumed(u64 tag) = 0; + + /** + * Empty out the buffer queue. + */ + virtual void ClearQueue() = 0; + + /** + * Check if the stream is paused. + * + * @return True if paused, otherwise false. + */ + bool IsPaused() { + return paused; + } + + /** + * Get the number of system channels in this stream. + * + * @return Number of system channels. + */ + u32 GetSystemChannels() const { + return system_channels; + } + + /** + * Set the number of channels the system expects. + * + * @param channels - New number of system channels. + */ + void SetSystemChannels(u32 channels) { + system_channels = channels; + } + + /** + * Get the number of channels the hardware supports. + * + * @return Number of channels supported. + */ + u32 GetDeviceChannels() const { + return device_channels; + } + + /** + * Get the total number of samples played by this stream. + * + * @return Number of samples played. + */ + u64 GetPlayedSampleCount() const { + return played_sample_count; + } + + /** + * Set the number of samples played. + * This is started and stopped on system start/stop. + * + * @param played_sample_count_ - Number of samples to set. + */ + void SetPlayedSampleCount(u64 played_sample_count_) { + played_sample_count = played_sample_count_; + } + + /** + * Add to the played sample count. + * + * @param num_samples - Number of samples to add. + */ + void AddPlayedSampleCount(u64 num_samples) { + played_sample_count += num_samples; + } + + /** + * Get the system volume. + * + * @return The current system volume. + */ + f32 GetSystemVolume() const { + return system_volume; + } + + /** + * Get the device volume. + * + * @return The current device volume. + */ + f32 GetDeviceVolume() const { + return device_volume; + } + + /** + * Set the system volume. + * + * @param volume_ - The new system volume. + */ + void SetSystemVolume(f32 volume_) { + system_volume = volume_; + } + + /** + * Set the device volume. + * + * @param volume_ - The new device volume. + */ + void SetDeviceVolume(f32 volume_) { + device_volume = volume_; + } + + /** + * Get the number of queued audio buffers. + * + * @return The number of queued buffers. + */ + u32 GetQueueSize() { + return queued_buffers.load(); + } + +protected: + /// Number of buffers waiting to be played + std::atomic<u32> queued_buffers{}; + /// Total samples played by this stream + std::atomic<u64> played_sample_count{}; + /// Set by the audio render/in/out system which uses this stream + f32 system_volume{1.0f}; + /// Set via IAudioDevice service calls + f32 device_volume{1.0f}; + /// Set by the audio render/in/out systen which uses this stream + u32 system_channels{2}; + /// Channels supported by hardware + u32 device_channels{2}; + /// Is this stream currently paused? + std::atomic<bool> paused{true}; + /// Was this stream previously playing? + std::atomic<bool> was_playing{false}; +}; + +using SinkStreamPtr = std::unique_ptr<SinkStream>; + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp deleted file mode 100644 index 835e12f67..000000000 --- a/src/audio_core/sink_context.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "audio_core/sink_context.h" - -namespace AudioCore { -SinkContext::SinkContext(std::size_t sink_count_) : sink_count{sink_count_} {} -SinkContext::~SinkContext() = default; - -std::size_t SinkContext::GetCount() const { - return sink_count; -} - -void SinkContext::UpdateMainSink(const SinkInfo::InParams& in) { - ASSERT(in.type == SinkTypes::Device); - - if (in.device.down_matrix_enabled) { - downmix_coefficients = in.device.down_matrix_coef; - } else { - downmix_coefficients = { - 1.0f, // front - 0.707f, // center - 0.0f, // lfe - 0.707f, // back - }; - } - - in_use = in.in_use; - use_count = in.device.input_count; - buffers = in.device.input; -} - -bool SinkContext::InUse() const { - return in_use; -} - -std::vector<u8> SinkContext::OutputBuffers() const { - std::vector<u8> buffer_ret(use_count); - std::memcpy(buffer_ret.data(), buffers.data(), use_count); - return buffer_ret; -} - -const DownmixCoefficients& SinkContext::GetDownmixCoefficients() const { - return downmix_coefficients; -} - -} // namespace AudioCore diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h deleted file mode 100644 index cc5a90d80..000000000 --- a/src/audio_core/sink_context.h +++ /dev/null @@ -1,95 +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 "common/common_funcs.h" -#include "common/common_types.h" -#include "common/swap.h" - -namespace AudioCore { - -using DownmixCoefficients = std::array<float_le, 4>; - -enum class SinkTypes : u8 { - Invalid = 0, - Device = 1, - Circular = 2, -}; - -enum class SinkSampleFormat : u32_le { - None = 0, - Pcm8 = 1, - Pcm16 = 2, - Pcm24 = 3, - Pcm32 = 4, - PcmFloat = 5, - Adpcm = 6, -}; - -class SinkInfo { -public: - struct CircularBufferIn { - u64_le address; - u32_le size; - u32_le input_count; - u32_le sample_count; - u32_le previous_position; - SinkSampleFormat sample_format; - std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input; - bool in_use; - INSERT_PADDING_BYTES_NOINIT(5); - }; - static_assert(sizeof(CircularBufferIn) == 0x28, - "SinkInfo::CircularBufferIn is in invalid size"); - - struct DeviceIn { - std::array<u8, 255> device_name; - INSERT_PADDING_BYTES_NOINIT(1); - s32_le input_count; - std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input; - INSERT_PADDING_BYTES_NOINIT(1); - bool down_matrix_enabled; - DownmixCoefficients down_matrix_coef; - }; - static_assert(sizeof(DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size"); - - struct InParams { - SinkTypes type{}; - bool in_use{}; - INSERT_PADDING_BYTES(2); - u32_le node_id{}; - INSERT_PADDING_WORDS(6); - union { - // std::array<u8, 0x120> raw{}; - DeviceIn device; - CircularBufferIn circular_buffer; - }; - }; - static_assert(sizeof(InParams) == 0x140, "SinkInfo::InParams are an invalid size!"); -}; - -class SinkContext { -public: - explicit SinkContext(std::size_t sink_count_); - ~SinkContext(); - - [[nodiscard]] std::size_t GetCount() const; - - void UpdateMainSink(const SinkInfo::InParams& in); - [[nodiscard]] bool InUse() const; - [[nodiscard]] std::vector<u8> OutputBuffers() const; - - [[nodiscard]] const DownmixCoefficients& GetDownmixCoefficients() const; - -private: - bool in_use{false}; - s32 use_count{}; - std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{}; - std::size_t sink_count{}; - DownmixCoefficients downmix_coefficients{}; -}; -} // namespace AudioCore diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h deleted file mode 100644 index 042766358..000000000 --- a/src/audio_core/sink_details.h +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <string> -#include <string_view> -#include <vector> - -namespace AudioCore { - -class Sink; - -/// Retrieves the IDs for all available audio sinks. -std::vector<const char*> GetSinkIDs(); - -/// Gets the list of devices for a particular sink identified by the given ID. -std::vector<std::string> GetDeviceListForSink(std::string_view sink_id); - -/// Creates an audio sink identified by the given device ID. -std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id); - -} // namespace AudioCore diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h deleted file mode 100644 index 0449b90af..000000000 --- a/src/audio_core/sink_stream.h +++ /dev/null @@ -1,35 +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 { - -/** - * Accepts samples in stereo signed PCM16 format to be output. Sinks *do not* handle resampling and - * expect the correct sample rate. They are dumb outputs. - */ -class SinkStream { -public: - virtual ~SinkStream() = default; - - /** - * Feed stereo samples to sink. - * @param num_channels Number of channels used. - * @param samples Samples in interleaved stereo PCM16 format. - */ - virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0; - - virtual std::size_t SamplesInQueue(u32 num_channels) const = 0; - - virtual void Flush() = 0; -}; - -using SinkStreamPtr = std::unique_ptr<SinkStream>; - -} // namespace AudioCore diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp deleted file mode 100644 index 10646dc05..000000000 --- a/src/audio_core/splitter_context.cpp +++ /dev/null @@ -1,616 +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/splitter_context.h" -#include "common/alignment.h" -#include "common/assert.h" -#include "common/logging/log.h" - -namespace AudioCore { - -ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id_) : id{id_} {} -ServerSplitterDestinationData::~ServerSplitterDestinationData() = default; - -void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) { - // Log error as these are not actually failure states - if (header.magic != SplitterMagic::DataHeader) { - LOG_ERROR(Audio, "Splitter destination header is invalid!"); - return; - } - - // Incorrect splitter id - if (header.splitter_id != id) { - LOG_ERROR(Audio, "Splitter destination ids do not match!"); - return; - } - - mix_id = header.mix_id; - // Copy our mix volumes - std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin()); - if (!in_use && header.in_use) { - // Update mix volumes - std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin()); - needs_update = false; - } - in_use = header.in_use; -} - -ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() { - return next; -} - -const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const { - return next; -} - -void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) { - next = dest; -} - -bool ServerSplitterDestinationData::ValidMixId() const { - return GetMixId() != AudioCommon::NO_MIX; -} - -s32 ServerSplitterDestinationData::GetMixId() const { - return mix_id; -} - -bool ServerSplitterDestinationData::IsConfigured() const { - return in_use && ValidMixId(); -} - -float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const { - ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); - return current_mix_volumes.at(i); -} - -const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& -ServerSplitterDestinationData::CurrentMixVolumes() const { - return current_mix_volumes; -} - -const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& -ServerSplitterDestinationData::LastMixVolumes() const { - return last_mix_volumes; -} - -void ServerSplitterDestinationData::MarkDirty() { - needs_update = true; -} - -void ServerSplitterDestinationData::UpdateInternalState() { - if (in_use && needs_update) { - std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin()); - } - needs_update = false; -} - -ServerSplitterInfo::ServerSplitterInfo(s32 id_) : id(id_) {} -ServerSplitterInfo::~ServerSplitterInfo() = default; - -void ServerSplitterInfo::InitializeInfos() { - send_length = 0; - head = nullptr; - new_connection = true; -} - -void ServerSplitterInfo::ClearNewConnectionFlag() { - new_connection = false; -} - -std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) { - if (header.send_id != id) { - return 0; - } - - sample_rate = header.sample_rate; - new_connection = true; - // We need to update the size here due to the splitter bug being present and providing an - // incorrect size. We're suppose to also update the header here but we just ignore and continue - return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3); -} - -ServerSplitterDestinationData* ServerSplitterInfo::GetHead() { - return head; -} - -const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const { - return head; -} - -ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) { - auto* current_head = head; - for (std::size_t i = 0; i < depth; i++) { - if (current_head == nullptr) { - return nullptr; - } - current_head = current_head->GetNextDestination(); - } - return current_head; -} - -const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const { - auto* current_head = head; - for (std::size_t i = 0; i < depth; i++) { - if (current_head == nullptr) { - return nullptr; - } - current_head = current_head->GetNextDestination(); - } - return current_head; -} - -bool ServerSplitterInfo::HasNewConnection() const { - return new_connection; -} - -s32 ServerSplitterInfo::GetLength() const { - return send_length; -} - -void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) { - head = new_head; -} - -void ServerSplitterInfo::SetHeadDepth(s32 length) { - send_length = length; -} - -SplitterContext::SplitterContext() = default; -SplitterContext::~SplitterContext() = default; - -void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count, - std::size_t _data_count) { - if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) { - Setup(0, 0, false); - return; - } - // Only initialize if we're using splitters - Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed()); -} - -bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset, - std::size_t& bytes_read) { - const auto UpdateOffsets = [&](std::size_t read) { - input_offset += read; - bytes_read += read; - }; - - if (info_count == 0 || data_count == 0) { - bytes_read = 0; - return true; - } - - if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, - sizeof(SplitterInfo::InHeader))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - SplitterInfo::InHeader header{}; - std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader)); - UpdateOffsets(sizeof(SplitterInfo::InHeader)); - - if (header.magic != SplitterMagic::SplitterHeader) { - LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}", - SplitterMagic::SplitterHeader, header.magic); - return false; - } - - // Clear all connections - for (auto& info : infos) { - info.ClearNewConnectionFlag(); - } - - UpdateInfo(input, input_offset, bytes_read, header.info_count); - UpdateData(input, input_offset, bytes_read, header.data_count); - const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16); - input_offset += aligned_bytes_read - bytes_read; - bytes_read = aligned_bytes_read; - return true; -} - -bool SplitterContext::UsingSplitter() const { - return info_count > 0 && data_count > 0; -} - -ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) { - ASSERT(i < info_count); - return infos.at(i); -} - -const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const { - ASSERT(i < info_count); - return infos.at(i); -} - -ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) { - ASSERT(i < data_count); - return datas.at(i); -} - -const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const { - ASSERT(i < data_count); - return datas.at(i); -} - -ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, - std::size_t data) { - ASSERT(info < info_count); - auto& cur_info = GetInfo(info); - return cur_info.GetData(data); -} - -const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, - std::size_t data) const { - ASSERT(info < info_count); - const auto& cur_info = GetInfo(info); - return cur_info.GetData(data); -} - -void SplitterContext::UpdateInternalState() { - if (data_count == 0) { - return; - } - - for (auto& data : datas) { - data.UpdateInternalState(); - } -} - -std::size_t SplitterContext::GetInfoCount() const { - return info_count; -} - -std::size_t SplitterContext::GetDataCount() const { - return data_count; -} - -void SplitterContext::Setup(std::size_t info_count_, std::size_t data_count_, - bool is_splitter_bug_fixed) { - - info_count = info_count_; - data_count = data_count_; - - for (std::size_t i = 0; i < info_count; i++) { - auto& splitter = infos.emplace_back(static_cast<s32>(i)); - splitter.InitializeInfos(); - } - for (std::size_t i = 0; i < data_count; i++) { - datas.emplace_back(static_cast<s32>(i)); - } - - bug_fixed = is_splitter_bug_fixed; -} - -bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset, - std::size_t& bytes_read, s32 in_splitter_count) { - const auto UpdateOffsets = [&](std::size_t read) { - input_offset += read; - bytes_read += read; - }; - - for (s32 i = 0; i < in_splitter_count; i++) { - if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, - sizeof(SplitterInfo::InInfoPrams))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - SplitterInfo::InInfoPrams header{}; - std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams)); - - // Logged as warning as these don't actually cause a bailout for some reason - if (header.magic != SplitterMagic::InfoHeader) { - LOG_ERROR(Audio, "Bad splitter data header"); - break; - } - - if (header.send_id < 0 || static_cast<std::size_t>(header.send_id) > info_count) { - LOG_ERROR(Audio, "Bad splitter data id"); - break; - } - - UpdateOffsets(sizeof(SplitterInfo::InInfoPrams)); - auto& info = GetInfo(header.send_id); - if (!RecomposeDestination(info, header, input, input_offset)) { - LOG_ERROR(Audio, "Failed to recompose destination for splitter!"); - return false; - } - const std::size_t read = info.Update(header); - bytes_read += read; - input_offset += read; - } - return true; -} - -bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset, - std::size_t& bytes_read, s32 in_data_count) { - const auto UpdateOffsets = [&](std::size_t read) { - input_offset += read; - bytes_read += read; - }; - - for (s32 i = 0; i < in_data_count; i++) { - if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, - sizeof(SplitterInfo::InDestinationParams))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - SplitterInfo::InDestinationParams header{}; - std::memcpy(&header, input.data() + input_offset, - sizeof(SplitterInfo::InDestinationParams)); - UpdateOffsets(sizeof(SplitterInfo::InDestinationParams)); - - // Logged as warning as these don't actually cause a bailout for some reason - if (header.magic != SplitterMagic::DataHeader) { - LOG_ERROR(Audio, "Bad splitter data header"); - break; - } - - if (header.splitter_id < 0 || static_cast<std::size_t>(header.splitter_id) > data_count) { - LOG_ERROR(Audio, "Bad splitter data id"); - break; - } - GetData(header.splitter_id).Update(header); - } - return true; -} - -bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info, - SplitterInfo::InInfoPrams& header, - const std::vector<u8>& input, - const std::size_t& input_offset) { - // Clear our current destinations - auto* current_head = info.GetHead(); - while (current_head != nullptr) { - auto* next_head = current_head->GetNextDestination(); - current_head->SetNextDestination(nullptr); - current_head = next_head; - } - info.SetHead(nullptr); - - s32 size = header.length; - // If the splitter bug is present, calculate fixed size - if (!bug_fixed) { - if (info_count > 0) { - const auto factor = data_count / info_count; - size = std::min(header.length, static_cast<s32>(factor)); - } else { - size = 0; - } - } - - if (size < 1) { - LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size); - return true; - } - - auto* start_head = &GetData(header.resource_id_base); - current_head = start_head; - std::vector<s32_le> resource_ids(size - 1); - if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, - resource_ids.size() * sizeof(s32_le))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - std::memcpy(resource_ids.data(), input.data() + input_offset, - resource_ids.size() * sizeof(s32_le)); - - for (auto resource_id : resource_ids) { - auto* head = &GetData(resource_id); - current_head->SetNextDestination(head); - current_head = head; - } - - info.SetHead(start_head); - info.SetHeadDepth(size); - - return true; -} - -NodeStates::NodeStates() = default; -NodeStates::~NodeStates() = default; - -void NodeStates::Initialize(std::size_t node_count_) { - // Setup our work parameters - node_count = node_count_; - was_node_found.resize(node_count); - was_node_completed.resize(node_count); - index_list.resize(node_count); - index_stack.Reset(node_count * node_count); -} - -bool NodeStates::Tsort(EdgeMatrix& edge_matrix) { - return DepthFirstSearch(edge_matrix); -} - -std::size_t NodeStates::GetIndexPos() const { - return index_pos; -} - -const std::vector<s32>& NodeStates::GetIndexList() const { - return index_list; -} - -void NodeStates::PushTsortResult(s32 index) { - ASSERT(index < static_cast<s32>(node_count)); - index_list[index_pos++] = index; -} - -bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) { - ResetState(); - for (std::size_t i = 0; i < node_count; i++) { - const auto node_id = static_cast<s32>(i); - - // If we don't have a state, send to our index stack for work - if (GetState(i) == NodeStates::State::NoState) { - index_stack.push(node_id); - } - - // While we have work to do in our stack - while (index_stack.Count() > 0) { - // Get the current node - const auto current_stack_index = index_stack.top(); - // Check if we've seen the node yet - const auto index_state = GetState(current_stack_index); - if (index_state == NodeStates::State::NoState) { - // Mark the node as seen - UpdateState(NodeStates::State::InFound, current_stack_index); - } else if (index_state == NodeStates::State::InFound) { - // We've seen this node before, mark it as completed - UpdateState(NodeStates::State::InCompleted, current_stack_index); - // Update our index list - PushTsortResult(current_stack_index); - // Pop the stack - index_stack.pop(); - continue; - } else if (index_state == NodeStates::State::InCompleted) { - // If our node is already sorted, clear it - index_stack.pop(); - continue; - } - - const auto edge_node_count = edge_matrix.GetNodeCount(); - for (s32 j = 0; j < static_cast<s32>(edge_node_count); j++) { - // Check if our node is connected to our edge matrix - if (!edge_matrix.Connected(current_stack_index, j)) { - continue; - } - - // Check if our node exists - const auto node_state = GetState(j); - if (node_state == NodeStates::State::NoState) { - // Add more work - index_stack.push(j); - } else if (node_state == NodeStates::State::InFound) { - ASSERT_MSG(false, "Node start marked as found"); - ResetState(); - return false; - } - } - } - } - return true; -} - -void NodeStates::ResetState() { - // Reset to the start of our index stack - index_pos = 0; - for (std::size_t i = 0; i < node_count; i++) { - // Mark all nodes as not found - was_node_found[i] = false; - // Mark all nodes as uncompleted - was_node_completed[i] = false; - // Mark all indexes as invalid - index_list[i] = -1; - } -} - -void NodeStates::UpdateState(NodeStates::State state, std::size_t i) { - switch (state) { - case NodeStates::State::NoState: - was_node_found[i] = false; - was_node_completed[i] = false; - break; - case NodeStates::State::InFound: - was_node_found[i] = true; - was_node_completed[i] = false; - break; - case NodeStates::State::InCompleted: - was_node_found[i] = false; - was_node_completed[i] = true; - break; - } -} - -NodeStates::State NodeStates::GetState(std::size_t i) { - ASSERT(i < node_count); - if (was_node_found[i]) { - // If our node exists in our found list - return NodeStates::State::InFound; - } else if (was_node_completed[i]) { - // If node is in the completed list - return NodeStates::State::InCompleted; - } else { - // If in neither - return NodeStates::State::NoState; - } -} - -NodeStates::Stack::Stack() = default; -NodeStates::Stack::~Stack() = default; - -void NodeStates::Stack::Reset(std::size_t size) { - // Mark our stack as empty - stack.resize(size); - stack_size = size; - stack_pos = 0; - std::fill(stack.begin(), stack.end(), 0); -} - -void NodeStates::Stack::push(s32 val) { - ASSERT(stack_pos < stack_size); - stack[stack_pos++] = val; -} - -std::size_t NodeStates::Stack::Count() const { - return stack_pos; -} - -s32 NodeStates::Stack::top() const { - ASSERT(stack_pos > 0); - return stack[stack_pos - 1]; -} - -s32 NodeStates::Stack::pop() { - ASSERT(stack_pos > 0); - stack_pos--; - return stack[stack_pos]; -} - -EdgeMatrix::EdgeMatrix() = default; -EdgeMatrix::~EdgeMatrix() = default; - -void EdgeMatrix::Initialize(std::size_t _node_count) { - node_count = _node_count; - edge_matrix.resize(node_count * node_count); -} - -bool EdgeMatrix::Connected(s32 a, s32 b) { - return GetState(a, b); -} - -void EdgeMatrix::Connect(s32 a, s32 b) { - SetState(a, b, true); -} - -void EdgeMatrix::Disconnect(s32 a, s32 b) { - SetState(a, b, false); -} - -void EdgeMatrix::RemoveEdges(s32 edge) { - for (std::size_t i = 0; i < node_count; i++) { - SetState(edge, static_cast<s32>(i), false); - } -} - -std::size_t EdgeMatrix::GetNodeCount() const { - return node_count; -} - -void EdgeMatrix::SetState(s32 a, s32 b, bool state) { - ASSERT(InRange(a, b)); - edge_matrix.at(a * node_count + b) = state; -} - -bool EdgeMatrix::GetState(s32 a, s32 b) { - ASSERT(InRange(a, b)); - return edge_matrix.at(a * node_count + b); -} - -bool EdgeMatrix::InRange(s32 a, s32 b) const { - const std::size_t pos = a * node_count + b; - return pos < (node_count * node_count); -} - -} // namespace AudioCore diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h deleted file mode 100644 index 3a4b055eb..000000000 --- a/src/audio_core/splitter_context.h +++ /dev/null @@ -1,218 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <stack> -#include <vector> -#include "audio_core/common.h" -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/swap.h" - -namespace AudioCore { -class BehaviorInfo; - -class EdgeMatrix { -public: - EdgeMatrix(); - ~EdgeMatrix(); - - void Initialize(std::size_t _node_count); - bool Connected(s32 a, s32 b); - void Connect(s32 a, s32 b); - void Disconnect(s32 a, s32 b); - void RemoveEdges(s32 edge); - std::size_t GetNodeCount() const; - -private: - void SetState(s32 a, s32 b, bool state); - bool GetState(s32 a, s32 b); - - bool InRange(s32 a, s32 b) const; - std::vector<bool> edge_matrix{}; - std::size_t node_count{}; -}; - -class NodeStates { -public: - enum class State { - NoState = 0, - InFound = 1, - InCompleted = 2, - }; - - // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols - class Stack { - public: - Stack(); - ~Stack(); - - void Reset(std::size_t size); - void push(s32 val); - std::size_t Count() const; - s32 top() const; - s32 pop(); - - private: - std::vector<s32> stack{}; - std::size_t stack_size{}; - std::size_t stack_pos{}; - }; - NodeStates(); - ~NodeStates(); - - void Initialize(std::size_t node_count_); - bool Tsort(EdgeMatrix& edge_matrix); - std::size_t GetIndexPos() const; - const std::vector<s32>& GetIndexList() const; - -private: - void PushTsortResult(s32 index); - bool DepthFirstSearch(EdgeMatrix& edge_matrix); - void ResetState(); - void UpdateState(State state, std::size_t i); - State GetState(std::size_t i); - - std::size_t node_count{}; - std::vector<bool> was_node_found{}; - std::vector<bool> was_node_completed{}; - std::size_t index_pos{}; - std::vector<s32> index_list{}; - Stack index_stack{}; -}; - -enum class SplitterMagic : u32_le { - SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'), - DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'), - InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'), -}; - -class SplitterInfo { -public: - struct InHeader { - SplitterMagic magic{}; - s32_le info_count{}; - s32_le data_count{}; - INSERT_PADDING_WORDS(5); - }; - static_assert(sizeof(InHeader) == 0x20, "SplitterInfo::InHeader is an invalid size"); - - struct InInfoPrams { - SplitterMagic magic{}; - s32_le send_id{}; - s32_le sample_rate{}; - s32_le length{}; - s32_le resource_id_base{}; - }; - static_assert(sizeof(InInfoPrams) == 0x14, "SplitterInfo::InInfoPrams is an invalid size"); - - struct InDestinationParams { - SplitterMagic magic{}; - s32_le splitter_id{}; - std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{}; - s32_le mix_id{}; - bool in_use{}; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(InDestinationParams) == 0x70, - "SplitterInfo::InDestinationParams is an invalid size"); -}; - -class ServerSplitterDestinationData { -public: - explicit ServerSplitterDestinationData(s32 id_); - ~ServerSplitterDestinationData(); - - void Update(SplitterInfo::InDestinationParams& header); - - ServerSplitterDestinationData* GetNextDestination(); - const ServerSplitterDestinationData* GetNextDestination() const; - void SetNextDestination(ServerSplitterDestinationData* dest); - bool ValidMixId() const; - s32 GetMixId() const; - bool IsConfigured() const; - float GetMixVolume(std::size_t i) const; - const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const; - const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const; - void MarkDirty(); - void UpdateInternalState(); - -private: - bool needs_update{}; - bool in_use{}; - s32 id{}; - s32 mix_id{}; - std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{}; - std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{}; - ServerSplitterDestinationData* next = nullptr; -}; - -class ServerSplitterInfo { -public: - explicit ServerSplitterInfo(s32 id_); - ~ServerSplitterInfo(); - - void InitializeInfos(); - void ClearNewConnectionFlag(); - std::size_t Update(SplitterInfo::InInfoPrams& header); - - ServerSplitterDestinationData* GetHead(); - const ServerSplitterDestinationData* GetHead() const; - ServerSplitterDestinationData* GetData(std::size_t depth); - const ServerSplitterDestinationData* GetData(std::size_t depth) const; - - bool HasNewConnection() const; - s32 GetLength() const; - - void SetHead(ServerSplitterDestinationData* new_head); - void SetHeadDepth(s32 length); - -private: - s32 sample_rate{}; - s32 id{}; - s32 send_length{}; - ServerSplitterDestinationData* head = nullptr; - bool new_connection{}; -}; - -class SplitterContext { -public: - SplitterContext(); - ~SplitterContext(); - - void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count, - std::size_t data_count); - - bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read); - bool UsingSplitter() const; - - ServerSplitterInfo& GetInfo(std::size_t i); - const ServerSplitterInfo& GetInfo(std::size_t i) const; - ServerSplitterDestinationData& GetData(std::size_t i); - const ServerSplitterDestinationData& GetData(std::size_t i) const; - ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data); - const ServerSplitterDestinationData* GetDestinationData(std::size_t info, - std::size_t data) const; - void UpdateInternalState(); - - std::size_t GetInfoCount() const; - std::size_t GetDataCount() const; - -private: - void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed); - bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset, - std::size_t& bytes_read, s32 in_splitter_count); - bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset, - std::size_t& bytes_read, s32 in_data_count); - bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header, - const std::vector<u8>& input, const std::size_t& input_offset); - - std::vector<ServerSplitterInfo> infos{}; - std::vector<ServerSplitterDestinationData> datas{}; - - std::size_t info_count{}; - std::size_t data_count{}; - bool bug_fixed{}; -}; -} // namespace AudioCore diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp deleted file mode 100644 index cf3d94c53..000000000 --- a/src/audio_core/stream.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <algorithm> -#include <cmath> - -#include "audio_core/sink.h" -#include "audio_core/sink_details.h" -#include "audio_core/sink_stream.h" -#include "audio_core/stream.h" -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/settings.h" -#include "core/core_timing.h" - -namespace AudioCore { - -constexpr std::size_t MaxAudioBufferCount{32}; - -u32 Stream::GetNumChannels() const { - switch (format) { - case Format::Mono16: - return 1; - case Format::Stereo16: - return 2; - case Format::Multi51Channel16: - return 6; - } - UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format)); - return {}; -} - -Stream::Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_, - ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_) - : sample_rate{sample_rate_}, format{format_}, release_callback{std::move(release_callback_)}, - sink_stream{sink_stream_}, core_timing{core_timing_}, name{std::move(name_)} { - release_event = Core::Timing::CreateEvent( - name, [this](std::uintptr_t, s64 time, std::chrono::nanoseconds ns_late) { - ReleaseActiveBuffer(ns_late); - return std::nullopt; - }); -} - -void Stream::Play() { - state = State::Playing; - PlayNextBuffer(); -} - -void Stream::Stop() { - state = State::Stopped; - UNIMPLEMENTED(); -} - -bool Stream::Flush() { - const bool had_buffers = !queued_buffers.empty(); - while (!queued_buffers.empty()) { - queued_buffers.pop(); - } - return had_buffers; -} - -void Stream::SetVolume(float volume) { - game_volume = volume; -} - -Stream::State Stream::GetState() const { - return state; -} - -std::chrono::nanoseconds Stream::GetBufferReleaseNS(const Buffer& buffer) const { - const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; - return std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate); -} - -static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) { - const float volume{std::clamp(Settings::Volume() - (1.0f - game_volume), 0.0f, 1.0f)}; - - if (volume == 1.0f) { - return; - } - - // Perceived volume is not the same as the volume level - const float volume_scale_factor = (0.85f * ((volume * volume) - volume)) + volume; - for (auto& sample : samples) { - sample = static_cast<s16>(sample * volume_scale_factor); - } -} - -void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) { - if (!IsPlaying()) { - // Ensure we are in playing state before playing the next buffer - sink_stream.Flush(); - return; - } - - if (active_buffer) { - // Do not queue a new buffer if we are already playing a buffer - return; - } - - if (queued_buffers.empty()) { - // No queued buffers - we are effectively paused - sink_stream.Flush(); - return; - } - - active_buffer = queued_buffers.front(); - queued_buffers.pop(); - - auto& samples = active_buffer->GetSamples(); - - VolumeAdjustSamples(samples, game_volume); - - sink_stream.EnqueueSamples(GetNumChannels(), samples); - played_samples += samples.size(); - - const auto buffer_release_ns = GetBufferReleaseNS(*active_buffer); - - // If ns_late is higher than the update rate ignore the delay - if (ns_late > buffer_release_ns) { - ns_late = {}; - } - - core_timing.ScheduleEvent(buffer_release_ns - ns_late, release_event, {}); -} - -void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) { - ASSERT(active_buffer); - released_buffers.push(std::move(active_buffer)); - release_callback(); - PlayNextBuffer(ns_late); -} - -bool Stream::QueueBuffer(BufferPtr&& buffer) { - if (queued_buffers.size() < MaxAudioBufferCount) { - queued_buffers.push(std::move(buffer)); - PlayNextBuffer(); - return true; - } - return false; -} - -bool Stream::ContainsBuffer([[maybe_unused]] Buffer::Tag tag) const { - UNIMPLEMENTED(); - return {}; -} - -std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(std::size_t max_count) { - std::vector<Buffer::Tag> tags; - for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) { - if (released_buffers.front()) { - tags.push_back(released_buffers.front()->GetTag()); - } else { - ASSERT_MSG(false, "Invalid tag in released_buffers!"); - } - released_buffers.pop(); - } - return tags; -} - -std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers() { - std::vector<Buffer::Tag> tags; - tags.reserve(released_buffers.size()); - while (!released_buffers.empty()) { - if (released_buffers.front()) { - tags.push_back(released_buffers.front()->GetTag()); - } else { - ASSERT_MSG(false, "Invalid tag in released_buffers!"); - } - released_buffers.pop(); - } - return tags; -} - -} // namespace AudioCore diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h deleted file mode 100644 index f5de70396..000000000 --- a/src/audio_core/stream.h +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <chrono> -#include <functional> -#include <memory> -#include <string> -#include <vector> -#include <queue> - -#include "audio_core/buffer.h" -#include "common/common_types.h" - -namespace Core::Timing { -class CoreTiming; -struct EventType; -} // namespace Core::Timing - -namespace AudioCore { - -class SinkStream; - -/** - * Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut - */ -class Stream { -public: - /// Audio format of the stream - enum class Format { - Mono16, - Stereo16, - Multi51Channel16, - }; - - /// Current state of the stream - enum class State { - Stopped, - Playing, - }; - - /// Callback function type, used to change guest state on a buffer being released - using ReleaseCallback = std::function<void()>; - - Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_, - ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_); - - /// Plays the audio stream - void Play(); - - /// Stops the audio stream - void Stop(); - - /// Queues a buffer into the audio stream, returns true on success - bool QueueBuffer(BufferPtr&& buffer); - - /// Flush audio buffers - bool Flush(); - - /// Returns true if the audio stream contains a buffer with the specified tag - [[nodiscard]] bool ContainsBuffer(Buffer::Tag tag) const; - - /// Returns a vector of recently released buffers specified by tag - [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count); - - /// Returns a vector of all recently released buffers specified by tag - [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(); - - void SetVolume(float volume); - - [[nodiscard]] float GetVolume() const { - return game_volume; - } - - /// Returns true if the stream is currently playing - [[nodiscard]] bool IsPlaying() const { - return state == State::Playing; - } - - /// Returns the number of queued buffers - [[nodiscard]] std::size_t GetQueueSize() const { - return queued_buffers.size(); - } - - /// Gets the sample rate - [[nodiscard]] u32 GetSampleRate() const { - return sample_rate; - } - - /// Gets the number of samples played so far - [[nodiscard]] u64 GetPlayedSampleCount() const { - return played_samples; - } - - /// Gets the number of channels - [[nodiscard]] u32 GetNumChannels() const; - - /// Get the state - [[nodiscard]] State GetState() const; - -private: - /// Plays the next queued buffer in the audio stream, starting playback if necessary - void PlayNextBuffer(std::chrono::nanoseconds ns_late = {}); - - /// Releases the actively playing buffer, signalling that it has been completed - void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {}); - - /// Gets the number of core cycles when the specified buffer will be released - [[nodiscard]] std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const; - - u32 sample_rate; ///< Sample rate of the stream - u64 played_samples{}; ///< The current played sample count - Format format; ///< Format of the stream - float game_volume = 1.0f; ///< The volume the game currently has set - ReleaseCallback release_callback; ///< Buffer release callback for the stream - State state{State::Stopped}; ///< Playback state of the stream - std::shared_ptr<Core::Timing::EventType> - release_event; ///< Core timing release event for the stream - BufferPtr active_buffer; ///< Actively playing buffer in the stream - std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream - std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream - SinkStream& sink_stream; ///< Output sink for the stream - Core::Timing::CoreTiming& core_timing; ///< Core timing instance. - std::string name; ///< Name of the stream, must be unique -}; - -using StreamPtr = std::shared_ptr<Stream>; - -} // namespace AudioCore diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp deleted file mode 100644 index f58a5c754..000000000 --- a/src/audio_core/voice_context.cpp +++ /dev/null @@ -1,579 +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/voice_context.h" -#include "core/memory.h" - -namespace AudioCore { - -ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id_) : id(id_) {} -ServerVoiceChannelResource::~ServerVoiceChannelResource() = default; - -bool ServerVoiceChannelResource::InUse() const { - return in_use; -} - -float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const { - ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); - return mix_volume.at(i); -} - -float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const { - ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); - return last_mix_volume.at(i); -} - -void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) { - in_use = in_params.in_use; - // Update our mix volumes only if it's in use - if (in_params.in_use) { - mix_volume = in_params.mix_volume; - } -} - -void ServerVoiceChannelResource::UpdateLastMixVolumes() { - last_mix_volume = mix_volume; -} - -const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& -ServerVoiceChannelResource::GetCurrentMixVolume() const { - return mix_volume; -} - -const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& -ServerVoiceChannelResource::GetLastMixVolume() const { - return last_mix_volume; -} - -ServerVoiceInfo::ServerVoiceInfo() { - Initialize(); -} -ServerVoiceInfo::~ServerVoiceInfo() = default; - -void ServerVoiceInfo::Initialize() { - in_params.in_use = false; - in_params.node_id = 0; - in_params.id = 0; - in_params.current_playstate = ServerPlayState::Stop; - in_params.priority = 255; - in_params.sample_rate = 0; - in_params.sample_format = SampleFormat::Invalid; - in_params.channel_count = 0; - in_params.pitch = 0.0f; - in_params.volume = 0.0f; - in_params.last_volume = 0.0f; - in_params.biquad_filter.fill({}); - in_params.wave_buffer_count = 0; - in_params.wave_buffer_head = 0; - in_params.mix_id = AudioCommon::NO_MIX; - in_params.splitter_info_id = AudioCommon::NO_SPLITTER; - in_params.additional_params_address = 0; - in_params.additional_params_size = 0; - in_params.is_new = false; - out_params.played_sample_count = 0; - out_params.wave_buffer_consumed = 0; - in_params.voice_drop_flag = false; - in_params.buffer_mapped = true; - in_params.wave_buffer_flush_request_count = 0; - in_params.was_biquad_filter_enabled.fill(false); - - for (auto& wave_buffer : in_params.wave_buffer) { - wave_buffer.start_sample_offset = 0; - wave_buffer.end_sample_offset = 0; - wave_buffer.is_looping = false; - wave_buffer.end_of_stream = false; - wave_buffer.buffer_address = 0; - wave_buffer.buffer_size = 0; - wave_buffer.context_address = 0; - wave_buffer.context_size = 0; - wave_buffer.sent_to_dsp = true; - } - - stored_samples.clear(); -} - -void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in, - BehaviorInfo& behavior_info) { - in_params.in_use = voice_in.is_in_use; - in_params.id = voice_in.id; - in_params.node_id = voice_in.node_id; - in_params.last_playstate = in_params.current_playstate; - switch (voice_in.play_state) { - case PlayState::Paused: - in_params.current_playstate = ServerPlayState::Paused; - break; - case PlayState::Stopped: - if (in_params.current_playstate != ServerPlayState::Stop) { - in_params.current_playstate = ServerPlayState::RequestStop; - } - break; - case PlayState::Started: - in_params.current_playstate = ServerPlayState::Play; - break; - default: - ASSERT_MSG(false, "Unknown playstate {}", voice_in.play_state); - break; - } - - in_params.priority = voice_in.priority; - in_params.sorting_order = voice_in.sorting_order; - in_params.sample_rate = voice_in.sample_rate; - in_params.sample_format = voice_in.sample_format; - in_params.channel_count = voice_in.channel_count; - in_params.pitch = voice_in.pitch; - in_params.volume = voice_in.volume; - in_params.biquad_filter = voice_in.biquad_filter; - in_params.wave_buffer_count = voice_in.wave_buffer_count; - in_params.wave_buffer_head = voice_in.wave_buffer_head; - if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { - const auto in_request_count = in_params.wave_buffer_flush_request_count; - const auto voice_request_count = voice_in.wave_buffer_flush_request_count; - in_params.wave_buffer_flush_request_count = - static_cast<u8>(in_request_count + voice_request_count); - } - in_params.mix_id = voice_in.mix_id; - if (behavior_info.IsSplitterSupported()) { - in_params.splitter_info_id = voice_in.splitter_info_id; - } else { - in_params.splitter_info_id = AudioCommon::NO_SPLITTER; - } - - std::memcpy(in_params.voice_channel_resource_id.data(), - voice_in.voice_channel_resource_ids.data(), - sizeof(s32) * in_params.voice_channel_resource_id.size()); - - if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) { - in_params.behavior_flags.is_played_samples_reset_at_loop_point = - voice_in.behavior_flags.is_played_samples_reset_at_loop_point; - } else { - in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0); - } - if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) { - in_params.behavior_flags.is_pitch_and_src_skipped = - voice_in.behavior_flags.is_pitch_and_src_skipped; - } else { - in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0); - } - - if (voice_in.is_voice_drop_flag_clear_requested) { - in_params.voice_drop_flag = false; - } - - if (in_params.additional_params_address != voice_in.additional_params_address || - in_params.additional_params_size != voice_in.additional_params_size) { - in_params.additional_params_address = voice_in.additional_params_address; - in_params.additional_params_size = voice_in.additional_params_size; - // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that - // our context is new - } -} - -void ServerVoiceInfo::UpdateWaveBuffers( - const VoiceInfo::InParams& voice_in, - std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states, - BehaviorInfo& behavior_info) { - if (voice_in.is_new) { - // Initialize our wave buffers - for (auto& wave_buffer : in_params.wave_buffer) { - wave_buffer.start_sample_offset = 0; - wave_buffer.end_sample_offset = 0; - wave_buffer.is_looping = false; - wave_buffer.end_of_stream = false; - wave_buffer.buffer_address = 0; - wave_buffer.buffer_size = 0; - wave_buffer.context_address = 0; - wave_buffer.context_size = 0; - wave_buffer.loop_start_sample = 0; - wave_buffer.loop_end_sample = 0; - wave_buffer.sent_to_dsp = true; - } - - // Mark all our wave buffers as invalid - for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count); - channel++) { - for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; ++i) { - voice_states[channel]->is_wave_buffer_valid[i] = false; - } - } - } - - // Update our wave buffers - for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { - // Assume that we have at least 1 channel voice state - const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i]; - - UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format, - have_valid_wave_buffer, behavior_info); - } -} - -void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, - const WaveBuffer& in_wave_buffer, SampleFormat sample_format, - bool is_buffer_valid, - [[maybe_unused]] BehaviorInfo& behavior_info) { - if (!is_buffer_valid && out_wavebuffer.sent_to_dsp && out_wavebuffer.buffer_address != 0) { - out_wavebuffer.buffer_address = 0; - out_wavebuffer.buffer_size = 0; - } - - if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) { - // Validate sample offset sizings - if (sample_format == SampleFormat::Pcm16) { - const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size); - const s64 start = sizeof(s16) * in_wave_buffer.start_sample_offset; - const s64 end = sizeof(s16) * in_wave_buffer.end_sample_offset; - if (0 > start || start > buffer_size || 0 > end || end > buffer_size) { - // TODO(ogniK): Write error info - LOG_ERROR(Audio, - "PCM16 wavebuffer has an invalid size. Buffer has size 0x{:08X}, but " - "offsets were " - "{:08X} - 0x{:08X}", - buffer_size, sizeof(s16) * in_wave_buffer.start_sample_offset, - sizeof(s16) * in_wave_buffer.end_sample_offset); - return; - } - } else if (sample_format == SampleFormat::Adpcm) { - const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size); - const s64 start_frames = in_wave_buffer.start_sample_offset / 14; - const s64 start_extra = in_wave_buffer.start_sample_offset % 14 == 0 - ? 0 - : (in_wave_buffer.start_sample_offset % 14) / 2 + 1 + - (in_wave_buffer.start_sample_offset % 2); - const s64 start = start_frames * 8 + start_extra; - const s64 end_frames = in_wave_buffer.end_sample_offset / 14; - const s64 end_extra = in_wave_buffer.end_sample_offset % 14 == 0 - ? 0 - : (in_wave_buffer.end_sample_offset % 14) / 2 + 1 + - (in_wave_buffer.end_sample_offset % 2); - const s64 end = end_frames * 8 + end_extra; - if (in_wave_buffer.start_sample_offset < 0 || start > buffer_size || - in_wave_buffer.end_sample_offset < 0 || end > buffer_size) { - LOG_ERROR(Audio, - "ADPMC wavebuffer has an invalid size. Buffer has size 0x{:08X}, but " - "offsets were " - "{:08X} - 0x{:08X}", - in_wave_buffer.buffer_size, start, end); - return; - } - } - // TODO(ogniK): ADPCM Size error - - out_wavebuffer.sent_to_dsp = false; - out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset; - out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset; - out_wavebuffer.is_looping = in_wave_buffer.is_looping; - out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream; - - out_wavebuffer.buffer_address = in_wave_buffer.buffer_address; - out_wavebuffer.buffer_size = in_wave_buffer.buffer_size; - out_wavebuffer.context_address = in_wave_buffer.context_address; - out_wavebuffer.context_size = in_wave_buffer.context_size; - out_wavebuffer.loop_start_sample = in_wave_buffer.loop_start_sample; - out_wavebuffer.loop_end_sample = in_wave_buffer.loop_end_sample; - in_params.buffer_mapped = - in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0; - // TODO(ogniK): Pool mapper attachment - // TODO(ogniK): IsAdpcmLoopContextBugFixed - if (sample_format == SampleFormat::Adpcm && in_wave_buffer.context_address != 0 && - in_wave_buffer.context_size != 0 && behavior_info.IsAdpcmLoopContextBugFixed()) { - } else { - out_wavebuffer.context_address = 0; - out_wavebuffer.context_size = 0; - } - } -} - -void ServerVoiceInfo::WriteOutStatus( - VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, - std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) { - if (voice_in.is_new || in_params.is_new) { - in_params.is_new = true; - voice_out.wave_buffer_consumed = 0; - voice_out.played_sample_count = 0; - voice_out.voice_dropped = false; - } else { - const auto& state = voice_states[0]; - voice_out.wave_buffer_consumed = state->wave_buffer_consumed; - voice_out.played_sample_count = state->played_sample_count; - voice_out.voice_dropped = state->voice_dropped; - } -} - -const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const { - return in_params; -} - -ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() { - return in_params; -} - -const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const { - return out_params; -} - -ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() { - return out_params; -} - -bool ServerVoiceInfo::ShouldSkip() const { - // TODO(ogniK): Handle unmapped wave buffers or parameters - return !in_params.in_use || in_params.wave_buffer_count == 0 || !in_params.buffer_mapped || - in_params.voice_drop_flag; -} - -bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { - std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{}; - if (in_params.is_new) { - ResetResources(voice_context); - in_params.last_volume = in_params.volume; - in_params.is_new = false; - } - - const s32 channel_count = in_params.channel_count; - for (s32 i = 0; i < channel_count; i++) { - const auto channel_resource = in_params.voice_channel_resource_id[i]; - dsp_voice_states[i] = - &voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource)); - } - return UpdateParametersForCommandGeneration(dsp_voice_states); -} - -void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) { - const s32 channel_count = in_params.channel_count; - for (s32 i = 0; i < channel_count; i++) { - const auto channel_resource = in_params.voice_channel_resource_id[i]; - auto& dsp_state = - voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource)); - dsp_state = {}; - voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource)) - .UpdateLastMixVolumes(); - } -} - -bool ServerVoiceInfo::UpdateParametersForCommandGeneration( - std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) { - const s32 channel_count = in_params.channel_count; - if (in_params.wave_buffer_flush_request_count > 0) { - FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states, - channel_count); - in_params.wave_buffer_flush_request_count = 0; - } - - switch (in_params.current_playstate) { - case ServerPlayState::Play: { - for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { - if (!in_params.wave_buffer[i].sent_to_dsp) { - for (s32 channel = 0; channel < channel_count; channel++) { - dsp_voice_states[channel]->is_wave_buffer_valid[i] = true; - } - in_params.wave_buffer[i].sent_to_dsp = true; - } - } - in_params.should_depop = false; - return HasValidWaveBuffer(dsp_voice_states[0]); - } - case ServerPlayState::Paused: - case ServerPlayState::Stop: { - in_params.should_depop = in_params.last_playstate == ServerPlayState::Play; - return in_params.should_depop; - } - case ServerPlayState::RequestStop: { - for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { - in_params.wave_buffer[i].sent_to_dsp = true; - for (s32 channel = 0; channel < channel_count; channel++) { - auto* dsp_state = dsp_voice_states[channel]; - - if (dsp_state->is_wave_buffer_valid[i]) { - dsp_state->wave_buffer_index = - (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; - dsp_state->wave_buffer_consumed++; - } - - dsp_state->is_wave_buffer_valid[i] = false; - } - } - - for (s32 channel = 0; channel < channel_count; channel++) { - auto* dsp_state = dsp_voice_states[channel]; - dsp_state->offset = 0; - dsp_state->played_sample_count = 0; - dsp_state->fraction = 0; - dsp_state->sample_history.fill(0); - dsp_state->context = {}; - } - - in_params.current_playstate = ServerPlayState::Stop; - in_params.should_depop = in_params.last_playstate == ServerPlayState::Play; - return in_params.should_depop; - } - default: - ASSERT_MSG(false, "Invalid playstate {}", in_params.current_playstate); - } - - return false; -} - -void ServerVoiceInfo::FlushWaveBuffers( - u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, - s32 channel_count) { - auto wave_head = in_params.wave_buffer_head; - - for (u8 i = 0; i < flush_count; i++) { - in_params.wave_buffer[wave_head].sent_to_dsp = true; - for (s32 channel = 0; channel < channel_count; channel++) { - auto* dsp_state = dsp_voice_states[channel]; - dsp_state->wave_buffer_consumed++; - dsp_state->is_wave_buffer_valid[wave_head] = false; - dsp_state->wave_buffer_index = - (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; - } - wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS; - } -} - -bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const { - const auto& valid_wb = state->is_wave_buffer_valid; - return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end(); -} - -void ServerVoiceInfo::SetWaveBufferCompleted(VoiceState& dsp_state, - const ServerWaveBuffer& wave_buffer) { - dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; - dsp_state.wave_buffer_consumed++; - dsp_state.wave_buffer_index = (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; - dsp_state.loop_count = 0; - if (wave_buffer.end_of_stream) { - dsp_state.played_sample_count = 0; - } -} - -VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} { - for (std::size_t i = 0; i < voice_count; i++) { - voice_channel_resources.emplace_back(static_cast<s32>(i)); - sorted_voice_info.push_back(&voice_info.emplace_back()); - voice_states.emplace_back(); - dsp_voice_states.emplace_back(); - } -} - -VoiceContext::~VoiceContext() { - sorted_voice_info.clear(); -} - -std::size_t VoiceContext::GetVoiceCount() const { - return voice_count; -} - -ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) { - ASSERT(i < voice_count); - return voice_channel_resources.at(i); -} - -const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const { - ASSERT(i < voice_count); - return voice_channel_resources.at(i); -} - -VoiceState& VoiceContext::GetState(std::size_t i) { - ASSERT(i < voice_count); - return voice_states.at(i); -} - -const VoiceState& VoiceContext::GetState(std::size_t i) const { - ASSERT(i < voice_count); - return voice_states.at(i); -} - -VoiceState& VoiceContext::GetDspSharedState(std::size_t i) { - ASSERT(i < voice_count); - return dsp_voice_states.at(i); -} - -const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const { - ASSERT(i < voice_count); - return dsp_voice_states.at(i); -} - -ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) { - ASSERT(i < voice_count); - return voice_info.at(i); -} - -const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const { - ASSERT(i < voice_count); - return voice_info.at(i); -} - -ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) { - ASSERT(i < voice_count); - return *sorted_voice_info.at(i); -} - -const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const { - ASSERT(i < voice_count); - return *sorted_voice_info.at(i); -} - -s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel, - s32 channel_count, s32 buffer_offset, s32 sample_count, - Core::Memory::Memory& memory) { - if (wave_buffer->buffer_address == 0) { - return 0; - } - if (wave_buffer->buffer_size == 0) { - return 0; - } - if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) { - return 0; - } - - const auto samples_remaining = - (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset; - const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count; - const auto buffer_pos = wave_buffer->buffer_address + start_offset; - - s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos)); - - const auto samples_processed = std::min(sample_count, samples_remaining); - - // Fast path - if (channel_count == 1) { - for (std::ptrdiff_t i = 0; i < samples_processed; i++) { - output_buffer[i] = buffer_data[i]; - } - } else { - for (std::ptrdiff_t i = 0; i < samples_processed; i++) { - output_buffer[i] = buffer_data[i * channel_count + channel]; - } - } - - return samples_processed; -} - -void VoiceContext::SortInfo() { - for (std::size_t i = 0; i < voice_count; i++) { - sorted_voice_info[i] = &voice_info[i]; - } - - std::sort(sorted_voice_info.begin(), sorted_voice_info.end(), - [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) { - const auto& lhs_in = lhs->GetInParams(); - const auto& rhs_in = rhs->GetInParams(); - // Sort by priority - if (lhs_in.priority != rhs_in.priority) { - return lhs_in.priority > rhs_in.priority; - } else { - // If the priorities match, sort by sorting order - return lhs_in.sorting_order > rhs_in.sorting_order; - } - }); -} - -void VoiceContext::UpdateStateByDspShared() { - voice_states = dsp_voice_states; -} - -} // namespace AudioCore diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h deleted file mode 100644 index 259220dc7..000000000 --- a/src/audio_core/voice_context.h +++ /dev/null @@ -1,302 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <array> -#include "audio_core/algorithm/interpolate.h" -#include "audio_core/codec.h" -#include "audio_core/common.h" -#include "common/bit_field.h" -#include "common/common_funcs.h" -#include "common/common_types.h" - -namespace Core::Memory { -class Memory; -} - -namespace AudioCore { - -class BehaviorInfo; -class VoiceContext; - -enum class SampleFormat : u8 { - Invalid = 0, - Pcm8 = 1, - Pcm16 = 2, - Pcm24 = 3, - Pcm32 = 4, - PcmFloat = 5, - Adpcm = 6, -}; - -enum class PlayState : u8 { - Started = 0, - Stopped = 1, - Paused = 2, -}; - -enum class ServerPlayState { - Play = 0, - Stop = 1, - RequestStop = 2, - Paused = 3, -}; - -struct BiquadFilterParameter { - bool enabled{}; - INSERT_PADDING_BYTES(1); - std::array<s16, 3> numerator{}; - std::array<s16, 2> denominator{}; -}; -static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size"); - -struct WaveBuffer { - u64_le buffer_address{}; - u64_le buffer_size{}; - s32_le start_sample_offset{}; - s32_le end_sample_offset{}; - u8 is_looping{}; - u8 end_of_stream{}; - u8 sent_to_server{}; - INSERT_PADDING_BYTES(1); - s32 loop_count{}; - u64 context_address{}; - u64 context_size{}; - u32 loop_start_sample{}; - u32 loop_end_sample{}; -}; -static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); - -struct ServerWaveBuffer { - VAddr buffer_address{}; - std::size_t buffer_size{}; - s32 start_sample_offset{}; - s32 end_sample_offset{}; - bool is_looping{}; - bool end_of_stream{}; - VAddr context_address{}; - std::size_t context_size{}; - s32 loop_count{}; - u32 loop_start_sample{}; - u32 loop_end_sample{}; - bool sent_to_dsp{true}; -}; - -struct BehaviorFlags { - BitField<0, 1, u16> is_played_samples_reset_at_loop_point; - BitField<1, 1, u16> is_pitch_and_src_skipped; -}; -static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size"); - -struct ADPCMContext { - u16 header; - s16 yn1; - s16 yn2; -}; -static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size"); - -struct VoiceState { - s64 played_sample_count; - s32 offset; - s32 wave_buffer_index; - std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid; - s32 wave_buffer_consumed; - std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history; - s32 fraction; - VAddr context_address; - Codec::ADPCM_Coeff coeff; - ADPCMContext context; - std::array<s64, 2> biquad_filter_state; - std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples; - u32 external_context_size; - bool is_external_context_used; - bool voice_dropped; - s32 loop_count; -}; - -class VoiceChannelResource { -public: - struct InParams { - s32_le id{}; - std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{}; - bool in_use{}; - INSERT_PADDING_BYTES(11); - }; - static_assert(sizeof(InParams) == 0x70, "InParams is an invalid size"); -}; - -class ServerVoiceChannelResource { -public: - explicit ServerVoiceChannelResource(s32 id_); - ~ServerVoiceChannelResource(); - - bool InUse() const; - float GetCurrentMixVolumeAt(std::size_t i) const; - float GetLastMixVolumeAt(std::size_t i) const; - void Update(VoiceChannelResource::InParams& in_params); - void UpdateLastMixVolumes(); - - const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const; - const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const; - -private: - s32 id{}; - std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{}; - std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{}; - bool in_use{}; -}; - -class VoiceInfo { -public: - struct InParams { - s32_le id{}; - u32_le node_id{}; - u8 is_new{}; - u8 is_in_use{}; - PlayState play_state{}; - SampleFormat sample_format{}; - s32_le sample_rate{}; - s32_le priority{}; - s32_le sorting_order{}; - s32_le channel_count{}; - float_le pitch{}; - float_le volume{}; - std::array<BiquadFilterParameter, 2> biquad_filter{}; - s32_le wave_buffer_count{}; - s16_le wave_buffer_head{}; - INSERT_PADDING_BYTES(6); - u64_le additional_params_address{}; - u64_le additional_params_size{}; - s32_le mix_id{}; - s32_le splitter_info_id{}; - std::array<WaveBuffer, 4> wave_buffer{}; - std::array<u32_le, 6> voice_channel_resource_ids{}; - // TODO(ogniK): Remaining flags - u8 is_voice_drop_flag_clear_requested{}; - u8 wave_buffer_flush_request_count{}; - INSERT_PADDING_BYTES(2); - BehaviorFlags behavior_flags{}; - INSERT_PADDING_BYTES(16); - }; - static_assert(sizeof(InParams) == 0x170, "InParams is an invalid size"); - - struct OutParams { - u64_le played_sample_count{}; - u32_le wave_buffer_consumed{}; - u8 voice_dropped{}; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size"); -}; - -class ServerVoiceInfo { -public: - struct InParams { - bool in_use{}; - bool is_new{}; - bool should_depop{}; - SampleFormat sample_format{}; - s32 sample_rate{}; - s32 channel_count{}; - s32 id{}; - s32 node_id{}; - s32 mix_id{}; - ServerPlayState current_playstate{}; - ServerPlayState last_playstate{}; - s32 priority{}; - s32 sorting_order{}; - float pitch{}; - float volume{}; - float last_volume{}; - std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{}; - s32 wave_buffer_count{}; - s16 wave_buffer_head{}; - INSERT_PADDING_BYTES(2); - BehaviorFlags behavior_flags{}; - VAddr additional_params_address{}; - std::size_t additional_params_size{}; - std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{}; - std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{}; - s32 splitter_info_id{}; - u8 wave_buffer_flush_request_count{}; - bool voice_drop_flag{}; - bool buffer_mapped{}; - std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{}; - }; - - struct OutParams { - s64 played_sample_count{}; - s32 wave_buffer_consumed{}; - }; - - ServerVoiceInfo(); - ~ServerVoiceInfo(); - void Initialize(); - void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info); - void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in, - std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states, - BehaviorInfo& behavior_info); - void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer, - SampleFormat sample_format, bool is_buffer_valid, - BehaviorInfo& behavior_info); - void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, - std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states); - - const InParams& GetInParams() const; - InParams& GetInParams(); - - const OutParams& GetOutParams() const; - OutParams& GetOutParams(); - - bool ShouldSkip() const; - bool UpdateForCommandGeneration(VoiceContext& voice_context); - void ResetResources(VoiceContext& voice_context); - bool UpdateParametersForCommandGeneration( - std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states); - void FlushWaveBuffers(u8 flush_count, - std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, - s32 channel_count); - void SetWaveBufferCompleted(VoiceState& dsp_state, const ServerWaveBuffer& wave_buffer); - -private: - std::vector<s16> stored_samples; - InParams in_params{}; - OutParams out_params{}; - - bool HasValidWaveBuffer(const VoiceState* state) const; -}; - -class VoiceContext { -public: - explicit VoiceContext(std::size_t voice_count_); - ~VoiceContext(); - - std::size_t GetVoiceCount() const; - ServerVoiceChannelResource& GetChannelResource(std::size_t i); - const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const; - VoiceState& GetState(std::size_t i); - const VoiceState& GetState(std::size_t i) const; - VoiceState& GetDspSharedState(std::size_t i); - const VoiceState& GetDspSharedState(std::size_t i) const; - ServerVoiceInfo& GetInfo(std::size_t i); - const ServerVoiceInfo& GetInfo(std::size_t i) const; - ServerVoiceInfo& GetSortedInfo(std::size_t i); - const ServerVoiceInfo& GetSortedInfo(std::size_t i) const; - - s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel, - s32 channel_count, s32 buffer_offset, s32 sample_count, - Core::Memory::Memory& memory); - void SortInfo(); - void UpdateStateByDspShared(); - -private: - std::size_t voice_count{}; - std::vector<ServerVoiceChannelResource> voice_channel_resources{}; - std::vector<VoiceState> voice_states{}; - std::vector<VoiceState> dsp_voice_states{}; - std::vector<ServerVoiceInfo> voice_info{}; - std::vector<ServerVoiceInfo*> sorted_voice_info{}; -}; - -} // namespace AudioCore diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 73bf626d4..d574e4b79 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -43,6 +43,7 @@ add_library(common STATIC alignment.h assert.cpp assert.h + atomic_helpers.h atomic_ops.h detached_tasks.cpp detached_tasks.h @@ -64,6 +65,7 @@ add_library(common STATIC expected.h fiber.cpp fiber.h + fixed_point.h fs/file.cpp fs/file.h fs/fs.cpp @@ -109,6 +111,7 @@ add_library(common STATIC parent_of_member.h point.h quaternion.h + reader_writer_queue.h ring_buffer.h scm_rev.cpp scm_rev.h @@ -182,8 +185,9 @@ create_target_directory_groups(common) target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) target_link_libraries(common PRIVATE lz4::lz4 xbyak) -if (MSVC) +if (TARGET zstd::zstd) target_link_libraries(common PRIVATE zstd::zstd) else() - target_link_libraries(common PRIVATE zstd) + target_link_libraries(common PRIVATE + $<IF:$<TARGET_EXISTS:zstd::libzstd_shared>,zstd::libzstd_shared,zstd::libzstd_static>) endif() diff --git a/src/common/atomic_helpers.h b/src/common/atomic_helpers.h new file mode 100644 index 000000000..6d912b52e --- /dev/null +++ b/src/common/atomic_helpers.h @@ -0,0 +1,772 @@ +// ©2013-2016 Cameron Desrochers. +// Distributed under the simplified BSD license (see the license file that +// should have come with this header). +// Uses Jeff Preshing's semaphore implementation (under the terms of its +// separate zlib license, embedded below). + +#pragma once + +// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant) +// implementation of low-level memory barriers, plus a few semi-portable utility macros (for +// inlining and alignment). Also has a basic atomic type (limited to hardware-supported atomics with +// no memory ordering guarantees). Uses the AE_* prefix for macros (historical reasons), and the +// "moodycamel" namespace for symbols. + +#include <cassert> +#include <cerrno> +#include <cstdint> +#include <ctime> +#include <type_traits> + +// Platform detection +#if defined(__INTEL_COMPILER) +#define AE_ICC +#elif defined(_MSC_VER) +#define AE_VCPP +#elif defined(__GNUC__) +#define AE_GCC +#endif + +#if defined(_M_IA64) || defined(__ia64__) +#define AE_ARCH_IA64 +#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__) +#define AE_ARCH_X64 +#elif defined(_M_IX86) || defined(__i386__) +#define AE_ARCH_X86 +#elif defined(_M_PPC) || defined(__powerpc__) +#define AE_ARCH_PPC +#else +#define AE_ARCH_UNKNOWN +#endif + +// AE_UNUSED +#define AE_UNUSED(x) ((void)x) + +// AE_NO_TSAN/AE_TSAN_ANNOTATE_* +#if defined(__has_feature) +#if __has_feature(thread_sanitizer) +#if __cplusplus >= 201703L // inline variables require C++17 +namespace Common { +inline int ae_tsan_global; +} +#define AE_TSAN_ANNOTATE_RELEASE() \ + AnnotateHappensBefore(__FILE__, __LINE__, (void*)(&::moodycamel::ae_tsan_global)) +#define AE_TSAN_ANNOTATE_ACQUIRE() \ + AnnotateHappensAfter(__FILE__, __LINE__, (void*)(&::moodycamel::ae_tsan_global)) +extern "C" void AnnotateHappensBefore(const char*, int, void*); +extern "C" void AnnotateHappensAfter(const char*, int, void*); +#else // when we can't work with tsan, attempt to disable its warnings +#define AE_NO_TSAN __attribute__((no_sanitize("thread"))) +#endif +#endif +#endif +#ifndef AE_NO_TSAN +#define AE_NO_TSAN +#endif +#ifndef AE_TSAN_ANNOTATE_RELEASE +#define AE_TSAN_ANNOTATE_RELEASE() +#define AE_TSAN_ANNOTATE_ACQUIRE() +#endif + +// AE_FORCEINLINE +#if defined(AE_VCPP) || defined(AE_ICC) +#define AE_FORCEINLINE __forceinline +#elif defined(AE_GCC) +//#define AE_FORCEINLINE __attribute__((always_inline)) +#define AE_FORCEINLINE inline +#else +#define AE_FORCEINLINE inline +#endif + +// AE_ALIGN +#if defined(AE_VCPP) || defined(AE_ICC) +#define AE_ALIGN(x) __declspec(align(x)) +#elif defined(AE_GCC) +#define AE_ALIGN(x) __attribute__((aligned(x))) +#else +// Assume GCC compliant syntax... +#define AE_ALIGN(x) __attribute__((aligned(x))) +#endif + +// Portable atomic fences implemented below: + +namespace Common { + +enum memory_order { + memory_order_relaxed, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, + + // memory_order_sync: Forces a full sync: + // #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad + memory_order_sync = memory_order_seq_cst +}; + +} // namespace Common + +#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || \ + (defined(AE_ICC) && __INTEL_COMPILER < 1600) +// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences + +#include <intrin.h> + +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) +#define AeFullSync _mm_mfence +#define AeLiteSync _mm_mfence +#elif defined(AE_ARCH_IA64) +#define AeFullSync __mf +#define AeLiteSync __mf +#elif defined(AE_ARCH_PPC) +#include <ppcintrinsics.h> +#define AeFullSync __sync +#define AeLiteSync __lwsync +#endif + +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable : 4365) // Disable erroneous 'conversion from long to unsigned int, + // signed/unsigned mismatch' error when using `assert` +#ifdef __cplusplus_cli +#pragma managed(push, off) +#endif +#endif + +namespace Common { + +AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN { + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + _ReadBarrier(); + break; + case memory_order_release: + _WriteBarrier(); + break; + case memory_order_acq_rel: + _ReadWriteBarrier(); + break; + case memory_order_seq_cst: + _ReadWriteBarrier(); + break; + default: + assert(false); + } +} + +// x86/x64 have a strong memory model -- all loads and stores have +// acquire and release semantics automatically (so only need compiler +// barriers for those). +#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64) +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN { + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + _ReadBarrier(); + break; + case memory_order_release: + _WriteBarrier(); + break; + case memory_order_acq_rel: + _ReadWriteBarrier(); + break; + case memory_order_seq_cst: + _ReadWriteBarrier(); + AeFullSync(); + _ReadWriteBarrier(); + break; + default: + assert(false); + } +} +#else +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN { + // Non-specialized arch, use heavier memory barriers everywhere just in case :-( + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + _ReadBarrier(); + AeLiteSync(); + _ReadBarrier(); + break; + case memory_order_release: + _WriteBarrier(); + AeLiteSync(); + _WriteBarrier(); + break; + case memory_order_acq_rel: + _ReadWriteBarrier(); + AeLiteSync(); + _ReadWriteBarrier(); + break; + case memory_order_seq_cst: + _ReadWriteBarrier(); + AeFullSync(); + _ReadWriteBarrier(); + break; + default: + assert(false); + } +} +#endif +} // namespace Common +#else +// Use standard library of atomics +#include <atomic> + +namespace Common { + +AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN { + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + std::atomic_signal_fence(std::memory_order_acquire); + break; + case memory_order_release: + std::atomic_signal_fence(std::memory_order_release); + break; + case memory_order_acq_rel: + std::atomic_signal_fence(std::memory_order_acq_rel); + break; + case memory_order_seq_cst: + std::atomic_signal_fence(std::memory_order_seq_cst); + break; + default: + assert(false); + } +} + +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN { + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + AE_TSAN_ANNOTATE_ACQUIRE(); + std::atomic_thread_fence(std::memory_order_acquire); + break; + case memory_order_release: + AE_TSAN_ANNOTATE_RELEASE(); + std::atomic_thread_fence(std::memory_order_release); + break; + case memory_order_acq_rel: + AE_TSAN_ANNOTATE_ACQUIRE(); + AE_TSAN_ANNOTATE_RELEASE(); + std::atomic_thread_fence(std::memory_order_acq_rel); + break; + case memory_order_seq_cst: + AE_TSAN_ANNOTATE_ACQUIRE(); + AE_TSAN_ANNOTATE_RELEASE(); + std::atomic_thread_fence(std::memory_order_seq_cst); + break; + default: + assert(false); + } +} + +} // namespace Common + +#endif + +#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli)) +#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC +#endif + +#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC +#include <atomic> +#endif +#include <utility> + +// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY: +// Provides basic support for atomic variables -- no memory ordering guarantees are provided. +// The guarantee of atomicity is only made for types that already have atomic load and store +// guarantees at the hardware level -- on most platforms this generally means aligned pointers and +// integers (only). +namespace Common { +template <typename T> +class weak_atomic { +public: + AE_NO_TSAN weak_atomic() : value() {} +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable : 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning +#endif + template <typename U> + AE_NO_TSAN weak_atomic(U&& x) : value(std::forward<U>(x)) {} +#ifdef __cplusplus_cli + // Work around bug with universal reference/nullptr combination that only appears when /clr is + // on + AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) {} +#endif + AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) {} + AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) {} +#ifdef AE_VCPP +#pragma warning(pop) +#endif + + AE_FORCEINLINE operator T() const AE_NO_TSAN { + return load(); + } + +#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC + template <typename U> + AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { + value = std::forward<U>(x); + return *this; + } + AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { + value = other.value; + return *this; + } + + AE_FORCEINLINE T load() const AE_NO_TSAN { + return value; + } + + AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN { +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) + if (sizeof(T) == 4) + return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); +#if defined(_M_AMD64) + else if (sizeof(T) == 8) + return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); +#endif +#else +#error Unsupported platform +#endif + assert(false && "T must be either a 32 or 64 bit type"); + return value; + } + + AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN { +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) + if (sizeof(T) == 4) + return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); +#if defined(_M_AMD64) + else if (sizeof(T) == 8) + return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); +#endif +#else +#error Unsupported platform +#endif + assert(false && "T must be either a 32 or 64 bit type"); + return value; + } +#else + template <typename U> + AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { + value.store(std::forward<U>(x), std::memory_order_relaxed); + return *this; + } + + AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { + value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed); + return *this; + } + + AE_FORCEINLINE T load() const AE_NO_TSAN { + return value.load(std::memory_order_relaxed); + } + + AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN { + return value.fetch_add(increment, std::memory_order_acquire); + } + + AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN { + return value.fetch_add(increment, std::memory_order_release); + } +#endif + +private: +#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC + // No std::atomic support, but still need to circumvent compiler optimizations. + // `volatile` will make memory access slow, but is guaranteed to be reliable. + volatile T value; +#else + std::atomic<T> value; +#endif +}; + +} // namespace Common + +// Portable single-producer, single-consumer semaphore below: + +#if defined(_WIN32) +// Avoid including windows.h in a header; we only need a handful of +// items, so we'll redeclare them here (this is relatively safe since +// the API generally has to remain stable between Windows versions). +// I know this is an ugly hack but it still beats polluting the global +// namespace with thousands of generic names or adding a .cpp for nothing. +extern "C" { +struct _SECURITY_ATTRIBUTES; +__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, + long lInitialCount, long lMaximumCount, + const wchar_t* lpName); +__declspec(dllimport) int __stdcall CloseHandle(void* hObject); +__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, + unsigned long dwMilliseconds); +__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, + long* lpPreviousCount); +} +#elif defined(__MACH__) +#include <mach/mach.h> +#elif defined(__unix__) +#include <semaphore.h> +#elif defined(FREERTOS) +#include <FreeRTOS.h> +#include <semphr.h> +#include <task.h> +#endif + +namespace Common { +// Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's +// portable + lightweight semaphore implementations, originally from +// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h +// LICENSE: +// Copyright (c) 2015 Jeff Preshing +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgement in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +namespace spsc_sema { +#if defined(_WIN32) +class Semaphore { +private: + void* m_hSema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + +public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema() { + assert(initialCount >= 0); + const long maxLong = 0x7fffffff; + m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); + assert(m_hSema); + } + + AE_NO_TSAN ~Semaphore() { + CloseHandle(m_hSema); + } + + bool wait() AE_NO_TSAN { + const unsigned long infinite = 0xffffffff; + return WaitForSingleObject(m_hSema, infinite) == 0; + } + + bool try_wait() AE_NO_TSAN { + return WaitForSingleObject(m_hSema, 0) == 0; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN { + return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; + } + + void signal(int count = 1) AE_NO_TSAN { + while (!ReleaseSemaphore(m_hSema, count, nullptr)) + ; + } +}; +#elif defined(__MACH__) +//--------------------------------------------------------- +// Semaphore (Apple iOS and OSX) +// Can't use POSIX semaphores due to +// http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html +//--------------------------------------------------------- +class Semaphore { +private: + semaphore_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + +public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() { + assert(initialCount >= 0); + kern_return_t rc = + semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); + assert(rc == KERN_SUCCESS); + AE_UNUSED(rc); + } + + AE_NO_TSAN ~Semaphore() { + semaphore_destroy(mach_task_self(), m_sema); + } + + bool wait() AE_NO_TSAN { + return semaphore_wait(m_sema) == KERN_SUCCESS; + } + + bool try_wait() AE_NO_TSAN { + return timed_wait(0); + } + + bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN { + mach_timespec_t ts; + ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000); + ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000); + + // added in OSX 10.10: + // https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html + kern_return_t rc = semaphore_timedwait(m_sema, ts); + return rc == KERN_SUCCESS; + } + + void signal() AE_NO_TSAN { + while (semaphore_signal(m_sema) != KERN_SUCCESS) + ; + } + + void signal(int count) AE_NO_TSAN { + while (count-- > 0) { + while (semaphore_signal(m_sema) != KERN_SUCCESS) + ; + } + } +}; +#elif defined(__unix__) +//--------------------------------------------------------- +// Semaphore (POSIX, Linux) +//--------------------------------------------------------- +class Semaphore { +private: + sem_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + +public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() { + assert(initialCount >= 0); + int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount)); + assert(rc == 0); + AE_UNUSED(rc); + } + + AE_NO_TSAN ~Semaphore() { + sem_destroy(&m_sema); + } + + bool wait() AE_NO_TSAN { + // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error + int rc; + do { + rc = sem_wait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool try_wait() AE_NO_TSAN { + int rc; + do { + rc = sem_trywait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN { + struct timespec ts; + const int usecs_in_1_sec = 1000000; + const int nsecs_in_1_sec = 1000000000; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += static_cast<time_t>(usecs / usecs_in_1_sec); + ts.tv_nsec += static_cast<long>(usecs % usecs_in_1_sec) * 1000; + // sem_timedwait bombs if you have more than 1e9 in tv_nsec + // so we have to clean things up before passing it in + if (ts.tv_nsec >= nsecs_in_1_sec) { + ts.tv_nsec -= nsecs_in_1_sec; + ++ts.tv_sec; + } + + int rc; + do { + rc = sem_timedwait(&m_sema, &ts); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + void signal() AE_NO_TSAN { + while (sem_post(&m_sema) == -1) + ; + } + + void signal(int count) AE_NO_TSAN { + while (count-- > 0) { + while (sem_post(&m_sema) == -1) + ; + } + } +}; +#elif defined(FREERTOS) +//--------------------------------------------------------- +// Semaphore (FreeRTOS) +//--------------------------------------------------------- +class Semaphore { +private: + SemaphoreHandle_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + +public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() { + assert(initialCount >= 0); + m_sema = xSemaphoreCreateCounting(static_cast<UBaseType_t>(~0ull), + static_cast<UBaseType_t>(initialCount)); + assert(m_sema); + } + + AE_NO_TSAN ~Semaphore() { + vSemaphoreDelete(m_sema); + } + + bool wait() AE_NO_TSAN { + return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE; + } + + bool try_wait() AE_NO_TSAN { + // Note: In an ISR context, if this causes a task to unblock, + // the caller won't know about it + if (xPortIsInsideInterrupt()) + return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE; + return xSemaphoreTake(m_sema, 0) == pdTRUE; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN { + std::uint64_t msecs = usecs / 1000; + TickType_t ticks = static_cast<TickType_t>(msecs / portTICK_PERIOD_MS); + if (ticks == 0) + return try_wait(); + return xSemaphoreTake(m_sema, ticks) == pdTRUE; + } + + void signal() AE_NO_TSAN { + // Note: In an ISR context, if this causes a task to unblock, + // the caller won't know about it + BaseType_t rc; + if (xPortIsInsideInterrupt()) + rc = xSemaphoreGiveFromISR(m_sema, NULL); + else + rc = xSemaphoreGive(m_sema); + assert(rc == pdTRUE); + AE_UNUSED(rc); + } + + void signal(int count) AE_NO_TSAN { + while (count-- > 0) + signal(); + } +}; +#else +#error Unsupported platform! (No semaphore wrapper available) +#endif + +//--------------------------------------------------------- +// LightweightSemaphore +//--------------------------------------------------------- +class LightweightSemaphore { +public: + typedef std::make_signed<std::size_t>::type ssize_t; + +private: + weak_atomic<ssize_t> m_count; + Semaphore m_sema; + + bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN { + ssize_t oldCount; + // Is there a better way to set the initial spin count? + // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC, + // as threads start hitting the kernel semaphore. + int spin = 1024; + while (--spin >= 0) { + if (m_count.load() > 0) { + m_count.fetch_add_acquire(-1); + return true; + } + compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop. + } + oldCount = m_count.fetch_add_acquire(-1); + if (oldCount > 0) + return true; + if (timeout_usecs < 0) { + if (m_sema.wait()) + return true; + } + if (timeout_usecs > 0 && m_sema.timed_wait(static_cast<uint64_t>(timeout_usecs))) + return true; + // At this point, we've timed out waiting for the semaphore, but the + // count is still decremented indicating we may still be waiting on + // it. So we have to re-adjust the count, but only if the semaphore + // wasn't signaled enough times for us too since then. If it was, we + // need to release the semaphore too. + while (true) { + oldCount = m_count.fetch_add_release(1); + if (oldCount < 0) + return false; // successfully restored things to the way they were + // Oh, the producer thread just signaled the semaphore after all. Try again: + oldCount = m_count.fetch_add_acquire(-1); + if (oldCount > 0 && m_sema.try_wait()) + return true; + } + } + +public: + AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema() { + assert(initialCount >= 0); + } + + bool tryWait() AE_NO_TSAN { + if (m_count.load() > 0) { + m_count.fetch_add_acquire(-1); + return true; + } + return false; + } + + bool wait() AE_NO_TSAN { + return tryWait() || waitWithPartialSpinning(); + } + + bool wait(std::int64_t timeout_usecs) AE_NO_TSAN { + return tryWait() || waitWithPartialSpinning(timeout_usecs); + } + + void signal(ssize_t count = 1) AE_NO_TSAN { + assert(count >= 0); + ssize_t oldCount = m_count.fetch_add_release(count); + assert(oldCount >= -1); + if (oldCount < 0) { + m_sema.signal(1); + } + } + + std::size_t availableApprox() const AE_NO_TSAN { + ssize_t count = m_count.load(); + return count > 0 ? static_cast<std::size_t>(count) : 0; + } +}; +} // namespace spsc_sema +} // namespace Common + +#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli)) +#pragma warning(pop) +#ifdef __cplusplus_cli +#pragma managed(pop) +#endif +#endif diff --git a/src/common/bit_field.h b/src/common/bit_field.h index 16d805694..7e1df62b1 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -146,7 +146,16 @@ public: } constexpr void Assign(const T& value) { +#ifdef _MSC_VER storage = static_cast<StorageType>((storage & ~mask) | FormatValue(value)); +#else + // Explicitly reload with memcpy to avoid compiler aliasing quirks + // regarding optimization: GCC/Clang clobber chained stores to + // different bitfields in the same struct with the last value. + StorageTypeWithEndian storage_; + std::memcpy(&storage_, &storage, sizeof(storage_)); + storage = static_cast<StorageType>((storage_ & ~mask) | FormatValue(value)); +#endif } [[nodiscard]] constexpr T Value() const { diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index adc31c758..e1e2a90fc 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -18,14 +18,16 @@ /// Helper macros to insert unused bytes or words to properly align structs. These values will be /// zero-initialized. #define INSERT_PADDING_BYTES(num_bytes) \ - std::array<u8, num_bytes> CONCAT2(pad, __LINE__) {} + [[maybe_unused]] std::array<u8, num_bytes> CONCAT2(pad, __LINE__) {} #define INSERT_PADDING_WORDS(num_words) \ - std::array<u32, num_words> CONCAT2(pad, __LINE__) {} + [[maybe_unused]] std::array<u32, num_words> CONCAT2(pad, __LINE__) {} /// These are similar to the INSERT_PADDING_* macros but do not zero-initialize the contents. /// This keeps the structure trivial to construct. -#define INSERT_PADDING_BYTES_NOINIT(num_bytes) std::array<u8, num_bytes> CONCAT2(pad, __LINE__) -#define INSERT_PADDING_WORDS_NOINIT(num_words) std::array<u32, num_words> CONCAT2(pad, __LINE__) +#define INSERT_PADDING_BYTES_NOINIT(num_bytes) \ + [[maybe_unused]] std::array<u8, num_bytes> CONCAT2(pad, __LINE__) +#define INSERT_PADDING_WORDS_NOINIT(num_words) \ + [[maybe_unused]] std::array<u32, num_words> CONCAT2(pad, __LINE__) #ifndef _MSC_VER diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h new file mode 100644 index 000000000..1d45e51b3 --- /dev/null +++ b/src/common/fixed_point.h @@ -0,0 +1,726 @@ +// From: https://github.com/eteran/cpp-utilities/blob/master/fixed/include/cpp-utilities/fixed.h +// See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Evan Teran + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FIXED_H_ +#define FIXED_H_ + +#if __cplusplus >= 201402L +#define CONSTEXPR14 constexpr +#else +#define CONSTEXPR14 +#endif + +#include <cstddef> // for size_t +#include <cstdint> +#include <exception> +#include <ostream> +#include <type_traits> + +namespace Common { + +template <size_t I, size_t F> +class FixedPoint; + +namespace detail { + +// helper templates to make magic with types :) +// these allow us to determine resonable types from +// a desired size, they also let us infer the next largest type +// from a type which is nice for the division op +template <size_t T> +struct type_from_size { + using value_type = void; + using unsigned_type = void; + using signed_type = void; + static constexpr bool is_specialized = false; +}; + +#if defined(__GNUC__) && defined(__x86_64__) && !defined(__STRICT_ANSI__) +template <> +struct type_from_size<128> { + static constexpr bool is_specialized = true; + static constexpr size_t size = 128; + + using value_type = __int128; + using unsigned_type = unsigned __int128; + using signed_type = __int128; + using next_size = type_from_size<256>; +}; +#endif + +template <> +struct type_from_size<64> { + static constexpr bool is_specialized = true; + static constexpr size_t size = 64; + + using value_type = int64_t; + using unsigned_type = std::make_unsigned<value_type>::type; + using signed_type = std::make_signed<value_type>::type; + using next_size = type_from_size<128>; +}; + +template <> +struct type_from_size<32> { + static constexpr bool is_specialized = true; + static constexpr size_t size = 32; + + using value_type = int32_t; + using unsigned_type = std::make_unsigned<value_type>::type; + using signed_type = std::make_signed<value_type>::type; + using next_size = type_from_size<64>; +}; + +template <> +struct type_from_size<16> { + static constexpr bool is_specialized = true; + static constexpr size_t size = 16; + + using value_type = int16_t; + using unsigned_type = std::make_unsigned<value_type>::type; + using signed_type = std::make_signed<value_type>::type; + using next_size = type_from_size<32>; +}; + +template <> +struct type_from_size<8> { + static constexpr bool is_specialized = true; + static constexpr size_t size = 8; + + using value_type = int8_t; + using unsigned_type = std::make_unsigned<value_type>::type; + using signed_type = std::make_signed<value_type>::type; + using next_size = type_from_size<16>; +}; + +// this is to assist in adding support for non-native base +// types (for adding big-int support), this should be fine +// unless your bit-int class doesn't nicely support casting +template <class B, class N> +constexpr B next_to_base(N rhs) { + return static_cast<B>(rhs); +} + +struct divide_by_zero : std::exception {}; + +template <size_t I, size_t F> +CONSTEXPR14 FixedPoint<I, F> divide( + FixedPoint<I, F> numerator, FixedPoint<I, F> denominator, FixedPoint<I, F>& remainder, + typename std::enable_if<type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) { + + using next_type = typename FixedPoint<I, F>::next_type; + using base_type = typename FixedPoint<I, F>::base_type; + constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits; + + next_type t(numerator.to_raw()); + t <<= fractional_bits; + + FixedPoint<I, F> quotient; + + quotient = FixedPoint<I, F>::from_base(next_to_base<base_type>(t / denominator.to_raw())); + remainder = FixedPoint<I, F>::from_base(next_to_base<base_type>(t % denominator.to_raw())); + + return quotient; +} + +template <size_t I, size_t F> +CONSTEXPR14 FixedPoint<I, F> divide( + FixedPoint<I, F> numerator, FixedPoint<I, F> denominator, FixedPoint<I, F>& remainder, + typename std::enable_if<!type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) { + + using unsigned_type = typename FixedPoint<I, F>::unsigned_type; + + constexpr int bits = FixedPoint<I, F>::total_bits; + + if (denominator == 0) { + throw divide_by_zero(); + } else { + + int sign = 0; + + FixedPoint<I, F> quotient; + + if (numerator < 0) { + sign ^= 1; + numerator = -numerator; + } + + if (denominator < 0) { + sign ^= 1; + denominator = -denominator; + } + + unsigned_type n = numerator.to_raw(); + unsigned_type d = denominator.to_raw(); + unsigned_type x = 1; + unsigned_type answer = 0; + + // egyptian division algorithm + while ((n >= d) && (((d >> (bits - 1)) & 1) == 0)) { + x <<= 1; + d <<= 1; + } + + while (x != 0) { + if (n >= d) { + n -= d; + answer += x; + } + + x >>= 1; + d >>= 1; + } + + unsigned_type l1 = n; + unsigned_type l2 = denominator.to_raw(); + + // calculate the lower bits (needs to be unsigned) + while (l1 >> (bits - F) > 0) { + l1 >>= 1; + l2 >>= 1; + } + const unsigned_type lo = (l1 << F) / l2; + + quotient = FixedPoint<I, F>::from_base((answer << F) | lo); + remainder = n; + + if (sign) { + quotient = -quotient; + } + + return quotient; + } +} + +// this is the usual implementation of multiplication +template <size_t I, size_t F> +CONSTEXPR14 FixedPoint<I, F> multiply( + FixedPoint<I, F> lhs, FixedPoint<I, F> rhs, + typename std::enable_if<type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) { + + using next_type = typename FixedPoint<I, F>::next_type; + using base_type = typename FixedPoint<I, F>::base_type; + + constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits; + + next_type t(static_cast<next_type>(lhs.to_raw()) * static_cast<next_type>(rhs.to_raw())); + t >>= fractional_bits; + + return FixedPoint<I, F>::from_base(next_to_base<base_type>(t)); +} + +// this is the fall back version we use when we don't have a next size +// it is slightly slower, but is more robust since it doesn't +// require and upgraded type +template <size_t I, size_t F> +CONSTEXPR14 FixedPoint<I, F> multiply( + FixedPoint<I, F> lhs, FixedPoint<I, F> rhs, + typename std::enable_if<!type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) { + + using base_type = typename FixedPoint<I, F>::base_type; + + constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits; + constexpr base_type integer_mask = FixedPoint<I, F>::integer_mask; + constexpr base_type fractional_mask = FixedPoint<I, F>::fractional_mask; + + // more costly but doesn't need a larger type + const base_type a_hi = (lhs.to_raw() & integer_mask) >> fractional_bits; + const base_type b_hi = (rhs.to_raw() & integer_mask) >> fractional_bits; + const base_type a_lo = (lhs.to_raw() & fractional_mask); + const base_type b_lo = (rhs.to_raw() & fractional_mask); + + const base_type x1 = a_hi * b_hi; + const base_type x2 = a_hi * b_lo; + const base_type x3 = a_lo * b_hi; + const base_type x4 = a_lo * b_lo; + + return FixedPoint<I, F>::from_base((x1 << fractional_bits) + (x3 + x2) + + (x4 >> fractional_bits)); +} +} // namespace detail + +template <size_t I, size_t F> +class FixedPoint { + static_assert(detail::type_from_size<I + F>::is_specialized, "invalid combination of sizes"); + +public: + static constexpr size_t fractional_bits = F; + static constexpr size_t integer_bits = I; + static constexpr size_t total_bits = I + F; + + using base_type_info = detail::type_from_size<total_bits>; + + using base_type = typename base_type_info::value_type; + using next_type = typename base_type_info::next_size::value_type; + using unsigned_type = typename base_type_info::unsigned_type; + +public: +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Woverflow" +#endif + static constexpr base_type fractional_mask = + ~(static_cast<unsigned_type>(~base_type(0)) << fractional_bits); + static constexpr base_type integer_mask = ~fractional_mask; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +public: + static constexpr base_type one = base_type(1) << fractional_bits; + +public: // constructors + FixedPoint() = default; + FixedPoint(const FixedPoint&) = default; + FixedPoint(FixedPoint&&) = default; + FixedPoint& operator=(const FixedPoint&) = default; + + template <class Number> + constexpr FixedPoint( + Number n, typename std::enable_if<std::is_arithmetic<Number>::value>::type* = nullptr) + : data_(static_cast<base_type>(n * one)) {} + +public: // conversion + template <size_t I2, size_t F2> + CONSTEXPR14 explicit FixedPoint(FixedPoint<I2, F2> other) { + static_assert(I2 <= I && F2 <= F, "Scaling conversion can only upgrade types"); + using T = FixedPoint<I2, F2>; + + const base_type fractional = (other.data_ & T::fractional_mask); + const base_type integer = (other.data_ & T::integer_mask) >> T::fractional_bits; + data_ = + (integer << fractional_bits) | (fractional << (fractional_bits - T::fractional_bits)); + } + +private: + // this makes it simpler to create a FixedPoint point object from + // a native type without scaling + // use "FixedPoint::from_base" in order to perform this. + struct NoScale {}; + + constexpr FixedPoint(base_type n, const NoScale&) : data_(n) {} + +public: + static constexpr FixedPoint from_base(base_type n) { + return FixedPoint(n, NoScale()); + } + +public: // comparison operators + constexpr bool operator==(FixedPoint rhs) const { + return data_ == rhs.data_; + } + + constexpr bool operator!=(FixedPoint rhs) const { + return data_ != rhs.data_; + } + + constexpr bool operator<(FixedPoint rhs) const { + return data_ < rhs.data_; + } + + constexpr bool operator>(FixedPoint rhs) const { + return data_ > rhs.data_; + } + + constexpr bool operator<=(FixedPoint rhs) const { + return data_ <= rhs.data_; + } + + constexpr bool operator>=(FixedPoint rhs) const { + return data_ >= rhs.data_; + } + +public: // unary operators + constexpr bool operator!() const { + return !data_; + } + + constexpr FixedPoint operator~() const { + // NOTE(eteran): this will often appear to "just negate" the value + // that is not an error, it is because -x == (~x+1) + // and that "+1" is adding an infinitesimally small fraction to the + // complimented value + return FixedPoint::from_base(~data_); + } + + constexpr FixedPoint operator-() const { + return FixedPoint::from_base(-data_); + } + + constexpr FixedPoint operator+() const { + return FixedPoint::from_base(+data_); + } + + CONSTEXPR14 FixedPoint& operator++() { + data_ += one; + return *this; + } + + CONSTEXPR14 FixedPoint& operator--() { + data_ -= one; + return *this; + } + + CONSTEXPR14 FixedPoint operator++(int) { + FixedPoint tmp(*this); + data_ += one; + return tmp; + } + + CONSTEXPR14 FixedPoint operator--(int) { + FixedPoint tmp(*this); + data_ -= one; + return tmp; + } + +public: // basic math operators + CONSTEXPR14 FixedPoint& operator+=(FixedPoint n) { + data_ += n.data_; + return *this; + } + + CONSTEXPR14 FixedPoint& operator-=(FixedPoint n) { + data_ -= n.data_; + return *this; + } + + CONSTEXPR14 FixedPoint& operator*=(FixedPoint n) { + return assign(detail::multiply(*this, n)); + } + + CONSTEXPR14 FixedPoint& operator/=(FixedPoint n) { + FixedPoint temp; + return assign(detail::divide(*this, n, temp)); + } + +private: + CONSTEXPR14 FixedPoint& assign(FixedPoint rhs) { + data_ = rhs.data_; + return *this; + } + +public: // binary math operators, effects underlying bit pattern since these + // don't really typically make sense for non-integer values + CONSTEXPR14 FixedPoint& operator&=(FixedPoint n) { + data_ &= n.data_; + return *this; + } + + CONSTEXPR14 FixedPoint& operator|=(FixedPoint n) { + data_ |= n.data_; + return *this; + } + + CONSTEXPR14 FixedPoint& operator^=(FixedPoint n) { + data_ ^= n.data_; + return *this; + } + + template <class Integer, + class = typename std::enable_if<std::is_integral<Integer>::value>::type> + CONSTEXPR14 FixedPoint& operator>>=(Integer n) { + data_ >>= n; + return *this; + } + + template <class Integer, + class = typename std::enable_if<std::is_integral<Integer>::value>::type> + CONSTEXPR14 FixedPoint& operator<<=(Integer n) { + data_ <<= n; + return *this; + } + +public: // conversion to basic types + constexpr void round_up() { + data_ += (data_ & fractional_mask) >> 1; + } + + constexpr int to_int() { + round_up(); + return static_cast<int>((data_ & integer_mask) >> fractional_bits); + } + + constexpr unsigned int to_uint() const { + round_up(); + return static_cast<unsigned int>((data_ & integer_mask) >> fractional_bits); + } + + constexpr int64_t to_long() { + round_up(); + return static_cast<int64_t>((data_ & integer_mask) >> fractional_bits); + } + + constexpr int to_int_floor() const { + return static_cast<int>((data_ & integer_mask) >> fractional_bits); + } + + constexpr int64_t to_long_floor() { + return static_cast<int64_t>((data_ & integer_mask) >> fractional_bits); + } + + constexpr unsigned int to_uint_floor() const { + return static_cast<unsigned int>((data_ & integer_mask) >> fractional_bits); + } + + constexpr float to_float() const { + return static_cast<float>(data_) / FixedPoint::one; + } + + constexpr double to_double() const { + return static_cast<double>(data_) / FixedPoint::one; + } + + constexpr base_type to_raw() const { + return data_; + } + + constexpr void clear_int() { + data_ &= fractional_mask; + } + + constexpr base_type get_frac() const { + return data_ & fractional_mask; + } + +public: + CONSTEXPR14 void swap(FixedPoint& rhs) { + using std::swap; + swap(data_, rhs.data_); + } + +public: + base_type data_; +}; + +// if we have the same fractional portion, but differing integer portions, we trivially upgrade the +// smaller type +template <size_t I1, size_t I2, size_t F> +CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type +operator+(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { + + using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type; + + const T l = T::from_base(lhs.to_raw()); + const T r = T::from_base(rhs.to_raw()); + return l + r; +} + +template <size_t I1, size_t I2, size_t F> +CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type +operator-(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { + + using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type; + + const T l = T::from_base(lhs.to_raw()); + const T r = T::from_base(rhs.to_raw()); + return l - r; +} + +template <size_t I1, size_t I2, size_t F> +CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type +operator*(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { + + using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type; + + const T l = T::from_base(lhs.to_raw()); + const T r = T::from_base(rhs.to_raw()); + return l * r; +} + +template <size_t I1, size_t I2, size_t F> +CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type +operator/(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { + + using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type; + + const T l = T::from_base(lhs.to_raw()); + const T r = T::from_base(rhs.to_raw()); + return l / r; +} + +template <size_t I, size_t F> +std::ostream& operator<<(std::ostream& os, FixedPoint<I, F> f) { + os << f.to_double(); + return os; +} + +// basic math operators +template <size_t I, size_t F> +CONSTEXPR14 FixedPoint<I, F> operator+(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { + lhs += rhs; + return lhs; +} +template <size_t I, size_t F> +CONSTEXPR14 FixedPoint<I, F> operator-(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { + lhs -= rhs; + return lhs; +} +template <size_t I, size_t F> +CONSTEXPR14 FixedPoint<I, F> operator*(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { + lhs *= rhs; + return lhs; +} +template <size_t I, size_t F> +CONSTEXPR14 FixedPoint<I, F> operator/(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { + lhs /= rhs; + return lhs; +} + +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +CONSTEXPR14 FixedPoint<I, F> operator+(FixedPoint<I, F> lhs, Number rhs) { + lhs += FixedPoint<I, F>(rhs); + return lhs; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +CONSTEXPR14 FixedPoint<I, F> operator-(FixedPoint<I, F> lhs, Number rhs) { + lhs -= FixedPoint<I, F>(rhs); + return lhs; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +CONSTEXPR14 FixedPoint<I, F> operator*(FixedPoint<I, F> lhs, Number rhs) { + lhs *= FixedPoint<I, F>(rhs); + return lhs; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +CONSTEXPR14 FixedPoint<I, F> operator/(FixedPoint<I, F> lhs, Number rhs) { + lhs /= FixedPoint<I, F>(rhs); + return lhs; +} + +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +CONSTEXPR14 FixedPoint<I, F> operator+(Number lhs, FixedPoint<I, F> rhs) { + FixedPoint<I, F> tmp(lhs); + tmp += rhs; + return tmp; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +CONSTEXPR14 FixedPoint<I, F> operator-(Number lhs, FixedPoint<I, F> rhs) { + FixedPoint<I, F> tmp(lhs); + tmp -= rhs; + return tmp; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +CONSTEXPR14 FixedPoint<I, F> operator*(Number lhs, FixedPoint<I, F> rhs) { + FixedPoint<I, F> tmp(lhs); + tmp *= rhs; + return tmp; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +CONSTEXPR14 FixedPoint<I, F> operator/(Number lhs, FixedPoint<I, F> rhs) { + FixedPoint<I, F> tmp(lhs); + tmp /= rhs; + return tmp; +} + +// shift operators +template <size_t I, size_t F, class Integer, + class = typename std::enable_if<std::is_integral<Integer>::value>::type> +CONSTEXPR14 FixedPoint<I, F> operator<<(FixedPoint<I, F> lhs, Integer rhs) { + lhs <<= rhs; + return lhs; +} +template <size_t I, size_t F, class Integer, + class = typename std::enable_if<std::is_integral<Integer>::value>::type> +CONSTEXPR14 FixedPoint<I, F> operator>>(FixedPoint<I, F> lhs, Integer rhs) { + lhs >>= rhs; + return lhs; +} + +// comparison operators +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator>(FixedPoint<I, F> lhs, Number rhs) { + return lhs > FixedPoint<I, F>(rhs); +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator<(FixedPoint<I, F> lhs, Number rhs) { + return lhs < FixedPoint<I, F>(rhs); +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator>=(FixedPoint<I, F> lhs, Number rhs) { + return lhs >= FixedPoint<I, F>(rhs); +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator<=(FixedPoint<I, F> lhs, Number rhs) { + return lhs <= FixedPoint<I, F>(rhs); +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator==(FixedPoint<I, F> lhs, Number rhs) { + return lhs == FixedPoint<I, F>(rhs); +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator!=(FixedPoint<I, F> lhs, Number rhs) { + return lhs != FixedPoint<I, F>(rhs); +} + +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator>(Number lhs, FixedPoint<I, F> rhs) { + return FixedPoint<I, F>(lhs) > rhs; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator<(Number lhs, FixedPoint<I, F> rhs) { + return FixedPoint<I, F>(lhs) < rhs; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator>=(Number lhs, FixedPoint<I, F> rhs) { + return FixedPoint<I, F>(lhs) >= rhs; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator<=(Number lhs, FixedPoint<I, F> rhs) { + return FixedPoint<I, F>(lhs) <= rhs; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator==(Number lhs, FixedPoint<I, F> rhs) { + return FixedPoint<I, F>(lhs) == rhs; +} +template <size_t I, size_t F, class Number, + class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> +constexpr bool operator!=(Number lhs, FixedPoint<I, F> rhs) { + return FixedPoint<I, F>(lhs) != rhs; +} + +} // namespace Common + +#undef CONSTEXPR14 + +#endif diff --git a/src/common/input.h b/src/common/input.h index bb42aaacc..995c35d9d 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -28,7 +28,7 @@ enum class InputType { Color, Vibration, Nfc, - Ir, + IrSensor, }; // Internal battery charge level @@ -53,6 +53,15 @@ enum class PollingMode { IR, }; +enum class CameraFormat { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, + None, +}; + // Vibration reply from the controller enum class VibrationError { None, @@ -68,6 +77,13 @@ enum class PollingError { Unknown, }; +// Ir camera reply from the controller +enum class CameraError { + None, + NotSupported, + Unknown, +}; + // Hint for amplification curve to be used enum class VibrationAmplificationType { Linear, @@ -176,6 +192,12 @@ struct LedStatus { bool led_4{}; }; +// Raw data fom camera +struct CameraStatus { + CameraFormat format{CameraFormat::None}; + std::vector<u8> data{}; +}; + // List of buttons to be passed to Qt that can be translated enum class ButtonNames { Undefined, @@ -233,6 +255,7 @@ struct CallbackStatus { BodyColorStatus color_status{}; BatteryStatus battery_status{}; VibrationStatus vibration_status{}; + CameraStatus camera_status{}; }; // Triggered once every input change @@ -281,6 +304,10 @@ public: virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) { return PollingError::NotSupported; } + + virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) { + return CameraError::NotSupported; + } }; /// An abstract class template for a factory that can create input devices. diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 4acbff649..6de9bacbf 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -128,7 +128,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Service, PM) \ SUB(Service, PREPO) \ SUB(Service, PSC) \ - SUB(Service, PSM) \ + SUB(Service, PTM) \ SUB(Service, SET) \ SUB(Service, SM) \ SUB(Service, SPL) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index cabb4db8e..595c15ada 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -95,7 +95,7 @@ enum class Class : u8 { Service_PM, ///< The PM service Service_PREPO, ///< The PREPO (Play report) service Service_PSC, ///< The PSC service - Service_PSM, ///< The PSM service + Service_PTM, ///< The PTM service Service_SET, ///< The SET (Settings) service Service_SM, ///< The SM (Service manager) service Service_SPL, ///< The SPL service diff --git a/src/common/reader_writer_queue.h b/src/common/reader_writer_queue.h new file mode 100644 index 000000000..8d2c9408c --- /dev/null +++ b/src/common/reader_writer_queue.h @@ -0,0 +1,941 @@ +// ©2013-2020 Cameron Desrochers. +// Distributed under the simplified BSD license (see the license file that +// should have come with this header). + +#pragma once + +#include <cassert> +#include <cstdint> +#include <cstdlib> // For malloc/free/abort & size_t +#include <memory> +#include <new> +#include <stdexcept> +#include <type_traits> +#include <utility> + +#include "common/atomic_helpers.h" + +#if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012 +#include <chrono> +#endif + +// A lock-free queue for a single-consumer, single-producer architecture. +// The queue is also wait-free in the common path (except if more memory +// needs to be allocated, in which case malloc is called). +// Allocates memory sparingly, and only once if the original maximum size +// estimate is never exceeded. +// Tested on x86/x64 processors, but semantics should be correct for all +// architectures (given the right implementations in atomicops.h), provided +// that aligned integer and pointer accesses are naturally atomic. +// Note that there should only be one consumer thread and producer thread; +// Switching roles of the threads, or using multiple consecutive threads for +// one role, is not safe unless properly synchronized. +// Using the queue exclusively from one thread is fine, though a bit silly. + +#ifndef MOODYCAMEL_CACHE_LINE_SIZE +#define MOODYCAMEL_CACHE_LINE_SIZE 64 +#endif + +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || \ + (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#endif +#endif + +#ifndef MOODYCAMEL_HAS_EMPLACE +#if !defined(_MSC_VER) || \ + _MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013 +#define MOODYCAMEL_HAS_EMPLACE 1 +#endif +#endif + +#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#if defined(__APPLE__) && defined(__MACH__) && __cplusplus >= 201703L +// This is required to find out what deployment target we are using +#include <CoreFoundation/CoreFoundation.h> +#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14 +// C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we +// can't support over-alignment in this case +#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#endif +#endif +#endif + +#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE) +#endif + +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable : 4324) // structure was padded due to __declspec(align()) +#pragma warning(disable : 4820) // padding was added +#pragma warning(disable : 4127) // conditional expression is constant +#endif + +namespace Common { + +template <typename T, size_t MAX_BLOCK_SIZE = 512> +class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue { + // Design: Based on a queue-of-queues. The low-level queues are just + // circular buffers with front and tail indices indicating where the + // next element to dequeue is and where the next element can be enqueued, + // respectively. Each low-level queue is called a "block". Each block + // wastes exactly one element's worth of space to keep the design simple + // (if front == tail then the queue is empty, and can't be full). + // The high-level queue is a circular linked list of blocks; again there + // is a front and tail, but this time they are pointers to the blocks. + // The front block is where the next element to be dequeued is, provided + // the block is not empty. The back block is where elements are to be + // enqueued, provided the block is not full. + // The producer thread owns all the tail indices/pointers. The consumer + // thread owns all the front indices/pointers. Both threads read each + // other's variables, but only the owning thread updates them. E.g. After + // the consumer reads the producer's tail, the tail may change before the + // consumer is done dequeuing an object, but the consumer knows the tail + // will never go backwards, only forwards. + // If there is no room to enqueue an object, an additional block (of + // equal size to the last block) is added. Blocks are never removed. + +public: + typedef T value_type; + + // Constructs a queue that can hold at least `size` elements without further + // allocations. If more than MAX_BLOCK_SIZE elements are requested, + // then several blocks of MAX_BLOCK_SIZE each are reserved (including + // at least one extra buffer block). + AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15) +#ifndef NDEBUG + : enqueuing(false), dequeuing(false) +#endif + { + assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) && + "MAX_BLOCK_SIZE must be a power of 2"); + assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2"); + + Block* firstBlock = nullptr; + + largestBlockSize = + ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block + if (largestBlockSize > MAX_BLOCK_SIZE * 2) { + // We need a spare block in case the producer is writing to a different block the + // consumer is reading from, and wants to enqueue the maximum number of elements. We + // also need a spare element in each block to avoid the ambiguity between front == tail + // meaning "empty" and "full". So the effective number of slots that are guaranteed to + // be usable at any time is the block size - 1 times the number of blocks - 1. Solving + // for size and applying a ceiling to the division gives us (after simplifying): + size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1); + largestBlockSize = MAX_BLOCK_SIZE; + Block* lastBlock = nullptr; + for (size_t i = 0; i != initialBlockCount; ++i) { + auto block = make_block(largestBlockSize); + if (block == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#else + abort(); +#endif + } + if (firstBlock == nullptr) { + firstBlock = block; + } else { + lastBlock->next = block; + } + lastBlock = block; + block->next = firstBlock; + } + } else { + firstBlock = make_block(largestBlockSize); + if (firstBlock == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#else + abort(); +#endif + } + firstBlock->next = firstBlock; + } + frontBlock = firstBlock; + tailBlock = firstBlock; + + // Make sure the reader/writer threads will have the initialized memory setup above: + fence(memory_order_sync); + } + + // Note: The queue should not be accessed concurrently while it's + // being moved. It's up to the user to synchronize this. + AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other) + : frontBlock(other.frontBlock.load()), tailBlock(other.tailBlock.load()), + largestBlockSize(other.largestBlockSize) +#ifndef NDEBUG + , + enqueuing(false), dequeuing(false) +#endif + { + other.largestBlockSize = 32; + Block* b = other.make_block(other.largestBlockSize); + if (b == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#else + abort(); +#endif + } + b->next = b; + other.frontBlock = b; + other.tailBlock = b; + } + + // Note: The queue should not be accessed concurrently while it's + // being moved. It's up to the user to synchronize this. + ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN { + Block* b = frontBlock.load(); + frontBlock = other.frontBlock.load(); + other.frontBlock = b; + b = tailBlock.load(); + tailBlock = other.tailBlock.load(); + other.tailBlock = b; + std::swap(largestBlockSize, other.largestBlockSize); + return *this; + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + AE_NO_TSAN ~ReaderWriterQueue() { + // Make sure we get the latest version of all variables from other CPUs: + fence(memory_order_sync); + + // Destroy any remaining objects in queue and free memory + Block* frontBlock_ = frontBlock; + Block* block = frontBlock_; + do { + Block* nextBlock = block->next; + size_t blockFront = block->front; + size_t blockTail = block->tail; + + for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) { + auto element = reinterpret_cast<T*>(block->data + i * sizeof(T)); + element->~T(); + (void)element; + } + + auto rawBlock = block->rawThis; + block->~Block(); + std::free(rawBlock); + block = nextBlock; + } while (block != frontBlock_); + } + + // Enqueues a copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN { + return inner_enqueue<CannotAlloc>(element); + } + + // Enqueues a moved copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN { + return inner_enqueue<CannotAlloc>(std::forward<T>(element)); + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). + template <typename... Args> + AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN { + return inner_enqueue<CannotAlloc>(std::forward<Args>(args)...); + } +#endif + + // Enqueues a copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN { + return inner_enqueue<CanAlloc>(element); + } + + // Enqueues a moved copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN { + return inner_enqueue<CanAlloc>(std::forward<T>(element)); + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like enqueue() but with emplace semantics (i.e. construct-in-place). + template <typename... Args> + AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN { + return inner_enqueue<CanAlloc>(std::forward<Args>(args)...); + } +#endif + + // Attempts to dequeue an element; if the queue is empty, + // returns false instead. If the queue has at least one element, + // moves front to result using operator=, then returns true. + template <typename U> + bool try_dequeue(U& result) AE_NO_TSAN { +#ifndef NDEBUG + ReentrantGuard guard(this->dequeuing); +#endif + + // High-level pseudocode: + // Remember where the tail block is + // If the front block has an element in it, dequeue it + // Else + // If front block was the tail block when we entered the function, return false + // Else advance to next block and dequeue the item there + + // Note that we have to use the value of the tail block from before we check if the front + // block is full or not, in case the front block is empty and then, before we check if the + // tail block is at the front block or not, the producer fills up the front block *and + // moves on*, which would make us skip a filled block. Seems unlikely, but was consistently + // reproducible in practice. + // In order to avoid overhead in the common case, though, we do a double-checked pattern + // where we have the fast path if the front block is not empty, then read the tail block, + // then re-read the front block and check if it's not empty again, then check if the tail + // block has advanced. + + Block* frontBlock_ = frontBlock.load(); + size_t blockTail = frontBlock_->localTail; + size_t blockFront = frontBlock_->front.load(); + + if (blockFront != blockTail || + blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { + fence(memory_order_acquire); + + non_empty_front_block: + // Front block not empty, dequeue from here + auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T)); + result = std::move(*element); + element->~T(); + + blockFront = (blockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = blockFront; + } else if (frontBlock_ != tailBlock.load()) { + fence(memory_order_acquire); + + frontBlock_ = frontBlock.load(); + blockTail = frontBlock_->localTail = frontBlock_->tail.load(); + blockFront = frontBlock_->front.load(); + fence(memory_order_acquire); + + if (blockFront != blockTail) { + // Oh look, the front block isn't empty after all + goto non_empty_front_block; + } + + // Front block is empty but there's another block ahead, advance to it + Block* nextBlock = frontBlock_->next; + // Don't need an acquire fence here since next can only ever be set on the tailBlock, + // and we're not the tailBlock, and we did an acquire earlier after reading tailBlock + // which ensures next is up-to-date on this CPU in case we recently were at tailBlock. + + size_t nextBlockFront = nextBlock->front.load(); + size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); + fence(memory_order_acquire); + + // Since the tailBlock is only ever advanced after being written to, + // we know there's for sure an element to dequeue on it + assert(nextBlockFront != nextBlockTail); + AE_UNUSED(nextBlockTail); + + // We're done with this block, let the producer use it if it needs + fence(memory_order_release); // Expose possibly pending changes to frontBlock->front + // from last dequeue + frontBlock = frontBlock_ = nextBlock; + + compiler_fence(memory_order_release); // Not strictly needed + + auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T)); + + result = std::move(*element); + element->~T(); + + nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = nextBlockFront; + } else { + // No elements in current block and no other block to advance to + return false; + } + + return true; + } + + // Returns a pointer to the front element in the queue (the one that + // would be removed next by a call to `try_dequeue` or `pop`). If the + // queue appears empty at the time the method is called, nullptr is + // returned instead. + // Must be called only from the consumer thread. + T* peek() const AE_NO_TSAN { +#ifndef NDEBUG + ReentrantGuard guard(this->dequeuing); +#endif + // See try_dequeue() for reasoning + + Block* frontBlock_ = frontBlock.load(); + size_t blockTail = frontBlock_->localTail; + size_t blockFront = frontBlock_->front.load(); + + if (blockFront != blockTail || + blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { + fence(memory_order_acquire); + non_empty_front_block: + return reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T)); + } else if (frontBlock_ != tailBlock.load()) { + fence(memory_order_acquire); + frontBlock_ = frontBlock.load(); + blockTail = frontBlock_->localTail = frontBlock_->tail.load(); + blockFront = frontBlock_->front.load(); + fence(memory_order_acquire); + + if (blockFront != blockTail) { + goto non_empty_front_block; + } + + Block* nextBlock = frontBlock_->next; + + size_t nextBlockFront = nextBlock->front.load(); + fence(memory_order_acquire); + + assert(nextBlockFront != nextBlock->tail.load()); + return reinterpret_cast<T*>(nextBlock->data + nextBlockFront * sizeof(T)); + } + + return nullptr; + } + + // Removes the front element from the queue, if any, without returning it. + // Returns true on success, or false if the queue appeared empty at the time + // `pop` was called. + bool pop() AE_NO_TSAN { +#ifndef NDEBUG + ReentrantGuard guard(this->dequeuing); +#endif + // See try_dequeue() for reasoning + + Block* frontBlock_ = frontBlock.load(); + size_t blockTail = frontBlock_->localTail; + size_t blockFront = frontBlock_->front.load(); + + if (blockFront != blockTail || + blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { + fence(memory_order_acquire); + + non_empty_front_block: + auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T)); + element->~T(); + + blockFront = (blockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = blockFront; + } else if (frontBlock_ != tailBlock.load()) { + fence(memory_order_acquire); + frontBlock_ = frontBlock.load(); + blockTail = frontBlock_->localTail = frontBlock_->tail.load(); + blockFront = frontBlock_->front.load(); + fence(memory_order_acquire); + + if (blockFront != blockTail) { + goto non_empty_front_block; + } + + // Front block is empty but there's another block ahead, advance to it + Block* nextBlock = frontBlock_->next; + + size_t nextBlockFront = nextBlock->front.load(); + size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); + fence(memory_order_acquire); + + assert(nextBlockFront != nextBlockTail); + AE_UNUSED(nextBlockTail); + + fence(memory_order_release); + frontBlock = frontBlock_ = nextBlock; + + compiler_fence(memory_order_release); + + auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T)); + element->~T(); + + nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = nextBlockFront; + } else { + // No elements in current block and no other block to advance to + return false; + } + + return true; + } + + // Returns the approximate number of items currently in the queue. + // Safe to call from both the producer and consumer threads. + inline size_t size_approx() const AE_NO_TSAN { + size_t result = 0; + Block* frontBlock_ = frontBlock.load(); + Block* block = frontBlock_; + do { + fence(memory_order_acquire); + size_t blockFront = block->front.load(); + size_t blockTail = block->tail.load(); + result += (blockTail - blockFront) & block->sizeMask; + block = block->next.load(); + } while (block != frontBlock_); + return result; + } + + // Returns the total number of items that could be enqueued without incurring + // an allocation when this queue is empty. + // Safe to call from both the producer and consumer threads. + // + // NOTE: The actual capacity during usage may be different depending on the consumer. + // If the consumer is removing elements concurrently, the producer cannot add to + // the block the consumer is removing from until it's completely empty, except in + // the case where the producer was writing to the same block the consumer was + // reading from the whole time. + inline size_t max_capacity() const { + size_t result = 0; + Block* frontBlock_ = frontBlock.load(); + Block* block = frontBlock_; + do { + fence(memory_order_acquire); + result += block->sizeMask; + block = block->next.load(); + } while (block != frontBlock_); + return result; + } + +private: + enum AllocationMode { CanAlloc, CannotAlloc }; + +#if MOODYCAMEL_HAS_EMPLACE + template <AllocationMode canAlloc, typename... Args> + bool inner_enqueue(Args&&... args) AE_NO_TSAN +#else + template <AllocationMode canAlloc, typename U> + bool inner_enqueue(U&& element) AE_NO_TSAN +#endif + { +#ifndef NDEBUG + ReentrantGuard guard(this->enqueuing); +#endif + + // High-level pseudocode (assuming we're allowed to alloc a new block): + // If room in tail block, add to tail + // Else check next block + // If next block is not the head block, enqueue on next block + // Else create a new block and enqueue there + // Advance tail to the block we just enqueued to + + Block* tailBlock_ = tailBlock.load(); + size_t blockFront = tailBlock_->localFront; + size_t blockTail = tailBlock_->tail.load(); + + size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask; + if (nextBlockTail != blockFront || + nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) { + fence(memory_order_acquire); + // This block has room for at least one more element + char* location = tailBlock_->data + blockTail * sizeof(T); +#if MOODYCAMEL_HAS_EMPLACE + new (location) T(std::forward<Args>(args)...); +#else + new (location) T(std::forward<U>(element)); +#endif + + fence(memory_order_release); + tailBlock_->tail = nextBlockTail; + } else { + fence(memory_order_acquire); + if (tailBlock_->next.load() != frontBlock) { + // Note that the reason we can't advance to the frontBlock and start adding new + // entries there is because if we did, then dequeue would stay in that block, + // eventually reading the new values, instead of advancing to the next full block + // (whose values were enqueued first and so should be consumed first). + + fence(memory_order_acquire); // Ensure we get latest writes if we got the latest + // frontBlock + + // tailBlock is full, but there's a free block ahead, use it + Block* tailBlockNext = tailBlock_->next.load(); + size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load(); + nextBlockTail = tailBlockNext->tail.load(); + fence(memory_order_acquire); + + // This block must be empty since it's not the head block and we + // go through the blocks in a circle + assert(nextBlockFront == nextBlockTail); + tailBlockNext->localFront = nextBlockFront; + + char* location = tailBlockNext->data + nextBlockTail * sizeof(T); +#if MOODYCAMEL_HAS_EMPLACE + new (location) T(std::forward<Args>(args)...); +#else + new (location) T(std::forward<U>(element)); +#endif + + tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask; + + fence(memory_order_release); + tailBlock = tailBlockNext; + } else if (canAlloc == CanAlloc) { + // tailBlock is full and there's no free block ahead; create a new block + auto newBlockSize = + largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2; + auto newBlock = make_block(newBlockSize); + if (newBlock == nullptr) { + // Could not allocate a block! + return false; + } + largestBlockSize = newBlockSize; + +#if MOODYCAMEL_HAS_EMPLACE + new (newBlock->data) T(std::forward<Args>(args)...); +#else + new (newBlock->data) T(std::forward<U>(element)); +#endif + assert(newBlock->front == 0); + newBlock->tail = newBlock->localTail = 1; + + newBlock->next = tailBlock_->next.load(); + tailBlock_->next = newBlock; + + // Might be possible for the dequeue thread to see the new tailBlock->next + // *without* seeing the new tailBlock value, but this is OK since it can't + // advance to the next block until tailBlock is set anyway (because the only + // case where it could try to read the next is if it's already at the tailBlock, + // and it won't advance past tailBlock in any circumstance). + + fence(memory_order_release); + tailBlock = newBlock; + } else if (canAlloc == CannotAlloc) { + // Would have had to allocate a new block to enqueue, but not allowed + return false; + } else { + assert(false && "Should be unreachable code"); + return false; + } + } + + return true; + } + + // Disable copying + ReaderWriterQueue(ReaderWriterQueue const&) {} + + // Disable assignment + ReaderWriterQueue& operator=(ReaderWriterQueue const&) {} + + AE_FORCEINLINE static size_t ceilToPow2(size_t x) { + // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (size_t i = 1; i < sizeof(size_t); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template <typename U> + static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN { + const std::size_t alignment = std::alignment_of<U>::value; + return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment; + } + +private: +#ifndef NDEBUG + struct ReentrantGuard { + AE_NO_TSAN ReentrantGuard(weak_atomic<bool>& _inSection) : inSection(_inSection) { + assert(!inSection && + "Concurrent (or re-entrant) enqueue or dequeue operation detected (only one " + "thread at a time may hold the producer or consumer role)"); + inSection = true; + } + + AE_NO_TSAN ~ReentrantGuard() { + inSection = false; + } + + private: + ReentrantGuard& operator=(ReentrantGuard const&); + + private: + weak_atomic<bool>& inSection; + }; +#endif + + struct Block { + // Avoid false-sharing by putting highly contended variables on their own cache lines + weak_atomic<size_t> front; // (Atomic) Elements are read from here + size_t localTail; // An uncontended shadow copy of tail, owned by the consumer + + char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) - + sizeof(size_t)]; + weak_atomic<size_t> tail; // (Atomic) Elements are enqueued here + size_t localFront; + + char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) - + sizeof(size_t)]; // next isn't very contended, but we don't want it on + // the same cache line as tail (which is) + weak_atomic<Block*> next; // (Atomic) + + char* data; // Contents (on heap) are aligned to T's alignment + + const size_t sizeMask; + + // size must be a power of two (and greater than 0) + AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data) + : front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data), + sizeMask(_size - 1), rawThis(_rawThis) {} + + private: + // C4512 - Assignment operator could not be generated + Block& operator=(Block const&); + + public: + char* rawThis; + }; + + static Block* make_block(size_t capacity) AE_NO_TSAN { + // Allocate enough memory for the block itself, as well as all the elements it will contain + auto size = sizeof(Block) + std::alignment_of<Block>::value - 1; + size += sizeof(T) * capacity + std::alignment_of<T>::value - 1; + auto newBlockRaw = static_cast<char*>(std::malloc(size)); + if (newBlockRaw == nullptr) { + return nullptr; + } + + auto newBlockAligned = align_for<Block>(newBlockRaw); + auto newBlockData = align_for<T>(newBlockAligned + sizeof(Block)); + return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData); + } + +private: + weak_atomic<Block*> frontBlock; // (Atomic) Elements are dequeued from this block + + char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<Block*>)]; + weak_atomic<Block*> tailBlock; // (Atomic) Elements are enqueued to this block + + size_t largestBlockSize; + +#ifndef NDEBUG + weak_atomic<bool> enqueuing; + mutable weak_atomic<bool> dequeuing; +#endif +}; + +// Like ReaderWriterQueue, but also providees blocking operations +template <typename T, size_t MAX_BLOCK_SIZE = 512> +class BlockingReaderWriterQueue { +private: + typedef ::Common::ReaderWriterQueue<T, MAX_BLOCK_SIZE> ReaderWriterQueue; + +public: + explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN + : inner(size), + sema(new spsc_sema::LightweightSemaphore()) {} + + BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN + : inner(std::move(other.inner)), + sema(std::move(other.sema)) {} + + BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN { + std::swap(sema, other.sema); + std::swap(inner, other.inner); + return *this; + } + + // Enqueues a copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN { + if (inner.try_enqueue(element)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a moved copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN { + if (inner.try_enqueue(std::forward<T>(element))) { + sema->signal(); + return true; + } + return false; + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). + template <typename... Args> + AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN { + if (inner.try_emplace(std::forward<Args>(args)...)) { + sema->signal(); + return true; + } + return false; + } +#endif + + // Enqueues a copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN { + if (inner.enqueue(element)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a moved copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN { + if (inner.enqueue(std::forward<T>(element))) { + sema->signal(); + return true; + } + return false; + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like enqueue() but with emplace semantics (i.e. construct-in-place). + template <typename... Args> + AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN { + if (inner.emplace(std::forward<Args>(args)...)) { + sema->signal(); + return true; + } + return false; + } +#endif + + // Attempts to dequeue an element; if the queue is empty, + // returns false instead. If the queue has at least one element, + // moves front to result using operator=, then returns true. + template <typename U> + bool try_dequeue(U& result) AE_NO_TSAN { + if (sema->tryWait()) { + bool success = inner.try_dequeue(result); + assert(success); + AE_UNUSED(success); + return true; + } + return false; + } + + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available, then dequeues it. + template <typename U> + void wait_dequeue(U& result) AE_NO_TSAN { + while (!sema->wait()) + ; + bool success = inner.try_dequeue(result); + AE_UNUSED(result); + assert(success); + AE_UNUSED(success); + } + + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available up to the specified timeout, + // then dequeues it and returns true, or returns false if the timeout + // expires before an element can be dequeued. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + template <typename U> + bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN { + if (!sema->wait(timeout_usecs)) { + return false; + } + bool success = inner.try_dequeue(result); + AE_UNUSED(result); + assert(success); + AE_UNUSED(success); + return true; + } + +#if __cplusplus > 199711L || _MSC_VER >= 1700 + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available up to the specified timeout, + // then dequeues it and returns true, or returns false if the timeout + // expires before an element can be dequeued. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + template <typename U, typename Rep, typename Period> + inline bool wait_dequeue_timed(U& result, + std::chrono::duration<Rep, Period> const& timeout) AE_NO_TSAN { + return wait_dequeue_timed( + result, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count()); + } +#endif + + // Returns a pointer to the front element in the queue (the one that + // would be removed next by a call to `try_dequeue` or `pop`). If the + // queue appears empty at the time the method is called, nullptr is + // returned instead. + // Must be called only from the consumer thread. + AE_FORCEINLINE T* peek() const AE_NO_TSAN { + return inner.peek(); + } + + // Removes the front element from the queue, if any, without returning it. + // Returns true on success, or false if the queue appeared empty at the time + // `pop` was called. + AE_FORCEINLINE bool pop() AE_NO_TSAN { + if (sema->tryWait()) { + bool result = inner.pop(); + assert(result); + AE_UNUSED(result); + return true; + } + return false; + } + + // Returns the approximate number of items currently in the queue. + // Safe to call from both the producer and consumer threads. + AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN { + return sema->availableApprox(); + } + + // Returns the total number of items that could be enqueued without incurring + // an allocation when this queue is empty. + // Safe to call from both the producer and consumer threads. + // + // NOTE: The actual capacity during usage may be different depending on the consumer. + // If the consumer is removing elements concurrently, the producer cannot add to + // the block the consumer is removing from until it's completely empty, except in + // the case where the producer was writing to the same block the consumer was + // reading from the whole time. + AE_FORCEINLINE size_t max_capacity() const { + return inner.max_capacity(); + } + +private: + // Disable copying & assignment + BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) {} + BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) {} + +private: + ReaderWriterQueue inner; + std::unique_ptr<spsc_sema::LightweightSemaphore> sema; +}; + +} // namespace Common + +#ifdef AE_VCPP +#pragma warning(pop) +#endif diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 751549583..1c7b6dfae 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -62,7 +62,8 @@ void LogSettings() { log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue()); log_setting("Audio_OutputEngine", values.sink_id.GetValue()); - log_setting("Audio_OutputDevice", values.audio_device_id.GetValue()); + log_setting("Audio_OutputDevice", values.audio_output_device_id.GetValue()); + log_setting("Audio_InputDevice", values.audio_input_device_id.GetValue()); log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue()); log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir)); log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir)); @@ -185,7 +186,6 @@ void RestoreGlobalState(bool is_powered_on) { values.max_anisotropy.SetGlobal(true); values.use_speed_limit.SetGlobal(true); values.speed_limit.SetGlobal(true); - values.fps_cap.SetGlobal(true); values.use_disk_shader_cache.SetGlobal(true); values.gpu_accuracy.SetGlobal(true); values.use_asynchronous_gpu_emulation.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 3583a2e70..1079cf8cb 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -106,7 +106,7 @@ struct ResolutionScalingInfo { * configurations. Specifying a default value and label is required. A minimum and maximum range can * be specified for sanitization. */ -template <typename Type> +template <typename Type, bool ranged = false> class Setting { protected: Setting() = default; @@ -126,8 +126,8 @@ public: * @param default_val Intial value of the setting, and default value of the setting * @param name Label for the setting */ - explicit Setting(const Type& default_val, const std::string& name) - : value{default_val}, default_value{default_val}, ranged{false}, label{name} {} + explicit Setting(const Type& default_val, const std::string& name) requires(!ranged) + : value{default_val}, default_value{default_val}, label{name} {} virtual ~Setting() = default; /** @@ -139,9 +139,9 @@ public: * @param name Label for the setting */ explicit Setting(const Type& default_val, const Type& min_val, const Type& max_val, - const std::string& name) - : value{default_val}, default_value{default_val}, maximum{max_val}, minimum{min_val}, - ranged{true}, label{name} {} + const std::string& name) requires(ranged) + : value{default_val}, + default_value{default_val}, maximum{max_val}, minimum{min_val}, label{name} {} /** * Returns a reference to the setting's value. @@ -158,7 +158,7 @@ public: * @param val The desired value */ virtual void SetValue(const Type& val) { - Type temp{(ranged) ? std::clamp(val, minimum, maximum) : val}; + Type temp{ranged ? std::clamp(val, minimum, maximum) : val}; std::swap(value, temp); } @@ -188,7 +188,7 @@ public: * @returns A reference to the setting */ virtual const Type& operator=(const Type& val) { - Type temp{(ranged) ? std::clamp(val, minimum, maximum) : val}; + Type temp{ranged ? std::clamp(val, minimum, maximum) : val}; std::swap(value, temp); return value; } @@ -207,7 +207,6 @@ protected: const Type default_value{}; ///< The default value const Type maximum{}; ///< Maximum allowed value of the setting const Type minimum{}; ///< Minimum allowed value of the setting - const bool ranged; ///< The setting has sanitization ranges const std::string label{}; ///< The setting's label }; @@ -219,8 +218,8 @@ protected: * * By default, the global setting is used. */ -template <typename Type> -class SwitchableSetting : virtual public Setting<Type> { +template <typename Type, bool ranged = false> +class SwitchableSetting : virtual public Setting<Type, ranged> { public: /** * Sets a default value, label, and setting value. @@ -228,7 +227,7 @@ public: * @param default_val Intial value of the setting, and default value of the setting * @param name Label for the setting */ - explicit SwitchableSetting(const Type& default_val, const std::string& name) + explicit SwitchableSetting(const Type& default_val, const std::string& name) requires(!ranged) : Setting<Type>{default_val, name} {} virtual ~SwitchableSetting() = default; @@ -241,8 +240,8 @@ public: * @param name Label for the setting */ explicit SwitchableSetting(const Type& default_val, const Type& min_val, const Type& max_val, - const std::string& name) - : Setting<Type>{default_val, min_val, max_val, name} {} + const std::string& name) requires(ranged) + : Setting<Type, true>{default_val, min_val, max_val, name} {} /** * Tells this setting to represent either the global or custom setting when other member @@ -290,7 +289,7 @@ public: * @param val The new value */ void SetValue(const Type& val) override { - Type temp{(this->ranged) ? std::clamp(val, this->minimum, this->maximum) : val}; + Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val}; if (use_global) { std::swap(this->value, temp); } else { @@ -306,7 +305,7 @@ public: * @returns A reference to the current setting value */ const Type& operator=(const Type& val) override { - Type temp{(this->ranged) ? std::clamp(val, this->minimum, this->maximum) : val}; + Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val}; if (use_global) { std::swap(this->value, temp); return this->value; @@ -371,18 +370,20 @@ struct TouchFromButtonMap { struct Values { // Audio - Setting<std::string> audio_device_id{"auto", "output_device"}; Setting<std::string> sink_id{"auto", "output_engine"}; + Setting<std::string> audio_output_device_id{"auto", "output_device"}; + Setting<std::string> audio_input_device_id{"auto", "input_device"}; Setting<bool> audio_muted{false, "audio_muted"}; - SwitchableSetting<u8> volume{100, 0, 100, "volume"}; + SwitchableSetting<u8, true> volume{100, 0, 100, "volume"}; + Setting<bool> dump_audio_commands{false, "dump_audio_commands"}; // Core SwitchableSetting<bool> use_multi_core{true, "use_multi_core"}; SwitchableSetting<bool> use_extended_memory_layout{false, "use_extended_memory_layout"}; // Cpu - SwitchableSetting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto, - CPUAccuracy::Paranoid, "cpu_accuracy"}; + SwitchableSetting<CPUAccuracy, true> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto, + CPUAccuracy::Paranoid, "cpu_accuracy"}; // TODO: remove cpu_accuracy_first_time, migration setting added 8 July 2021 Setting<bool> cpu_accuracy_first_time{true, "cpu_accuracy_first_time"}; Setting<bool> cpu_debug_mode{false, "cpu_debug_mode"}; @@ -409,7 +410,7 @@ struct Values { true, "cpuopt_unsafe_ignore_global_monitor"}; // Renderer - SwitchableSetting<RendererBackend> renderer_backend{ + SwitchableSetting<RendererBackend, true> renderer_backend{ RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"}; Setting<bool> renderer_debug{false, "debug"}; Setting<bool> renderer_shader_feedback{false, "shader_feedback"}; @@ -423,28 +424,26 @@ struct Values { SwitchableSetting<AntiAliasing> anti_aliasing{AntiAliasing::None, "anti_aliasing"}; // *nix platforms may have issues with the borderless windowed fullscreen mode. // Default to exclusive fullscreen on these platforms for now. - SwitchableSetting<FullscreenMode> fullscreen_mode{ + SwitchableSetting<FullscreenMode, true> fullscreen_mode{ #ifdef _WIN32 FullscreenMode::Borderless, #else FullscreenMode::Exclusive, #endif FullscreenMode::Borderless, FullscreenMode::Exclusive, "fullscreen_mode"}; - SwitchableSetting<int> aspect_ratio{0, 0, 3, "aspect_ratio"}; - SwitchableSetting<int> max_anisotropy{0, 0, 5, "max_anisotropy"}; + SwitchableSetting<int, true> aspect_ratio{0, 0, 3, "aspect_ratio"}; + SwitchableSetting<int, true> max_anisotropy{0, 0, 5, "max_anisotropy"}; SwitchableSetting<bool> use_speed_limit{true, "use_speed_limit"}; - SwitchableSetting<u16> speed_limit{100, 0, 9999, "speed_limit"}; + SwitchableSetting<u16, true> speed_limit{100, 0, 9999, "speed_limit"}; SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"}; - SwitchableSetting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal, - GPUAccuracy::Extreme, "gpu_accuracy"}; + SwitchableSetting<GPUAccuracy, true> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal, + GPUAccuracy::Extreme, "gpu_accuracy"}; SwitchableSetting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"}; SwitchableSetting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"}; SwitchableSetting<bool> accelerate_astc{true, "accelerate_astc"}; SwitchableSetting<bool> use_vsync{true, "use_vsync"}; - SwitchableSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"}; - Setting<bool> disable_fps_limit{false, "disable_fps_limit"}; - SwitchableSetting<ShaderBackend> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL, - ShaderBackend::SPIRV, "shader_backend"}; + SwitchableSetting<ShaderBackend, true> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL, + ShaderBackend::SPIRV, "shader_backend"}; SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"}; SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"}; @@ -460,10 +459,10 @@ struct Values { s64 custom_rtc_differential; Setting<s32> current_user{0, "current_user"}; - SwitchableSetting<s32> language_index{1, 0, 17, "language_index"}; - SwitchableSetting<s32> region_index{1, 0, 6, "region_index"}; - SwitchableSetting<s32> time_zone_index{0, 0, 45, "time_zone_index"}; - SwitchableSetting<s32> sound_index{1, 0, 2, "sound_index"}; + SwitchableSetting<s32, true> language_index{1, 0, 17, "language_index"}; + SwitchableSetting<s32, true> region_index{1, 0, 6, "region_index"}; + SwitchableSetting<s32, true> time_zone_index{0, 0, 45, "time_zone_index"}; + SwitchableSetting<s32, true> sound_index{1, 0, 2, "sound_index"}; // Controls InputSetting<std::array<PlayerInput, 10>> players; @@ -485,7 +484,7 @@ struct Values { Setting<bool> tas_loop{false, "tas_loop"}; Setting<bool> mouse_panning{false, "mouse_panning"}; - Setting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; + Setting<u8, true> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; Setting<bool> mouse_enabled{false, "mouse_enabled"}; Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"}; @@ -504,6 +503,9 @@ struct Values { Setting<bool> enable_ring_controller{true, "enable_ring_controller"}; RingconRaw ringcon_analogs; + Setting<bool> enable_ir_sensor{false, "enable_ir_sensor"}; + Setting<std::string> ir_sensor_device{"auto", "ir_sensor_device"}; + // Data Storage Setting<bool> use_virtual_sd{true, "use_virtual_sd"}; Setting<bool> gamecard_inserted{false, "gamecard_inserted"}; diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp index b4fb3a59f..ae07f2811 100644 --- a/src/common/wall_clock.cpp +++ b/src/common/wall_clock.cpp @@ -67,7 +67,7 @@ std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency, const auto& caps = GetCPUCaps(); u64 rtsc_frequency = 0; if (caps.invariant_tsc) { - rtsc_frequency = EstimateRDTSCFrequency(); + rtsc_frequency = caps.tsc_frequency ? caps.tsc_frequency : EstimateRDTSCFrequency(); } // Fallback to StandardWallClock if the hardware TSC does not have the precision greater than: diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 322aa1f08..1a27532d4 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -161,6 +161,22 @@ static CPUCaps Detect() { caps.invariant_tsc = Common::Bit<8>(cpu_id[3]); } + if (max_std_fn >= 0x15) { + __cpuid(cpu_id, 0x15); + caps.tsc_crystal_ratio_denominator = cpu_id[0]; + caps.tsc_crystal_ratio_numerator = cpu_id[1]; + caps.crystal_frequency = cpu_id[2]; + // Some CPU models might not return a crystal frequency. + // The CPU model can be detected to use the values from turbostat + // https://github.com/torvalds/linux/blob/master/tools/power/x86/turbostat/turbostat.c#L5569 + // but it's easier to just estimate the TSC tick rate for these cases. + if (caps.tsc_crystal_ratio_denominator) { + caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) * + caps.tsc_crystal_ratio_numerator / + caps.tsc_crystal_ratio_denominator; + } + } + if (max_std_fn >= 0x16) { __cpuid(cpu_id, 0x16); caps.base_frequency = cpu_id[0]; diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h index 9bdc9dbfa..6830f3795 100644 --- a/src/common/x64/cpu_detect.h +++ b/src/common/x64/cpu_detect.h @@ -30,6 +30,11 @@ struct CPUCaps { u32 max_frequency; u32 bus_frequency; + u32 tsc_crystal_ratio_denominator; + u32 tsc_crystal_ratio_numerator; + u32 crystal_frequency; + u64 tsc_frequency; // Derived from the above three values + bool sse : 1; bool sse2 : 1; bool sse3 : 1; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d9357138f..32cc2f392 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -158,6 +158,7 @@ add_library(core STATIC hid/input_converter.h hid/input_interpreter.cpp hid/input_interpreter.h + hid/irs_types.h hid/motion_input.cpp hid/motion_input.h hle/api_version.h @@ -477,6 +478,20 @@ add_library(core STATIC hle/service/hid/hidbus/starlink.h hle/service/hid/hidbus/stubbed.cpp hle/service/hid/hidbus/stubbed.h + hle/service/hid/irsensor/clustering_processor.cpp + hle/service/hid/irsensor/clustering_processor.h + hle/service/hid/irsensor/image_transfer_processor.cpp + hle/service/hid/irsensor/image_transfer_processor.h + hle/service/hid/irsensor/ir_led_processor.cpp + hle/service/hid/irsensor/ir_led_processor.h + hle/service/hid/irsensor/moment_processor.cpp + hle/service/hid/irsensor/moment_processor.h + hle/service/hid/irsensor/pointing_processor.cpp + hle/service/hid/irsensor/pointing_processor.h + hle/service/hid/irsensor/processor_base.cpp + hle/service/hid/irsensor/processor_base.h + hle/service/hid/irsensor/tera_plugin_processor.cpp + hle/service/hid/irsensor/tera_plugin_processor.h hle/service/jit/jit_context.cpp hle/service/jit/jit_context.h hle/service/jit/jit.cpp @@ -605,6 +620,10 @@ add_library(core STATIC hle/service/psc/psc.h hle/service/ptm/psm.cpp hle/service/ptm/psm.h + hle/service/ptm/ptm.cpp + hle/service/ptm/ptm.h + hle/service/ptm/ts.cpp + hle/service/ptm/ts.h hle/service/kernel_helpers.cpp hle/service/kernel_helpers.h hle/service/service.cpp diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index cef79b245..953d96439 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -147,7 +147,6 @@ void ARM_Interface::Run() { // Notify the debugger and go to sleep if a watchpoint was hit. if (Has(hr, watchpoint)) { - RewindBreakpointInstruction(); if (system.DebuggerEnabled()) { system.GetDebugger().NotifyThreadWatchpoint(current_thread, *HaltedWatchpoint()); } @@ -155,9 +154,10 @@ void ARM_Interface::Run() { break; } - // Handle syscalls and scheduling (this may change the current thread) + // Handle syscalls and scheduling (this may change the current thread/core) if (Has(hr, svc_call)) { Kernel::Svc::Call(system, GetSvcNumber()); + break; } if (Has(hr, break_loop) || !uses_wall_clock) { break; diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index 8a066ed91..c092db9ff 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -203,7 +203,7 @@ public: static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2; static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3; static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4; - static constexpr Dynarmic::HaltReason watchpoint = Dynarmic::HaltReason::UserDefined5; + static constexpr Dynarmic::HaltReason watchpoint = Dynarmic::HaltReason::MemoryAbort; static constexpr Dynarmic::HaltReason no_execute = Dynarmic::HaltReason::UserDefined6; protected: diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index 1be5fe1c1..b8d2ce224 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -52,7 +52,7 @@ public: if (!memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) { return std::nullopt; } - return MemoryRead32(vaddr); + return memory.Read32(vaddr); } void MemoryWrite8(u32 vaddr, u8 value) override { @@ -97,7 +97,7 @@ public: parent.LogBacktrace(); LOG_ERROR(Core_ARM, "Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc, - num_instructions, MemoryRead32(pc)); + num_instructions, memory.Read32(pc)); } void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { @@ -115,7 +115,7 @@ public: parent.LogBacktrace(); LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})", - exception, pc, MemoryRead32(pc), parent.IsInThumbMode()); + exception, pc, memory.Read32(pc), parent.IsInThumbMode()); } } @@ -155,7 +155,7 @@ public: const auto match{parent.MatchingWatchpoint(addr, size, type)}; if (match) { parent.halted_watchpoint = match; - ReturnException(parent.jit.load()->Regs()[15], ARM_Interface::watchpoint); + parent.jit.load()->HaltExecution(ARM_Interface::watchpoint); return false; } @@ -204,7 +204,6 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* // Code cache size config.code_cache_size = 512_MiB; - config.far_code_offset = 400_MiB; // Allow memory fault handling to work if (system.DebuggerEnabled()) { @@ -215,7 +214,6 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* if (!page_table) { // Don't waste too much memory on null_jit config.code_cache_size = 8_MiB; - config.far_code_offset = 4_MiB; } // Safe optimizations diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index c437f24b8..1a4d37cbc 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -56,7 +56,7 @@ public: if (!memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) { return std::nullopt; } - return MemoryRead32(vaddr); + return memory.Read32(vaddr); } void MemoryWrite8(u64 vaddr, u8 value) override { @@ -111,7 +111,7 @@ public: parent.LogBacktrace(); LOG_ERROR(Core_ARM, "Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc, - num_instructions, MemoryRead32(pc)); + num_instructions, memory.Read32(pc)); } void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, @@ -156,7 +156,7 @@ public: parent.LogBacktrace(); LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", - static_cast<std::size_t>(exception), pc, MemoryRead32(pc)); + static_cast<std::size_t>(exception), pc, memory.Read32(pc)); } } @@ -198,7 +198,7 @@ public: const auto match{parent.MatchingWatchpoint(addr, size, type)}; if (match) { parent.halted_watchpoint = match; - ReturnException(parent.jit.load()->GetPC(), ARM_Interface::watchpoint); + parent.jit.load()->HaltExecution(ARM_Interface::watchpoint); return false; } @@ -264,7 +264,6 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* // Code cache size config.code_cache_size = 512_MiB; - config.far_code_offset = 400_MiB; // Allow memory fault handling to work if (system.DebuggerEnabled()) { @@ -275,7 +274,6 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* if (!page_table) { // Don't waste too much memory on null_jit config.code_cache_size = 8_MiB; - config.far_code_offset = 4_MiB; } // Safe optimizations diff --git a/src/core/core.cpp b/src/core/core.cpp index 7723d9782..0ede0d85c 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -8,6 +8,7 @@ #include <memory> #include <utility> +#include "audio_core/audio_core.h" #include "common/fs/fs.h" #include "common/logging/log.h" #include "common/microprofile.h" @@ -140,6 +141,8 @@ struct System::Impl { core_timing.SyncPause(false); is_paused = false; + audio_core->PauseSinks(false); + return status; } @@ -147,6 +150,8 @@ struct System::Impl { std::unique_lock<std::mutex> lk(suspend_guard); status = SystemResultStatus::Success; + audio_core->PauseSinks(true); + core_timing.SyncPause(true); kernel.Suspend(true); is_paused = true; @@ -154,6 +159,11 @@ struct System::Impl { return status; } + bool IsPaused() const { + std::unique_lock lk(suspend_guard); + return is_paused; + } + std::unique_lock<std::mutex> StallProcesses() { std::unique_lock<std::mutex> lk(suspend_guard); kernel.Suspend(true); @@ -214,6 +224,8 @@ struct System::Impl { return SystemResultStatus::ErrorVideoCore; } + audio_core = std::make_unique<AudioCore::AudioCore>(system); + service_manager = std::make_shared<Service::SM::ServiceManager>(kernel); services = std::make_unique<Service::Services>(service_manager, system); interrupt_manager = std::make_unique<Hardware::InterruptManager>(system); @@ -290,7 +302,7 @@ struct System::Impl { if (Settings::values.gamecard_current_game) { fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath)); } else if (!Settings::values.gamecard_path.GetValue().empty()) { - const auto gamecard_path = Settings::values.gamecard_path.GetValue(); + const auto& gamecard_path = Settings::values.gamecard_path.GetValue(); fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, gamecard_path)); } } @@ -308,6 +320,8 @@ struct System::Impl { } void Shutdown() { + SetShuttingDown(true); + // Log last frame performance stats if game was loded if (perf_stats) { const auto perf_results = GetAndResetPerfStats(); @@ -333,14 +347,15 @@ struct System::Impl { kernel.ShutdownCores(); cpu_manager.Shutdown(); debugger.reset(); + kernel.CloseServices(); services.reset(); service_manager.reset(); cheat_engine.reset(); telemetry_session.reset(); - cpu_manager.Shutdown(); time_manager.Shutdown(); core_timing.Shutdown(); app_loader.reset(); + audio_core.reset(); gpu_core.reset(); perf_stats.reset(); kernel.Shutdown(); @@ -350,6 +365,14 @@ struct System::Impl { LOG_DEBUG(Core, "Shutdown OK"); } + bool IsShuttingDown() const { + return is_shutting_down; + } + + void SetShuttingDown(bool shutting_down) { + is_shutting_down = shutting_down; + } + Loader::ResultStatus GetGameName(std::string& out) const { if (app_loader == nullptr) return Loader::ResultStatus::ErrorNotInitialized; @@ -392,8 +415,9 @@ struct System::Impl { return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs()); } - std::mutex suspend_guard; + mutable std::mutex suspend_guard; bool is_paused{}; + std::atomic<bool> is_shutting_down{}; Timing::CoreTiming core_timing; Kernel::KernelCore kernel; @@ -407,6 +431,7 @@ struct System::Impl { std::unique_ptr<Tegra::GPU> gpu_core; std::unique_ptr<Hardware::InterruptManager> interrupt_manager; std::unique_ptr<Core::DeviceMemory> device_memory; + std::unique_ptr<AudioCore::AudioCore> audio_core; Core::Memory::Memory memory; Core::HID::HIDCore hid_core; CpuManager cpu_manager; @@ -479,6 +504,10 @@ SystemResultStatus System::Pause() { return impl->Pause(); } +bool System::IsPaused() const { + return impl->IsPaused(); +} + void System::InvalidateCpuInstructionCaches() { impl->kernel.InvalidateAllInstructionCaches(); } @@ -491,6 +520,14 @@ void System::Shutdown() { impl->Shutdown(); } +bool System::IsShuttingDown() const { + return impl->IsShuttingDown(); +} + +void System::SetShuttingDown(bool shutting_down) { + impl->SetShuttingDown(shutting_down); +} + void System::DetachDebugger() { if (impl->debugger) { impl->debugger->NotifyShutdown(); @@ -640,6 +677,14 @@ const HID::HIDCore& System::HIDCore() const { return impl->hid_core; } +AudioCore::AudioCore& System::AudioCore() { + return *impl->audio_core; +} + +const AudioCore::AudioCore& System::AudioCore() const { + return *impl->audio_core; +} + Timing::CoreTiming& System::CoreTiming() { return impl->core_timing; } diff --git a/src/core/core.h b/src/core/core.h index 60efe4410..a49d1214b 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -81,6 +81,10 @@ namespace VideoCore { class RendererBase; } // namespace VideoCore +namespace AudioCore { +class AudioCore; +} // namespace AudioCore + namespace Core::Timing { class CoreTiming; } @@ -148,6 +152,9 @@ public: */ [[nodiscard]] SystemResultStatus Pause(); + /// Check if the core is currently paused. + [[nodiscard]] bool IsPaused() const; + /** * Invalidate the CPU instruction caches * This function should only be used by GDB Stub to support breakpoints, memory updates and @@ -160,6 +167,12 @@ public: /// Shutdown the emulated system. void Shutdown(); + /// Check if the core is shutting down. + [[nodiscard]] bool IsShuttingDown() const; + + /// Set the shutting down state. + void SetShuttingDown(bool shutting_down); + /// Forcibly detach the debugger if it is running. void DetachDebugger(); @@ -250,6 +263,12 @@ public: /// Gets an immutable reference to the renderer. [[nodiscard]] const VideoCore::RendererBase& Renderer() const; + /// Gets a mutable reference to the audio interface + [[nodiscard]] AudioCore::AudioCore& AudioCore(); + + /// Gets an immutable reference to the audio interface. + [[nodiscard]] const AudioCore::AudioCore& AudioCore() const; + /// Gets the global scheduler [[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext(); diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 37d3d83b9..9b1565ae1 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -8,6 +8,7 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/cpu_manager.h" +#include "core/hle/kernel/k_interrupt_manager.h" #include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" @@ -49,14 +50,6 @@ void CpuManager::GuestThreadFunction() { } } -void CpuManager::GuestRewindFunction() { - if (is_multicore) { - MultiCoreRunGuestLoop(); - } else { - SingleCoreRunGuestLoop(); - } -} - void CpuManager::IdleThreadFunction() { if (is_multicore) { MultiCoreRunIdleThread(); @@ -69,21 +62,21 @@ void CpuManager::ShutdownThreadFunction() { ShutdownThread(); } +void CpuManager::HandleInterrupt() { + auto& kernel = system.Kernel(); + auto core_index = kernel.CurrentPhysicalCoreIndex(); + + Kernel::KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_index)); +} + /////////////////////////////////////////////////////////////////////////////// /// MultiCore /// /////////////////////////////////////////////////////////////////////////////// void CpuManager::MultiCoreRunGuestThread() { + // Similar to UserModeThreadStarter in HOS auto& kernel = system.Kernel(); kernel.CurrentScheduler()->OnThreadStart(); - auto* thread = kernel.CurrentScheduler()->GetSchedulerCurrentThread(); - auto& host_context = thread->GetHostContext(); - host_context->SetRewindPoint([this] { GuestRewindFunction(); }); - MultiCoreRunGuestLoop(); -} - -void CpuManager::MultiCoreRunGuestLoop() { - auto& kernel = system.Kernel(); while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); @@ -91,18 +84,26 @@ void CpuManager::MultiCoreRunGuestLoop() { physical_core->Run(); physical_core = &kernel.CurrentPhysicalCore(); } - { - Kernel::KScopedDisableDispatch dd(kernel); - physical_core->ArmInterface().ClearExclusiveState(); - } + + HandleInterrupt(); } } void CpuManager::MultiCoreRunIdleThread() { + // Not accurate to HOS. Remove this entire method when singlecore is removed. + // See notes in KScheduler::ScheduleImpl for more information about why this + // is inaccurate. + auto& kernel = system.Kernel(); + kernel.CurrentScheduler()->OnThreadStart(); + while (true) { - Kernel::KScopedDisableDispatch dd(kernel); - kernel.CurrentPhysicalCore().Idle(); + auto& physical_core = kernel.CurrentPhysicalCore(); + if (!physical_core.IsInterrupted()) { + physical_core.Idle(); + } + + HandleInterrupt(); } } @@ -113,80 +114,73 @@ void CpuManager::MultiCoreRunIdleThread() { void CpuManager::SingleCoreRunGuestThread() { auto& kernel = system.Kernel(); kernel.CurrentScheduler()->OnThreadStart(); - auto* thread = kernel.CurrentScheduler()->GetSchedulerCurrentThread(); - auto& host_context = thread->GetHostContext(); - host_context->SetRewindPoint([this] { GuestRewindFunction(); }); - SingleCoreRunGuestLoop(); -} -void CpuManager::SingleCoreRunGuestLoop() { - auto& kernel = system.Kernel(); while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); if (!physical_core->IsInterrupted()) { physical_core->Run(); physical_core = &kernel.CurrentPhysicalCore(); } + kernel.SetIsPhantomModeForSingleCore(true); system.CoreTiming().Advance(); kernel.SetIsPhantomModeForSingleCore(false); - physical_core->ArmInterface().ClearExclusiveState(); + PreemptSingleCore(); - auto& scheduler = kernel.Scheduler(current_core); - scheduler.RescheduleCurrentCore(); + HandleInterrupt(); } } void CpuManager::SingleCoreRunIdleThread() { auto& kernel = system.Kernel(); + kernel.CurrentScheduler()->OnThreadStart(); + while (true) { - auto& physical_core = kernel.CurrentPhysicalCore(); PreemptSingleCore(false); system.CoreTiming().AddTicks(1000U); idle_count++; - auto& scheduler = physical_core.Scheduler(); - scheduler.RescheduleCurrentCore(); + HandleInterrupt(); } } -void CpuManager::PreemptSingleCore(bool from_running_enviroment) { - { - auto& kernel = system.Kernel(); - auto& scheduler = kernel.Scheduler(current_core); - Kernel::KThread* current_thread = scheduler.GetSchedulerCurrentThread(); - if (idle_count >= 4 || from_running_enviroment) { - if (!from_running_enviroment) { - system.CoreTiming().Idle(); - idle_count = 0; - } - kernel.SetIsPhantomModeForSingleCore(true); - system.CoreTiming().Advance(); - kernel.SetIsPhantomModeForSingleCore(false); - } - current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); - system.CoreTiming().ResetTicks(); - scheduler.Unload(scheduler.GetSchedulerCurrentThread()); - - auto& next_scheduler = kernel.Scheduler(current_core); - Common::Fiber::YieldTo(current_thread->GetHostContext(), *next_scheduler.ControlContext()); - } +void CpuManager::PreemptSingleCore(bool from_running_environment) { + auto& kernel = system.Kernel(); - // May have changed scheduler - { - auto& scheduler = system.Kernel().Scheduler(current_core); - scheduler.Reload(scheduler.GetSchedulerCurrentThread()); - if (!scheduler.IsIdle()) { + if (idle_count >= 4 || from_running_environment) { + if (!from_running_environment) { + system.CoreTiming().Idle(); idle_count = 0; } + kernel.SetIsPhantomModeForSingleCore(true); + system.CoreTiming().Advance(); + kernel.SetIsPhantomModeForSingleCore(false); + } + current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); + system.CoreTiming().ResetTicks(); + kernel.Scheduler(current_core).PreemptSingleCore(); + + // We've now been scheduled again, and we may have exchanged schedulers. + // Reload the scheduler in case it's different. + if (!kernel.Scheduler(current_core).IsIdle()) { + idle_count = 0; } } +void CpuManager::GuestActivate() { + // Similar to the HorizonKernelMain callback in HOS + auto& kernel = system.Kernel(); + auto* scheduler = kernel.CurrentScheduler(); + + scheduler->Activate(); + UNREACHABLE(); +} + void CpuManager::ShutdownThread() { auto& kernel = system.Kernel(); + auto* thread = kernel.GetCurrentEmuThread(); auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0; - auto* current_thread = kernel.GetCurrentEmuThread(); - Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context); + Common::Fiber::YieldTo(thread->GetHostContext(), *core_data[core].host_context); UNREACHABLE(); } @@ -218,9 +212,12 @@ void CpuManager::RunThread(std::size_t core) { system.GPU().ObtainContext(); } - auto* current_thread = system.Kernel().CurrentScheduler()->GetIdleThread(); - Kernel::SetCurrentThread(system.Kernel(), current_thread); - Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext()); + auto& kernel = system.Kernel(); + auto& scheduler = *kernel.CurrentScheduler(); + auto* thread = scheduler.GetSchedulerCurrentThread(); + Kernel::SetCurrentThread(kernel, thread); + + Common::Fiber::YieldTo(data.host_context, *thread->GetHostContext()); } } // namespace Core diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h index 76dc58ee1..95ea3ef39 100644 --- a/src/core/cpu_manager.h +++ b/src/core/cpu_manager.h @@ -50,7 +50,10 @@ public: void Initialize(); void Shutdown(); - std::function<void()> GetGuestThreadStartFunc() { + std::function<void()> GetGuestActivateFunc() { + return [this] { GuestActivate(); }; + } + std::function<void()> GetGuestThreadFunc() { return [this] { GuestThreadFunction(); }; } std::function<void()> GetIdleThreadStartFunc() { @@ -68,20 +71,19 @@ public: private: void GuestThreadFunction(); - void GuestRewindFunction(); void IdleThreadFunction(); void ShutdownThreadFunction(); void MultiCoreRunGuestThread(); - void MultiCoreRunGuestLoop(); void MultiCoreRunIdleThread(); void SingleCoreRunGuestThread(); - void SingleCoreRunGuestLoop(); void SingleCoreRunIdleThread(); static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core); + void GuestActivate(); + void HandleInterrupt(); void ShutdownThread(); void RunThread(std::size_t core); diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index bd2384515..8c3895937 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -126,10 +126,14 @@ void EmulatedController::LoadDevices() { battery_params[LeftIndex].Set("battery", true); battery_params[RightIndex].Set("battery", true); + camera_params = Common::ParamPackage{"engine:camera,camera:1"}; + output_params[LeftIndex] = left_joycon; output_params[RightIndex] = right_joycon; + output_params[2] = camera_params; output_params[LeftIndex].Set("output", true); output_params[RightIndex].Set("output", true); + output_params[2].Set("output", true); LoadTASParams(); @@ -146,6 +150,7 @@ void EmulatedController::LoadDevices() { Common::Input::CreateDevice<Common::Input::InputDevice>); std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(), Common::Input::CreateDevice<Common::Input::InputDevice>); + camera_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(camera_params); std::transform(output_params.begin(), output_params.end(), output_devices.begin(), Common::Input::CreateDevice<Common::Input::OutputDevice>); @@ -267,6 +272,14 @@ void EmulatedController::ReloadInput() { motion_devices[index]->ForceUpdate(); } + if (camera_devices) { + camera_devices->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); }, + }); + camera_devices->ForceUpdate(); + } + // Use a common UUID for TAS static constexpr Common::UUID TAS_UUID = Common::UUID{ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; @@ -851,6 +864,25 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac TriggerOnChange(ControllerTriggerType::Battery, true); } +void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) { + std::unique_lock lock{mutex}; + controller.camera_values = TransformToCamera(callback); + + if (is_configuring) { + lock.unlock(); + TriggerOnChange(ControllerTriggerType::IrSensor, false); + return; + } + + controller.camera_state.sample++; + controller.camera_state.format = + static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format); + controller.camera_state.data = controller.camera_values.data; + + lock.unlock(); + TriggerOnChange(ControllerTriggerType::IrSensor, true); +} + bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { if (device_index >= output_devices.size()) { return false; @@ -928,6 +960,23 @@ bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None; } +bool EmulatedController::SetCameraFormat( + Core::IrSensor::ImageTransferProcessorFormat camera_format) { + LOG_INFO(Service_HID, "Set camera format {}", camera_format); + + auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; + auto& camera_output_device = output_devices[2]; + + if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>( + camera_format)) == Common::Input::CameraError::None) { + return true; + } + + // Fallback to Qt camera if native device doesn't have support + return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>( + camera_format)) == Common::Input::CameraError::None; +} + void EmulatedController::SetLedPattern() { for (auto& device : output_devices) { if (!device) { @@ -1163,6 +1212,11 @@ BatteryValues EmulatedController::GetBatteryValues() const { return controller.battery_values; } +CameraValues EmulatedController::GetCameraValues() const { + std::scoped_lock lock{mutex}; + return controller.camera_values; +} + HomeButtonState EmulatedController::GetHomeButtons() const { std::scoped_lock lock{mutex}; if (is_configuring) { @@ -1251,6 +1305,11 @@ BatteryLevelState EmulatedController::GetBattery() const { return controller.battery_state; } +const CameraState& EmulatedController::GetCamera() const { + std::scoped_lock lock{mutex}; + return controller.camera_state; +} + void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) { std::scoped_lock lock{callback_mutex}; for (const auto& poller_pair : callback_list) { diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index 3f02ed3c0..823c1700c 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -15,10 +15,12 @@ #include "common/settings.h" #include "common/vector_math.h" #include "core/hid/hid_types.h" +#include "core/hid/irs_types.h" #include "core/hid/motion_input.h" namespace Core::HID { const std::size_t max_emulated_controllers = 2; +const std::size_t output_devices = 3; struct ControllerMotionInfo { Common::Input::MotionStatus raw_status{}; MotionInput emulated{}; @@ -34,15 +36,16 @@ using TriggerDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>; using BatteryDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; -using OutputDevices = - std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>; +using CameraDevices = std::unique_ptr<Common::Input::InputDevice>; +using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices>; using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>; using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>; -using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>; +using CameraParams = Common::ParamPackage; +using OutputParams = std::array<Common::ParamPackage, output_devices>; using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>; @@ -51,6 +54,7 @@ using TriggerValues = using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>; using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>; using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>; +using CameraValues = Common::Input::CameraStatus; using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>; struct AnalogSticks { @@ -70,6 +74,12 @@ struct BatteryLevelState { NpadPowerInfo right{}; }; +struct CameraState { + Core::IrSensor::ImageTransferProcessorFormat format{}; + std::vector<u8> data{}; + std::size_t sample{}; +}; + struct ControllerMotion { Common::Vec3f accel{}; Common::Vec3f gyro{}; @@ -96,6 +106,7 @@ struct ControllerStatus { ColorValues color_values{}; BatteryValues battery_values{}; VibrationValues vibration_values{}; + CameraValues camera_values{}; // Data for HID serices HomeButtonState home_button_state{}; @@ -107,6 +118,7 @@ struct ControllerStatus { NpadGcTriggerState gc_trigger_state{}; ControllerColors colors_state{}; BatteryLevelState battery_state{}; + CameraState camera_state{}; }; enum class ControllerTriggerType { @@ -117,6 +129,7 @@ enum class ControllerTriggerType { Color, Battery, Vibration, + IrSensor, Connected, Disconnected, Type, @@ -269,6 +282,9 @@ public: /// Returns the latest battery status from the controller with parameters BatteryValues GetBatteryValues() const; + /// Returns the latest camera status from the controller with parameters + CameraValues GetCameraValues() const; + /// Returns the latest status of button input for the hid::HomeButton service HomeButtonState GetHomeButtons() const; @@ -296,6 +312,9 @@ public: /// Returns the latest battery status from the controller BatteryLevelState GetBattery() const; + /// Returns the latest camera status from the controller + const CameraState& GetCamera() const; + /** * Sends a specific vibration to the output device * @return true if vibration had no errors @@ -315,6 +334,13 @@ public: */ bool SetPollingMode(Common::Input::PollingMode polling_mode); + /** + * Sets the desired camera format to be polled from a controller + * @param camera_format size of each frame + * @return true if SetCameraFormat was successfull + */ + bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format); + /// Returns the led pattern corresponding to this emulated controller LedPattern GetLedPattern() const; @@ -393,6 +419,12 @@ private: void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index); /** + * Updates the camera status of the controller + * @param callback A CallbackStatus containing the camera status + */ + void SetCamera(const Common::Input::CallbackStatus& callback); + + /** * Triggers a callback that something has changed on the controller status * @param type Input type of the event to trigger * @param is_service_update indicates if this event should only be sent to HID services @@ -417,6 +449,7 @@ private: ControllerMotionParams motion_params; TriggerParams trigger_params; BatteryParams battery_params; + CameraParams camera_params; OutputParams output_params; ButtonDevices button_devices; @@ -424,6 +457,7 @@ private: ControllerMotionDevices motion_devices; TriggerDevices trigger_devices; BatteryDevices battery_devices; + CameraDevices camera_devices; OutputDevices output_devices; // TAS related variables diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 18d9f042d..68d143a01 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp @@ -270,6 +270,20 @@ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatu return status; } +Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback) { + Common::Input::CameraStatus camera{}; + switch (callback.type) { + case Common::Input::InputType::IrSensor: + camera = callback.camera_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to camera not implemented", callback.type); + break; + } + + return camera; +} + void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { const auto& properties = analog.properties; float& raw_value = analog.raw_value; diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h index 2be36889f..143c50cc0 100644 --- a/src/core/hid/input_converter.h +++ b/src/core/hid/input_converter.h @@ -77,6 +77,14 @@ Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackSta Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback); /** + * Converts raw input data into a valid camera status. + * + * @param callback Supported callbacks: Camera. + * @return A valid CameraObject object. + */ +Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback); + +/** * Converts raw analog data into a valid analog value * @param analog An analog object containing raw data and properties * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f. diff --git a/src/core/hid/irs_types.h b/src/core/hid/irs_types.h new file mode 100644 index 000000000..88c5b016d --- /dev/null +++ b/src/core/hid/irs_types.h @@ -0,0 +1,301 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/hid/hid_types.h" + +namespace Core::IrSensor { + +// This is nn::irsensor::CameraAmbientNoiseLevel +enum class CameraAmbientNoiseLevel : u32 { + Low, + Medium, + High, + Unkown3, // This level can't be reached +}; + +// This is nn::irsensor::CameraLightTarget +enum class CameraLightTarget : u32 { + AllLeds, + BrightLeds, + DimLeds, + None, +}; + +// This is nn::irsensor::PackedCameraLightTarget +enum class PackedCameraLightTarget : u8 { + AllLeds, + BrightLeds, + DimLeds, + None, +}; + +// This is nn::irsensor::AdaptiveClusteringMode +enum class AdaptiveClusteringMode : u32 { + StaticFov, + DynamicFov, +}; + +// This is nn::irsensor::AdaptiveClusteringTargetDistance +enum class AdaptiveClusteringTargetDistance : u32 { + Near, + Middle, + Far, +}; + +// This is nn::irsensor::ImageTransferProcessorFormat +enum class ImageTransferProcessorFormat : u32 { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, +}; + +// This is nn::irsensor::PackedImageTransferProcessorFormat +enum class PackedImageTransferProcessorFormat : u8 { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, +}; + +// This is nn::irsensor::IrCameraStatus +enum class IrCameraStatus : u32 { + Available, + Unsupported, + Unconnected, +}; + +// This is nn::irsensor::IrCameraInternalStatus +enum class IrCameraInternalStatus : u32 { + Stopped, + FirmwareUpdateNeeded, + Unkown2, + Unkown3, + Unkown4, + FirmwareVersionRequested, + FirmwareVersionIsInvalid, + Ready, + Setting, +}; + +// This is nn::irsensor::detail::StatusManager::IrSensorMode +enum class IrSensorMode : u64 { + None, + MomentProcessor, + ClusteringProcessor, + ImageTransferProcessor, + PointingProcessorMarker, + TeraPluginProcessor, + IrLedProcessor, +}; + +// This is nn::irsensor::ImageProcessorStatus +enum ImageProcessorStatus : u32 { + Stopped, + Running, +}; + +// This is nn::irsensor::HandAnalysisMode +enum class HandAnalysisMode : u32 { + None, + Silhouette, + Image, + SilhoueteAndImage, + SilhuetteOnly, +}; + +// This is nn::irsensor::IrSensorFunctionLevel +enum class IrSensorFunctionLevel : u8 { + unknown0, + unknown1, + unknown2, + unknown3, + unknown4, +}; + +// This is nn::irsensor::MomentProcessorPreprocess +enum class MomentProcessorPreprocess : u32 { + Unkown0, + Unkown1, +}; + +// This is nn::irsensor::PackedMomentProcessorPreprocess +enum class PackedMomentProcessorPreprocess : u8 { + Unkown0, + Unkown1, +}; + +// This is nn::irsensor::PointingStatus +enum class PointingStatus : u32 { + Unkown0, + Unkown1, +}; + +struct IrsRect { + s16 x; + s16 y; + s16 width; + s16 height; +}; + +struct IrsCentroid { + f32 x; + f32 y; +}; + +struct CameraConfig { + u64 exposure_time; + CameraLightTarget light_target; + u32 gain; + bool is_negative_used; + INSERT_PADDING_BYTES(7); +}; +static_assert(sizeof(CameraConfig) == 0x18, "CameraConfig is an invalid size"); + +struct PackedCameraConfig { + u64 exposure_time; + PackedCameraLightTarget light_target; + u8 gain; + bool is_negative_used; + INSERT_PADDING_BYTES(5); +}; +static_assert(sizeof(PackedCameraConfig) == 0x10, "PackedCameraConfig is an invalid size"); + +// This is nn::irsensor::IrCameraHandle +struct IrCameraHandle { + u8 npad_id{}; + Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None}; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size"); + +// This is nn::irsensor::PackedMcuVersion +struct PackedMcuVersion { + u16 major; + u16 minor; +}; +static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size"); + +// This is nn::irsensor::PackedMomentProcessorConfig +struct PackedMomentProcessorConfig { + PackedCameraConfig camera_config; + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; + PackedMomentProcessorPreprocess preprocess; + u8 preprocess_intensity_threshold; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(PackedMomentProcessorConfig) == 0x20, + "PackedMomentProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedClusteringProcessorConfig +struct PackedClusteringProcessorConfig { + PackedCameraConfig camera_config; + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; + u32 pixel_count_min; + u32 pixel_count_max; + u8 object_intensity_min; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(PackedClusteringProcessorConfig) == 0x28, + "PackedClusteringProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedImageTransferProcessorConfig +struct PackedImageTransferProcessorConfig { + PackedCameraConfig camera_config; + PackedMcuVersion required_mcu_version; + PackedImageTransferProcessorFormat format; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18, + "PackedImageTransferProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedTeraPluginProcessorConfig +struct PackedTeraPluginProcessorConfig { + PackedMcuVersion required_mcu_version; + u8 mode; + u8 unknown_1; + u8 unknown_2; + u8 unknown_3; +}; +static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8, + "PackedTeraPluginProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedPointingProcessorConfig +struct PackedPointingProcessorConfig { + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; +}; +static_assert(sizeof(PackedPointingProcessorConfig) == 0xC, + "PackedPointingProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedFunctionLevel +struct PackedFunctionLevel { + IrSensorFunctionLevel function_level; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size"); + +// This is nn::irsensor::PackedImageTransferProcessorExConfig +struct PackedImageTransferProcessorExConfig { + PackedCameraConfig camera_config; + PackedMcuVersion required_mcu_version; + PackedImageTransferProcessorFormat origin_format; + PackedImageTransferProcessorFormat trimming_format; + u16 trimming_start_x; + u16 trimming_start_y; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(5); +}; +static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20, + "PackedImageTransferProcessorExConfig is an invalid size"); + +// This is nn::irsensor::PackedIrLedProcessorConfig +struct PackedIrLedProcessorConfig { + PackedMcuVersion required_mcu_version; + u8 light_target; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8, + "PackedIrLedProcessorConfig is an invalid size"); + +// This is nn::irsensor::HandAnalysisConfig +struct HandAnalysisConfig { + HandAnalysisMode mode; +}; +static_assert(sizeof(HandAnalysisConfig) == 0x4, "HandAnalysisConfig is an invalid size"); + +// This is nn::irsensor::detail::ProcessorState contents are different for each processor +struct ProcessorState { + std::array<u8, 0xE20> processor_raw_data{}; +}; +static_assert(sizeof(ProcessorState) == 0xE20, "ProcessorState is an invalid size"); + +// This is nn::irsensor::detail::DeviceFormat +struct DeviceFormat { + Core::IrSensor::IrCameraStatus camera_status{Core::IrSensor::IrCameraStatus::Unconnected}; + Core::IrSensor::IrCameraInternalStatus camera_internal_status{ + Core::IrSensor::IrCameraInternalStatus::Ready}; + Core::IrSensor::IrSensorMode mode{Core::IrSensor::IrSensorMode::None}; + ProcessorState state{}; +}; +static_assert(sizeof(DeviceFormat) == 0xE30, "DeviceFormat is an invalid size"); + +// This is nn::irsensor::ImageTransferProcessorState +struct ImageTransferProcessorState { + u64 sampling_number; + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + INSERT_PADDING_BYTES(4); +}; +static_assert(sizeof(ImageTransferProcessorState) == 0x10, + "ImageTransferProcessorState is an invalid size"); + +} // namespace Core::IrSensor diff --git a/src/core/hle/kernel/global_scheduler_context.cpp b/src/core/hle/kernel/global_scheduler_context.cpp index 164436b26..65576b8c4 100644 --- a/src/core/hle/kernel/global_scheduler_context.cpp +++ b/src/core/hle/kernel/global_scheduler_context.cpp @@ -41,12 +41,7 @@ void GlobalSchedulerContext::PreemptThreads() { ASSERT(IsLocked()); for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { const u32 priority = preemption_priorities[core_id]; - kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority); - - // Signal an interrupt occurred. For core 3, this is a certainty, as preemption will result - // in the rotator thread being scheduled. For cores 0-2, this is to simulate or system - // interrupts that may have occurred. - kernel.PhysicalCore(core_id).Interrupt(); + KScheduler::RotateScheduledQueue(kernel, core_id, priority); } } diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 45135a07f..5b3feec66 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -287,18 +287,52 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size, BufferDescriptorB().size() > buffer_index && BufferDescriptorB()[buffer_index].Size() >= size, { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size); - memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); + WriteBufferB(buffer, size, buffer_index); } else { ASSERT_OR_EXECUTE_MSG( BufferDescriptorC().size() > buffer_index && BufferDescriptorC()[buffer_index].Size() >= size, { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size); - memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); + WriteBufferC(buffer, size, buffer_index); } return size; } +std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size, + std::size_t buffer_index) const { + if (buffer_index >= BufferDescriptorB().size() || size == 0) { + return 0; + } + + const auto buffer_size{BufferDescriptorB()[buffer_index].Size()}; + if (size > buffer_size) { + LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, + buffer_size); + size = buffer_size; // TODO(bunnei): This needs to be HW tested + } + + memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); + return size; +} + +std::size_t HLERequestContext::WriteBufferC(const void* buffer, std::size_t size, + std::size_t buffer_index) const { + if (buffer_index >= BufferDescriptorC().size() || size == 0) { + return 0; + } + + const auto buffer_size{BufferDescriptorC()[buffer_index].Size()}; + if (size > buffer_size) { + LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, + buffer_size); + size = buffer_size; // TODO(bunnei): This needs to be HW tested + } + + memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); + return size; +} + std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const { const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && BufferDescriptorA()[buffer_index].Size()}; diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index d3abeee85..99265ce90 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -277,6 +277,14 @@ public: std::size_t WriteBuffer(const void* buffer, std::size_t size, std::size_t buffer_index = 0) const; + /// Helper function to write buffer B + std::size_t WriteBufferB(const void* buffer, std::size_t size, + std::size_t buffer_index = 0) const; + + /// Helper function to write buffer C + std::size_t WriteBufferC(const void* buffer, std::size_t size, + std::size_t buffer_index = 0) const; + /* Helper function to write a buffer using the appropriate buffer descriptor * * @tparam T an arbitrary container that satisfies the diff --git a/src/core/hle/kernel/k_code_memory.h b/src/core/hle/kernel/k_code_memory.h index 2410f74a3..2e7e1436a 100644 --- a/src/core/hle/kernel/k_code_memory.h +++ b/src/core/hle/kernel/k_code_memory.h @@ -30,19 +30,19 @@ public: explicit KCodeMemory(KernelCore& kernel_); Result Initialize(Core::DeviceMemory& device_memory, VAddr address, size_t size); - void Finalize(); + void Finalize() override; Result Map(VAddr address, size_t size); Result Unmap(VAddr address, size_t size); Result MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm); Result UnmapFromOwner(VAddr address, size_t size); - bool IsInitialized() const { + bool IsInitialized() const override { return m_is_initialized; } static void PostDestroy([[maybe_unused]] uintptr_t arg) {} - KProcess* GetOwner() const { + KProcess* GetOwner() const override { return m_owner; } VAddr GetSourceAddress() const { diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp index d606a7f86..1b577a5b3 100644 --- a/src/core/hle/kernel/k_interrupt_manager.cpp +++ b/src/core/hle/kernel/k_interrupt_manager.cpp @@ -6,6 +6,7 @@ #include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/physical_core.h" namespace Kernel::KInterruptManager { @@ -15,6 +16,9 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) { return; } + // Acknowledge the interrupt. + kernel.PhysicalCore(core_id).ClearInterrupt(); + auto& current_thread = GetCurrentThread(kernel); // If the user disable count is set, we may need to pin the current thread. @@ -27,6 +31,9 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) { // Set the interrupt flag for the thread. GetCurrentThread(kernel).SetInterruptFlag(); } + + // Request interrupt scheduling. + kernel.CurrentScheduler()->RequestScheduleOnInterrupt(); } } // namespace Kernel::KInterruptManager diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp index d599d2bcb..c34ce7a17 100644 --- a/src/core/hle/kernel/k_scheduler.cpp +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -27,69 +27,185 @@ static void IncrementScheduledCount(Kernel::KThread* thread) { } } -void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule) { - auto scheduler = kernel.CurrentScheduler(); - - u32 current_core{0xF}; - bool must_context_switch{}; - if (scheduler) { - current_core = scheduler->core_id; - // TODO(bunnei): Should be set to true when we deprecate single core - must_context_switch = !kernel.IsPhantomModeForSingleCore(); - } - - while (cores_pending_reschedule != 0) { - const auto core = static_cast<u32>(std::countr_zero(cores_pending_reschedule)); - ASSERT(core < Core::Hardware::NUM_CPU_CORES); - if (!must_context_switch || core != current_core) { - auto& phys_core = kernel.PhysicalCore(core); - phys_core.Interrupt(); +KScheduler::KScheduler(KernelCore& kernel_) : kernel{kernel_} { + m_switch_fiber = std::make_shared<Common::Fiber>([this] { + while (true) { + ScheduleImplFiber(); } - cores_pending_reschedule &= ~(1ULL << core); + }); + + m_state.needs_scheduling = true; +} + +KScheduler::~KScheduler() = default; + +void KScheduler::SetInterruptTaskRunnable() { + m_state.interrupt_task_runnable = true; + m_state.needs_scheduling = true; +} + +void KScheduler::RequestScheduleOnInterrupt() { + m_state.needs_scheduling = true; + + if (CanSchedule(kernel)) { + ScheduleOnInterrupt(); } +} - for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; ++core_id) { - if (kernel.PhysicalCore(core_id).IsInterrupted()) { - KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_id)); - } +void KScheduler::DisableScheduling(KernelCore& kernel) { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0); + GetCurrentThread(kernel).DisableDispatch(); +} + +void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 1); + + auto* scheduler{kernel.CurrentScheduler()}; + + if (!scheduler || kernel.IsPhantomModeForSingleCore()) { + KScheduler::RescheduleCores(kernel, cores_needing_scheduling); + KScheduler::RescheduleCurrentHLEThread(kernel); + return; + } + + scheduler->RescheduleOtherCores(cores_needing_scheduling); + + if (GetCurrentThread(kernel).GetDisableDispatchCount() > 1) { + GetCurrentThread(kernel).EnableDispatch(); + } else { + scheduler->RescheduleCurrentCore(); + } +} + +void KScheduler::RescheduleCurrentHLEThread(KernelCore& kernel) { + // HACK: we cannot schedule from this thread, it is not a core thread + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); + + // Special case to ensure dummy threads that are waiting block + GetCurrentThread(kernel).IfDummyThreadTryWait(); + + ASSERT(GetCurrentThread(kernel).GetState() != ThreadState::Waiting); + GetCurrentThread(kernel).EnableDispatch(); +} + +u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) { + if (IsSchedulerUpdateNeeded(kernel)) { + return UpdateHighestPriorityThreadsImpl(kernel); + } else { + return 0; + } +} + +void KScheduler::Schedule() { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); + ASSERT(m_core_id == GetCurrentCoreId(kernel)); + + ScheduleImpl(); +} + +void KScheduler::ScheduleOnInterrupt() { + GetCurrentThread(kernel).DisableDispatch(); + Schedule(); + GetCurrentThread(kernel).EnableDispatch(); +} + +void KScheduler::PreemptSingleCore() { + GetCurrentThread(kernel).DisableDispatch(); + + auto* thread = GetCurrentThreadPointer(kernel); + auto& previous_scheduler = kernel.Scheduler(thread->GetCurrentCore()); + previous_scheduler.Unload(thread); + + Common::Fiber::YieldTo(thread->GetHostContext(), *m_switch_fiber); + + GetCurrentThread(kernel).EnableDispatch(); +} + +void KScheduler::RescheduleCurrentCore() { + ASSERT(!kernel.IsPhantomModeForSingleCore()); + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); + + GetCurrentThread(kernel).EnableDispatch(); + + if (m_state.needs_scheduling.load()) { + // Disable interrupts, and then check again if rescheduling is needed. + // KScopedInterruptDisable intr_disable; + + kernel.CurrentScheduler()->RescheduleCurrentCoreImpl(); } +} - if (must_context_switch) { - auto core_scheduler = kernel.CurrentScheduler(); - kernel.ExitSVCProfile(); - core_scheduler->RescheduleCurrentCore(); - kernel.EnterSVCProfile(); +void KScheduler::RescheduleCurrentCoreImpl() { + // Check that scheduling is needed. + if (m_state.needs_scheduling.load()) [[likely]] { + GetCurrentThread(kernel).DisableDispatch(); + Schedule(); + GetCurrentThread(kernel).EnableDispatch(); } } +void KScheduler::Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id) { + // Set core ID/idle thread/interrupt task manager. + m_core_id = core_id; + m_idle_thread = idle_thread; + // m_state.idle_thread_stack = m_idle_thread->GetStackTop(); + // m_state.interrupt_task_manager = &kernel.GetInterruptTaskManager(); + + // Insert the main thread into the priority queue. + // { + // KScopedSchedulerLock lk{kernel}; + // GetPriorityQueue(kernel).PushBack(GetCurrentThreadPointer(kernel)); + // SetSchedulerUpdateNeeded(kernel); + // } + + // Bind interrupt handler. + // kernel.GetInterruptManager().BindHandler( + // GetSchedulerInterruptHandler(kernel), KInterruptName::Scheduler, m_core_id, + // KInterruptController::PriorityLevel::Scheduler, false, false); + + // Set the current thread. + m_current_thread = main_thread; +} + +void KScheduler::Activate() { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); + + // m_state.should_count_idle = KTargetSystem::IsDebugMode(); + m_is_active = true; + RescheduleCurrentCore(); +} + +void KScheduler::OnThreadStart() { + GetCurrentThread(kernel).EnableDispatch(); +} + u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) { - KScopedSpinLock lk{guard}; - if (KThread* prev_highest_thread = state.highest_priority_thread; - prev_highest_thread != highest_thread) { - if (prev_highest_thread != nullptr) { + if (KThread* prev_highest_thread = m_state.highest_priority_thread; + prev_highest_thread != highest_thread) [[likely]] { + if (prev_highest_thread != nullptr) [[likely]] { IncrementScheduledCount(prev_highest_thread); - prev_highest_thread->SetLastScheduledTick(system.CoreTiming().GetCPUTicks()); + prev_highest_thread->SetLastScheduledTick(kernel.System().CoreTiming().GetCPUTicks()); } - if (state.should_count_idle) { - if (highest_thread != nullptr) { + if (m_state.should_count_idle) { + if (highest_thread != nullptr) [[likely]] { if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) { - process->SetRunningThread(core_id, highest_thread, state.idle_count); + process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count); } } else { - state.idle_count++; + m_state.idle_count++; } } - state.highest_priority_thread = highest_thread; - state.needs_scheduling.store(true); - return (1ULL << core_id); + m_state.highest_priority_thread = highest_thread; + m_state.needs_scheduling = true; + return (1ULL << m_core_id); } else { return 0; } } u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { - ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); // Clear that we need to update. ClearSchedulerUpdateNeeded(kernel); @@ -98,18 +214,20 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { KThread* top_threads[Core::Hardware::NUM_CPU_CORES]; auto& priority_queue = GetPriorityQueue(kernel); - /// We want to go over all cores, finding the highest priority thread and determining if - /// scheduling is needed for that core. + // We want to go over all cores, finding the highest priority thread and determining if + // scheduling is needed for that core. for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { KThread* top_thread = priority_queue.GetScheduledFront(static_cast<s32>(core_id)); if (top_thread != nullptr) { - // If the thread has no waiters, we need to check if the process has a thread pinned. - if (top_thread->GetNumKernelWaiters() == 0) { - if (KProcess* parent = top_thread->GetOwnerProcess(); parent != nullptr) { - if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id)); - pinned != nullptr && pinned != top_thread) { - // We prefer our parent's pinned thread if possible. However, we also don't - // want to schedule un-runnable threads. + // We need to check if the thread's process has a pinned thread. + if (KProcess* parent = top_thread->GetOwnerProcess()) { + // Check that there's a pinned thread other than the current top thread. + if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id)); + pinned != nullptr && pinned != top_thread) { + // We need to prefer threads with kernel waiters to the pinned thread. + if (top_thread->GetNumKernelWaiters() == + 0 /* && top_thread != parent->GetExceptionThread() */) { + // If the pinned thread is runnable, use it. if (pinned->GetRawState() == ThreadState::Runnable) { top_thread = pinned; } else { @@ -129,7 +247,8 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { // Idle cores are bad. We're going to try to migrate threads to each idle core in turn. while (idle_cores != 0) { - const auto core_id = static_cast<u32>(std::countr_zero(idle_cores)); + const s32 core_id = static_cast<s32>(std::countr_zero(idle_cores)); + if (KThread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) { s32 migration_candidates[Core::Hardware::NUM_CPU_CORES]; size_t num_candidates = 0; @@ -150,7 +269,6 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { // The suggested thread isn't bound to its core, so we can migrate it! suggested->SetActiveCore(core_id); priority_queue.ChangeCore(suggested_core, suggested); - top_threads[core_id] = suggested; cores_needing_scheduling |= kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); @@ -183,7 +301,6 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { // Perform the migration. suggested->SetActiveCore(core_id); priority_queue.ChangeCore(candidate_core, suggested); - top_threads[core_id] = suggested; cores_needing_scheduling |= kernel.Scheduler(core_id).UpdateHighestPriorityThread( @@ -200,24 +317,210 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { return cores_needing_scheduling; } +void KScheduler::SwitchThread(KThread* next_thread) { + KProcess* const cur_process = kernel.CurrentProcess(); + KThread* const cur_thread = GetCurrentThreadPointer(kernel); + + // We never want to schedule a null thread, so use the idle thread if we don't have a next. + if (next_thread == nullptr) { + next_thread = m_idle_thread; + } + + if (next_thread->GetCurrentCore() != m_core_id) { + next_thread->SetCurrentCore(m_core_id); + } + + // If we're not actually switching thread, there's nothing to do. + if (next_thread == cur_thread) { + return; + } + + // Next thread is now known not to be nullptr, and must not be dispatchable. + ASSERT(next_thread->GetDisableDispatchCount() == 1); + ASSERT(!next_thread->IsDummyThread()); + + // Update the CPU time tracking variables. + const s64 prev_tick = m_last_context_switch_time; + const s64 cur_tick = kernel.System().CoreTiming().GetCPUTicks(); + const s64 tick_diff = cur_tick - prev_tick; + cur_thread->AddCpuTime(m_core_id, tick_diff); + if (cur_process != nullptr) { + cur_process->UpdateCPUTimeTicks(tick_diff); + } + m_last_context_switch_time = cur_tick; + + // Update our previous thread. + if (cur_process != nullptr) { + if (!cur_thread->IsTerminationRequested() && cur_thread->GetActiveCore() == m_core_id) + [[likely]] { + m_state.prev_thread = cur_thread; + } else { + m_state.prev_thread = nullptr; + } + } + + // Switch the current process, if we're switching processes. + // if (KProcess *next_process = next_thread->GetOwnerProcess(); next_process != cur_process) { + // KProcess::Switch(cur_process, next_process); + // } + + // Set the new thread. + SetCurrentThread(kernel, next_thread); + m_current_thread = next_thread; + + // Set the new Thread Local region. + // cpu::SwitchThreadLocalRegion(GetInteger(next_thread->GetThreadLocalRegionAddress())); +} + +void KScheduler::ScheduleImpl() { + // First, clear the needs scheduling bool. + m_state.needs_scheduling.store(false, std::memory_order_seq_cst); + + // Load the appropriate thread pointers for scheduling. + KThread* const cur_thread{GetCurrentThreadPointer(kernel)}; + KThread* highest_priority_thread{m_state.highest_priority_thread}; + + // Check whether there are runnable interrupt tasks. + if (m_state.interrupt_task_runnable) { + // The interrupt task is runnable. + // We want to switch to the interrupt task/idle thread. + highest_priority_thread = nullptr; + } + + // If there aren't, we want to check if the highest priority thread is the same as the current + // thread. + if (highest_priority_thread == cur_thread) { + // If they're the same, then we can just return. + return; + } + + // The highest priority thread is not the same as the current thread. + // Jump to the switcher and continue executing from there. + m_switch_cur_thread = cur_thread; + m_switch_highest_priority_thread = highest_priority_thread; + m_switch_from_schedule = true; + Common::Fiber::YieldTo(cur_thread->host_context, *m_switch_fiber); + + // Returning from ScheduleImpl occurs after this thread has been scheduled again. +} + +void KScheduler::ScheduleImplFiber() { + KThread* const cur_thread{m_switch_cur_thread}; + KThread* highest_priority_thread{m_switch_highest_priority_thread}; + + // If we're not coming from scheduling (i.e., we came from SC preemption), + // we should restart the scheduling loop directly. Not accurate to HOS. + if (!m_switch_from_schedule) { + goto retry; + } + + // Mark that we are not coming from scheduling anymore. + m_switch_from_schedule = false; + + // Save the original thread context. + Unload(cur_thread); + + // The current thread's context has been entirely taken care of. + // Now we want to loop until we successfully switch the thread context. + while (true) { + // We're starting to try to do the context switch. + // Check if the highest priority thread is null. + if (!highest_priority_thread) { + // The next thread is nullptr! + + // Switch to the idle thread. Note: HOS treats idling as a special case for + // performance. This is not *required* for yuzu's purposes, and for singlecore + // compatibility, we can just move the logic that would go here into the execution + // of the idle thread. If we ever remove singlecore, we should implement this + // accurately to HOS. + highest_priority_thread = m_idle_thread; + } + + // We want to try to lock the highest priority thread's context. + // Try to take it. + while (!highest_priority_thread->context_guard.try_lock()) { + // The highest priority thread's context is already locked. + // Check if we need scheduling. If we don't, we can retry directly. + if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) { + // If we do, another core is interfering, and we must start again. + goto retry; + } + } + + // It's time to switch the thread. + // Switch to the highest priority thread. + SwitchThread(highest_priority_thread); + + // Check if we need scheduling. If we do, then we can't complete the switch and should + // retry. + if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) { + // Our switch failed. + // We should unlock the thread context, and then retry. + highest_priority_thread->context_guard.unlock(); + goto retry; + } else { + break; + } + + retry: + + // We failed to successfully do the context switch, and need to retry. + // Clear needs_scheduling. + m_state.needs_scheduling.store(false, std::memory_order_seq_cst); + + // Refresh the highest priority thread. + highest_priority_thread = m_state.highest_priority_thread; + } + + // Reload the guest thread context. + Reload(highest_priority_thread); + + // Reload the host thread. + Common::Fiber::YieldTo(m_switch_fiber, *highest_priority_thread->host_context); +} + +void KScheduler::Unload(KThread* thread) { + auto& cpu_core = kernel.System().ArmInterface(m_core_id); + cpu_core.SaveContext(thread->GetContext32()); + cpu_core.SaveContext(thread->GetContext64()); + // Save the TPIDR_EL0 system register in case it was modified. + thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); + cpu_core.ClearExclusiveState(); + + // Check if the thread is terminated by checking the DPC flags. + if ((thread->GetStackParameters().dpc_flags & static_cast<u32>(DpcFlag::Terminated)) == 0) { + // The thread isn't terminated, so we want to unlock it. + thread->context_guard.unlock(); + } +} + +void KScheduler::Reload(KThread* thread) { + auto& cpu_core = kernel.System().ArmInterface(m_core_id); + cpu_core.LoadContext(thread->GetContext32()); + cpu_core.LoadContext(thread->GetContext64()); + cpu_core.SetTlsAddress(thread->GetTLSAddress()); + cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0()); + cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints()); + cpu_core.ClearExclusiveState(); +} + void KScheduler::ClearPreviousThread(KernelCore& kernel, KThread* thread) { - ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; ++i) { // Get an atomic reference to the core scheduler's previous thread. - std::atomic_ref<KThread*> prev_thread(kernel.Scheduler(static_cast<s32>(i)).prev_thread); - static_assert(std::atomic_ref<KThread*>::is_always_lock_free); + auto& prev_thread{kernel.Scheduler(i).m_state.prev_thread}; // Atomically clear the previous thread if it's our target. KThread* compare = thread; - prev_thread.compare_exchange_strong(compare, nullptr); + prev_thread.compare_exchange_strong(compare, nullptr, std::memory_order_seq_cst); } } void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state) { - ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); // Check if the state has changed, because if it hasn't there's nothing to do. - const auto cur_state = thread->GetRawState(); + const ThreadState cur_state = thread->GetRawState(); if (cur_state == old_state) { return; } @@ -237,12 +540,12 @@ void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, Threa } void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority) { - ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); // If the thread is runnable, we want to change its priority in the queue. if (thread->GetRawState() == ThreadState::Runnable) { GetPriorityQueue(kernel).ChangePriority(old_priority, - thread == kernel.GetCurrentEmuThread(), thread); + thread == GetCurrentThreadPointer(kernel), thread); IncrementScheduledCount(thread); SetSchedulerUpdateNeeded(kernel); } @@ -250,7 +553,7 @@ void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s3 void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread, const KAffinityMask& old_affinity, s32 old_core) { - ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); // If the thread is runnable, we want to change its affinity in the queue. if (thread->GetRawState() == ThreadState::Runnable) { @@ -260,15 +563,14 @@ void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread } } -void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { - ASSERT(system.GlobalSchedulerContext().IsLocked()); +void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 priority) { + ASSERT(IsSchedulerLockedByCurrentThread(kernel)); // Get a reference to the priority queue. - auto& kernel = system.Kernel(); auto& priority_queue = GetPriorityQueue(kernel); // Rotate the front of the queue to the end. - KThread* top_thread = priority_queue.GetScheduledFront(cpu_core_id, priority); + KThread* top_thread = priority_queue.GetScheduledFront(core_id, priority); KThread* next_thread = nullptr; if (top_thread != nullptr) { next_thread = priority_queue.MoveToScheduledBack(top_thread); @@ -280,7 +582,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { // While we have a suggested thread, try to migrate it! { - KThread* suggested = priority_queue.GetSuggestedFront(cpu_core_id, priority); + KThread* suggested = priority_queue.GetSuggestedFront(core_id, priority); while (suggested != nullptr) { // Check if the suggested thread is the top thread on its core. const s32 suggested_core = suggested->GetActiveCore(); @@ -301,7 +603,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { // to the front of the queue. if (top_on_suggested_core == nullptr || top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { - suggested->SetActiveCore(cpu_core_id); + suggested->SetActiveCore(core_id); priority_queue.ChangeCore(suggested_core, suggested, true); IncrementScheduledCount(suggested); break; @@ -309,22 +611,21 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { } // Get the next suggestion. - suggested = priority_queue.GetSamePriorityNext(cpu_core_id, suggested); + suggested = priority_queue.GetSamePriorityNext(core_id, suggested); } } // Now that we might have migrated a thread with the same priority, check if we can do better. - { - KThread* best_thread = priority_queue.GetScheduledFront(cpu_core_id); + KThread* best_thread = priority_queue.GetScheduledFront(core_id); if (best_thread == GetCurrentThreadPointer(kernel)) { - best_thread = priority_queue.GetScheduledNext(cpu_core_id, best_thread); + best_thread = priority_queue.GetScheduledNext(core_id, best_thread); } // If the best thread we can choose has a priority the same or worse than ours, try to // migrate a higher priority thread. if (best_thread != nullptr && best_thread->GetPriority() >= priority) { - KThread* suggested = priority_queue.GetSuggestedFront(cpu_core_id); + KThread* suggested = priority_queue.GetSuggestedFront(core_id); while (suggested != nullptr) { // If the suggestion's priority is the same as ours, don't bother. if (suggested->GetPriority() >= best_thread->GetPriority()) { @@ -343,7 +644,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { if (top_on_suggested_core == nullptr || top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { - suggested->SetActiveCore(cpu_core_id); + suggested->SetActiveCore(core_id); priority_queue.ChangeCore(suggested_core, suggested, true); IncrementScheduledCount(suggested); break; @@ -351,7 +652,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { } // Get the next suggestion. - suggested = priority_queue.GetSuggestedNext(cpu_core_id, suggested); + suggested = priority_queue.GetSuggestedNext(core_id, suggested); } } } @@ -360,64 +661,6 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { SetSchedulerUpdateNeeded(kernel); } -bool KScheduler::CanSchedule(KernelCore& kernel) { - return kernel.GetCurrentEmuThread()->GetDisableDispatchCount() <= 1; -} - -bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) { - return kernel.GlobalSchedulerContext().scheduler_update_needed.load(std::memory_order_acquire); -} - -void KScheduler::SetSchedulerUpdateNeeded(KernelCore& kernel) { - kernel.GlobalSchedulerContext().scheduler_update_needed.store(true, std::memory_order_release); -} - -void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) { - kernel.GlobalSchedulerContext().scheduler_update_needed.store(false, std::memory_order_release); -} - -void KScheduler::DisableScheduling(KernelCore& kernel) { - // If we are shutting down the kernel, none of this is relevant anymore. - if (kernel.IsShuttingDown()) { - return; - } - - ASSERT(GetCurrentThreadPointer(kernel)->GetDisableDispatchCount() >= 0); - GetCurrentThreadPointer(kernel)->DisableDispatch(); -} - -void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) { - // If we are shutting down the kernel, none of this is relevant anymore. - if (kernel.IsShuttingDown()) { - return; - } - - auto* current_thread = GetCurrentThreadPointer(kernel); - - ASSERT(current_thread->GetDisableDispatchCount() >= 1); - - if (current_thread->GetDisableDispatchCount() > 1) { - current_thread->EnableDispatch(); - } else { - RescheduleCores(kernel, cores_needing_scheduling); - } - - // Special case to ensure dummy threads that are waiting block. - current_thread->IfDummyThreadTryWait(); -} - -u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) { - if (IsSchedulerUpdateNeeded(kernel)) { - return UpdateHighestPriorityThreadsImpl(kernel); - } else { - return 0; - } -} - -KSchedulerPriorityQueue& KScheduler::GetPriorityQueue(KernelCore& kernel) { - return kernel.GlobalSchedulerContext().priority_queue; -} - void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) { // Validate preconditions. ASSERT(CanSchedule(kernel)); @@ -437,7 +680,7 @@ void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) { // Perform the yield. { - KScopedSchedulerLock lock(kernel); + KScopedSchedulerLock sl{kernel}; const auto cur_state = cur_thread.GetRawState(); if (cur_state == ThreadState::Runnable) { @@ -476,7 +719,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) { // Perform the yield. { - KScopedSchedulerLock lock(kernel); + KScopedSchedulerLock sl{kernel}; const auto cur_state = cur_thread.GetRawState(); if (cur_state == ThreadState::Runnable) { @@ -496,7 +739,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) { if (KThread* running_on_suggested_core = (suggested_core >= 0) - ? kernel.Scheduler(suggested_core).state.highest_priority_thread + ? kernel.Scheduler(suggested_core).m_state.highest_priority_thread : nullptr; running_on_suggested_core != suggested) { // If the current thread's priority is higher than our suggestion's we prefer @@ -564,7 +807,7 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) { // Perform the yield. { - KScopedSchedulerLock lock(kernel); + KScopedSchedulerLock sl{kernel}; const auto cur_state = cur_thread.GetRawState(); if (cur_state == ThreadState::Runnable) { @@ -621,223 +864,19 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) { } } -KScheduler::KScheduler(Core::System& system_, s32 core_id_) : system{system_}, core_id{core_id_} { - switch_fiber = std::make_shared<Common::Fiber>([this] { SwitchToCurrent(); }); - state.needs_scheduling.store(true); - state.interrupt_task_thread_runnable = false; - state.should_count_idle = false; - state.idle_count = 0; - state.idle_thread_stack = nullptr; - state.highest_priority_thread = nullptr; -} - -void KScheduler::Finalize() { - if (idle_thread) { - idle_thread->Close(); - idle_thread = nullptr; - } -} - -KScheduler::~KScheduler() { - ASSERT(!idle_thread); -} - -KThread* KScheduler::GetSchedulerCurrentThread() const { - if (auto result = current_thread.load(); result) { - return result; +void KScheduler::RescheduleOtherCores(u64 cores_needing_scheduling) { + if (const u64 core_mask = cores_needing_scheduling & ~(1ULL << m_core_id); core_mask != 0) { + RescheduleCores(kernel, core_mask); } - return idle_thread; -} - -u64 KScheduler::GetLastContextSwitchTicks() const { - return last_context_switch_time; } -void KScheduler::RescheduleCurrentCore() { - ASSERT(GetCurrentThread(system.Kernel()).GetDisableDispatchCount() == 1); - - auto& phys_core = system.Kernel().PhysicalCore(core_id); - if (phys_core.IsInterrupted()) { - phys_core.ClearInterrupt(); - } - - guard.Lock(); - if (state.needs_scheduling.load()) { - Schedule(); - } else { - GetCurrentThread(system.Kernel()).EnableDispatch(); - guard.Unlock(); - } -} - -void KScheduler::OnThreadStart() { - SwitchContextStep2(); -} - -void KScheduler::Unload(KThread* thread) { - ASSERT(thread); - - LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr"); - - if (thread->IsCallingSvc()) { - thread->ClearIsCallingSvc(); - } - - auto& physical_core = system.Kernel().PhysicalCore(core_id); - if (!physical_core.IsInitialized()) { - return; - } - - Core::ARM_Interface& cpu_core = physical_core.ArmInterface(); - cpu_core.SaveContext(thread->GetContext32()); - cpu_core.SaveContext(thread->GetContext64()); - // Save the TPIDR_EL0 system register in case it was modified. - thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); - cpu_core.ClearExclusiveState(); - - if (!thread->IsTerminationRequested() && thread->GetActiveCore() == core_id) { - prev_thread = thread; - } else { - prev_thread = nullptr; - } - - thread->context_guard.unlock(); -} - -void KScheduler::Reload(KThread* thread) { - LOG_TRACE(Kernel, "core {}, reload thread {}", core_id, thread->GetName()); - - Core::ARM_Interface& cpu_core = system.ArmInterface(core_id); - cpu_core.LoadContext(thread->GetContext32()); - cpu_core.LoadContext(thread->GetContext64()); - cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints()); - cpu_core.SetTlsAddress(thread->GetTLSAddress()); - cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0()); - cpu_core.ClearExclusiveState(); -} - -void KScheduler::SwitchContextStep2() { - // Load context of new thread - Reload(GetCurrentThreadPointer(system.Kernel())); - - RescheduleCurrentCore(); -} - -void KScheduler::Schedule() { - ASSERT(GetCurrentThread(system.Kernel()).GetDisableDispatchCount() == 1); - this->ScheduleImpl(); -} - -void KScheduler::ScheduleImpl() { - KThread* previous_thread = GetCurrentThreadPointer(system.Kernel()); - KThread* next_thread = state.highest_priority_thread; - - state.needs_scheduling.store(false); - - // We never want to schedule a null thread, so use the idle thread if we don't have a next. - if (next_thread == nullptr) { - next_thread = idle_thread; - } - - if (next_thread->GetCurrentCore() != core_id) { - next_thread->SetCurrentCore(core_id); - } - - // We never want to schedule a dummy thread, as these are only used by host threads for locking. - if (next_thread->GetThreadType() == ThreadType::Dummy) { - ASSERT_MSG(false, "Dummy threads should never be scheduled!"); - next_thread = idle_thread; - } - - // If we're not actually switching thread, there's nothing to do. - if (next_thread == current_thread.load()) { - previous_thread->EnableDispatch(); - guard.Unlock(); - return; - } - - // Update the CPU time tracking variables. - KProcess* const previous_process = system.Kernel().CurrentProcess(); - UpdateLastContextSwitchTime(previous_thread, previous_process); - - // Save context for previous thread - Unload(previous_thread); - - std::shared_ptr<Common::Fiber>* old_context; - old_context = &previous_thread->GetHostContext(); - - // Set the new thread. - SetCurrentThread(system.Kernel(), next_thread); - current_thread.store(next_thread); - - guard.Unlock(); - - Common::Fiber::YieldTo(*old_context, *switch_fiber); - /// When a thread wakes up, the scheduler may have changed to other in another core. - auto& next_scheduler = *system.Kernel().CurrentScheduler(); - next_scheduler.SwitchContextStep2(); -} - -void KScheduler::SwitchToCurrent() { - while (true) { - { - KScopedSpinLock lk{guard}; - current_thread.store(state.highest_priority_thread); - state.needs_scheduling.store(false); +void KScheduler::RescheduleCores(KernelCore& kernel, u64 core_mask) { + // Send IPI + for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { + if (core_mask & (1ULL << i)) { + kernel.PhysicalCore(i).Interrupt(); } - const auto is_switch_pending = [this] { - KScopedSpinLock lk{guard}; - return state.needs_scheduling.load(); - }; - do { - auto next_thread = current_thread.load(); - if (next_thread != nullptr) { - const auto locked = next_thread->context_guard.try_lock(); - if (state.needs_scheduling.load()) { - next_thread->context_guard.unlock(); - break; - } - if (next_thread->GetActiveCore() != core_id) { - next_thread->context_guard.unlock(); - break; - } - if (!locked) { - continue; - } - } - auto thread = next_thread ? next_thread : idle_thread; - SetCurrentThread(system.Kernel(), thread); - Common::Fiber::YieldTo(switch_fiber, *thread->GetHostContext()); - } while (!is_switch_pending()); } } -void KScheduler::UpdateLastContextSwitchTime(KThread* thread, KProcess* process) { - const u64 prev_switch_ticks = last_context_switch_time; - const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks(); - const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks; - - if (thread != nullptr) { - thread->AddCpuTime(core_id, update_ticks); - } - - if (process != nullptr) { - process->UpdateCPUTimeTicks(update_ticks); - } - - last_context_switch_time = most_recent_switch_ticks; -} - -void KScheduler::Initialize() { - idle_thread = KThread::Create(system.Kernel()); - ASSERT(KThread::InitializeIdleThread(system, idle_thread, core_id).IsSuccess()); - idle_thread->SetName(fmt::format("IdleThread:{}", core_id)); - idle_thread->EnableDispatch(); -} - -KScopedSchedulerLock::KScopedSchedulerLock(KernelCore& kernel) - : KScopedLock(kernel.GlobalSchedulerContext().SchedulerLock()) {} - -KScopedSchedulerLock::~KScopedSchedulerLock() = default; - } // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler.h b/src/core/hle/kernel/k_scheduler.h index 6a4760eca..534321d8d 100644 --- a/src/core/hle/kernel/k_scheduler.h +++ b/src/core/hle/kernel/k_scheduler.h @@ -11,6 +11,7 @@ #include "core/hle/kernel/k_scheduler_lock.h" #include "core/hle/kernel/k_scoped_lock.h" #include "core/hle/kernel/k_spin_lock.h" +#include "core/hle/kernel/k_thread.h" namespace Common { class Fiber; @@ -23,184 +24,150 @@ class System; namespace Kernel { class KernelCore; +class KInterruptTaskManager; class KProcess; -class SchedulerLock; class KThread; +class KScopedDisableDispatch; +class KScopedSchedulerLock; +class KScopedSchedulerLockAndSleep; class KScheduler final { public: - explicit KScheduler(Core::System& system_, s32 core_id_); - ~KScheduler(); - - void Finalize(); + YUZU_NON_COPYABLE(KScheduler); + YUZU_NON_MOVEABLE(KScheduler); - /// Reschedules to the next available thread (call after current thread is suspended) - void RescheduleCurrentCore(); + using LockType = KAbstractSchedulerLock<KScheduler>; - /// Reschedules cores pending reschedule, to be called on EnableScheduling. - static void RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule); + explicit KScheduler(KernelCore& kernel); + ~KScheduler(); - /// The next two are for SingleCore Only. - /// Unload current thread before preempting core. + void Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id); + void Activate(); + void OnThreadStart(); void Unload(KThread* thread); - - /// Reload current thread after core preemption. void Reload(KThread* thread); - /// Gets the current running thread - [[nodiscard]] KThread* GetSchedulerCurrentThread() const; + void SetInterruptTaskRunnable(); + void RequestScheduleOnInterrupt(); + void PreemptSingleCore(); - /// Gets the idle thread - [[nodiscard]] KThread* GetIdleThread() const { - return idle_thread; + u64 GetIdleCount() { + return m_state.idle_count; } - /// Returns true if the scheduler is idle - [[nodiscard]] bool IsIdle() const { - return GetSchedulerCurrentThread() == idle_thread; + KThread* GetIdleThread() const { + return m_idle_thread; } - /// Gets the timestamp for the last context switch in ticks. - [[nodiscard]] u64 GetLastContextSwitchTicks() const; - - [[nodiscard]] bool ContextSwitchPending() const { - return state.needs_scheduling.load(std::memory_order_relaxed); + bool IsIdle() const { + return m_current_thread.load() == m_idle_thread; } - void Initialize(); + KThread* GetPreviousThread() const { + return m_state.prev_thread; + } - void OnThreadStart(); + KThread* GetSchedulerCurrentThread() const { + return m_current_thread.load(); + } - [[nodiscard]] std::shared_ptr<Common::Fiber>& ControlContext() { - return switch_fiber; + s64 GetLastContextSwitchTime() const { + return m_last_context_switch_time; } - [[nodiscard]] const std::shared_ptr<Common::Fiber>& ControlContext() const { - return switch_fiber; + // Static public API. + static bool CanSchedule(KernelCore& kernel) { + return GetCurrentThread(kernel).GetDisableDispatchCount() == 0; + } + static bool IsSchedulerLockedByCurrentThread(KernelCore& kernel) { + return kernel.GlobalSchedulerContext().scheduler_lock.IsLockedByCurrentThread(); } - [[nodiscard]] u64 UpdateHighestPriorityThread(KThread* highest_thread); + static bool IsSchedulerUpdateNeeded(KernelCore& kernel) { + return kernel.GlobalSchedulerContext().scheduler_update_needed; + } + static void SetSchedulerUpdateNeeded(KernelCore& kernel) { + kernel.GlobalSchedulerContext().scheduler_update_needed = true; + } + static void ClearSchedulerUpdateNeeded(KernelCore& kernel) { + kernel.GlobalSchedulerContext().scheduler_update_needed = false; + } - /** - * Takes a thread and moves it to the back of the it's priority list. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - static void YieldWithoutCoreMigration(KernelCore& kernel); + static void DisableScheduling(KernelCore& kernel); + static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling); - /** - * Takes a thread and moves it to the back of the it's priority list. - * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or - * a better priority than the next thread in the core. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - static void YieldWithCoreMigration(KernelCore& kernel); - - /** - * Takes a thread and moves it out of the scheduling queue. - * and into the suggested queue. If no thread can be scheduled afterwards in that core, - * a suggested thread is obtained instead. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - static void YieldToAnyThread(KernelCore& kernel); + static u64 UpdateHighestPriorityThreads(KernelCore& kernel); static void ClearPreviousThread(KernelCore& kernel, KThread* thread); - /// Notify the scheduler a thread's status has changed. static void OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state); - - /// Notify the scheduler a thread's priority has changed. static void OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority); - - /// Notify the scheduler a thread's core and/or affinity mask has changed. static void OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread, const KAffinityMask& old_affinity, s32 old_core); - static bool CanSchedule(KernelCore& kernel); - static bool IsSchedulerUpdateNeeded(const KernelCore& kernel); - static void SetSchedulerUpdateNeeded(KernelCore& kernel); - static void ClearSchedulerUpdateNeeded(KernelCore& kernel); - static void DisableScheduling(KernelCore& kernel); - static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling); - [[nodiscard]] static u64 UpdateHighestPriorityThreads(KernelCore& kernel); + static void RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 priority); + static void RescheduleCores(KernelCore& kernel, u64 cores_needing_scheduling); + + static void YieldWithoutCoreMigration(KernelCore& kernel); + static void YieldWithCoreMigration(KernelCore& kernel); + static void YieldToAnyThread(KernelCore& kernel); private: - friend class GlobalSchedulerContext; - - /** - * Takes care of selecting the new scheduled threads in three steps: - * - * 1. First a thread is selected from the top of the priority queue. If no thread - * is obtained then we move to step two, else we are done. - * - * 2. Second we try to get a suggested thread that's not assigned to any core or - * that is not the top thread in that core. - * - * 3. Third is no suggested thread is found, we do a second pass and pick a running - * thread in another core and swap it with its current thread. - * - * returns the cores needing scheduling. - */ - [[nodiscard]] static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel); - - [[nodiscard]] static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel); - - void RotateScheduledQueue(s32 cpu_core_id, s32 priority); + // Static private API. + static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel) { + return kernel.GlobalSchedulerContext().priority_queue; + } + static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel); - void Schedule(); + static void RescheduleCurrentHLEThread(KernelCore& kernel); - /// Switches the CPU's active thread context to that of the specified thread + // Instanced private API. void ScheduleImpl(); + void ScheduleImplFiber(); + void SwitchThread(KThread* next_thread); - /// When a thread wakes up, it must run this through it's new scheduler - void SwitchContextStep2(); - - /** - * Called on every context switch to update the internal timestamp - * This also updates the running time ticks for the given thread and - * process using the following difference: - * - * ticks += most_recent_ticks - last_context_switch_ticks - * - * The internal tick timestamp for the scheduler is simply the - * most recent tick count retrieved. No special arithmetic is - * applied to it. - */ - void UpdateLastContextSwitchTime(KThread* thread, KProcess* process); - - void SwitchToCurrent(); + void Schedule(); + void ScheduleOnInterrupt(); - KThread* prev_thread{}; - std::atomic<KThread*> current_thread{}; + void RescheduleOtherCores(u64 cores_needing_scheduling); + void RescheduleCurrentCore(); + void RescheduleCurrentCoreImpl(); - KThread* idle_thread{}; + u64 UpdateHighestPriorityThread(KThread* thread); - std::shared_ptr<Common::Fiber> switch_fiber{}; +private: + friend class KScopedDisableDispatch; struct SchedulingState { - std::atomic<bool> needs_scheduling{}; - bool interrupt_task_thread_runnable{}; - bool should_count_idle{}; - u64 idle_count{}; - KThread* highest_priority_thread{}; - void* idle_thread_stack{}; + std::atomic<bool> needs_scheduling{false}; + bool interrupt_task_runnable{false}; + bool should_count_idle{false}; + u64 idle_count{0}; + KThread* highest_priority_thread{nullptr}; + void* idle_thread_stack{nullptr}; + std::atomic<KThread*> prev_thread{nullptr}; + KInterruptTaskManager* interrupt_task_manager{nullptr}; }; - SchedulingState state; - - Core::System& system; - u64 last_context_switch_time{}; - const s32 core_id; - - KSpinLock guard{}; + KernelCore& kernel; + SchedulingState m_state; + bool m_is_active{false}; + s32 m_core_id{0}; + s64 m_last_context_switch_time{0}; + KThread* m_idle_thread{nullptr}; + std::atomic<KThread*> m_current_thread{nullptr}; + + std::shared_ptr<Common::Fiber> m_switch_fiber{}; + KThread* m_switch_cur_thread{}; + KThread* m_switch_highest_priority_thread{}; + bool m_switch_from_schedule{}; }; -class [[nodiscard]] KScopedSchedulerLock : KScopedLock<GlobalSchedulerContext::LockType> { +class KScopedSchedulerLock : public KScopedLock<KScheduler::LockType> { public: - explicit KScopedSchedulerLock(KernelCore& kernel); - ~KScopedSchedulerLock(); + explicit KScopedSchedulerLock(KernelCore& kernel) + : KScopedLock(kernel.GlobalSchedulerContext().scheduler_lock) {} + ~KScopedSchedulerLock() = default; }; } // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler_lock.h b/src/core/hle/kernel/k_scheduler_lock.h index 4fa256970..73314b45e 100644 --- a/src/core/hle/kernel/k_scheduler_lock.h +++ b/src/core/hle/kernel/k_scheduler_lock.h @@ -5,9 +5,11 @@ #include <atomic> #include "common/assert.h" +#include "core/hle/kernel/k_interrupt_manager.h" #include "core/hle/kernel/k_spin_lock.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/physical_core.h" namespace Kernel { diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 50cb5fc90..174afc80d 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -258,7 +258,18 @@ Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_ } Result KThread::InitializeDummyThread(KThread* thread) { - return thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy); + // Initialize the thread. + R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy)); + + // Initialize emulation parameters. + thread->stack_parameters.disable_count = 0; + + return ResultSuccess; +} + +Result KThread::InitializeMainThread(Core::System& system, KThread* thread, s32 virt_core) { + return InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main, + system.GetCpuManager().GetGuestActivateFunc()); } Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) { @@ -277,7 +288,7 @@ Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThr KProcess* owner) { system.Kernel().GlobalSchedulerContext().AddThread(thread); return InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner, - ThreadType::User, system.GetCpuManager().GetGuestThreadStartFunc()); + ThreadType::User, system.GetCpuManager().GetGuestThreadFunc()); } void KThread::PostDestroy(uintptr_t arg) { @@ -308,14 +319,20 @@ void KThread::Finalize() { auto it = waiter_list.begin(); while (it != waiter_list.end()) { - // Clear the lock owner - it->SetLockOwner(nullptr); + // Get the thread. + KThread* const waiter = std::addressof(*it); + + // The thread shouldn't be a kernel waiter. + ASSERT(!IsKernelAddressKey(waiter->GetAddressKey())); + + // Clear the lock owner. + waiter->SetLockOwner(nullptr); // Erase the waiter from our list. it = waiter_list.erase(it); // Cancel the thread's wait. - it->CancelWait(ResultInvalidState, true); + waiter->CancelWait(ResultInvalidState, true); } } @@ -1052,6 +1069,8 @@ void KThread::Exit() { // Register the thread as a work task. KWorkerTaskManager::AddTask(kernel, KWorkerTaskManager::WorkerType::Exit, this); } + + UNREACHABLE_MSG("KThread::Exit() would return"); } Result KThread::Sleep(s64 timeout) { @@ -1087,6 +1106,8 @@ void KThread::IfDummyThreadTryWait() { return; } + ASSERT(!kernel.IsPhantomModeForSingleCore()); + // Block until we are no longer waiting. std::unique_lock lk(dummy_wait_lock); dummy_wait_cv.wait( @@ -1191,16 +1212,13 @@ KScopedDisableDispatch::~KScopedDisableDispatch() { return; } - // Skip the reschedule if single-core, as dispatch tracking is disabled here. - if (!Settings::values.use_multi_core.GetValue()) { - return; - } - if (GetCurrentThread(kernel).GetDisableDispatchCount() <= 1) { - auto scheduler = kernel.CurrentScheduler(); + auto* scheduler = kernel.CurrentScheduler(); - if (scheduler) { + if (scheduler && !kernel.IsPhantomModeForSingleCore()) { scheduler->RescheduleCurrentCore(); + } else { + KScheduler::RescheduleCurrentHLEThread(kernel); } } else { GetCurrentThread(kernel).EnableDispatch(); diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index 28cd7ecb0..9ee20208e 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -413,6 +413,9 @@ public: [[nodiscard]] static Result InitializeDummyThread(KThread* thread); + [[nodiscard]] static Result InitializeMainThread(Core::System& system, KThread* thread, + s32 virt_core); + [[nodiscard]] static Result InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core); @@ -480,39 +483,16 @@ public: return per_core_priority_queue_entry[core]; } - [[nodiscard]] bool IsKernelThread() const { - return GetActiveCore() == 3; - } - - [[nodiscard]] bool IsDispatchTrackingDisabled() const { - return is_single_core || IsKernelThread(); - } - [[nodiscard]] s32 GetDisableDispatchCount() const { - if (IsDispatchTrackingDisabled()) { - // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch. - return 1; - } - return this->GetStackParameters().disable_count; } void DisableDispatch() { - if (IsDispatchTrackingDisabled()) { - // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch. - return; - } - ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0); this->GetStackParameters().disable_count++; } void EnableDispatch() { - if (IsDispatchTrackingDisabled()) { - // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch. - return; - } - ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0); this->GetStackParameters().disable_count--; } diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 7307cf262..f4072e1c3 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -64,8 +64,6 @@ struct KernelCore::Impl { is_phantom_mode_for_singlecore = false; - InitializePhysicalCores(); - // Derive the initial memory layout from the emulated board Init::InitializeSlabResourceCounts(kernel); DeriveInitialMemoryLayout(); @@ -75,9 +73,9 @@ struct KernelCore::Impl { InitializeSystemResourceLimit(kernel, system.CoreTiming()); InitializeMemoryLayout(); Init::InitializeKPageBufferSlabHeap(system); - InitializeSchedulers(); InitializeShutdownThreads(); InitializePreemption(kernel); + InitializePhysicalCores(); RegisterHostThread(); } @@ -95,19 +93,7 @@ struct KernelCore::Impl { process_list.clear(); - // Close all open server sessions and ports. - std::unordered_set<KAutoObject*> server_objects_; - { - std::scoped_lock lk(server_objects_lock); - server_objects_ = server_objects; - server_objects.clear(); - } - for (auto* server_object : server_objects_) { - server_object->Close(); - } - - // Ensures all service threads gracefully shutdown. - ClearServiceThreads(); + CloseServices(); next_object_id = 0; next_kernel_process_id = KProcess::InitialKIPIDMin; @@ -148,7 +134,6 @@ struct KernelCore::Impl { shutdown_threads[core_id] = nullptr; } - schedulers[core_id]->Finalize(); schedulers[core_id].reset(); } @@ -191,18 +176,41 @@ struct KernelCore::Impl { global_object_list_container.reset(); } + void CloseServices() { + // Close all open server sessions and ports. + std::unordered_set<KAutoObject*> server_objects_; + { + std::scoped_lock lk(server_objects_lock); + server_objects_ = server_objects; + server_objects.clear(); + } + for (auto* server_object : server_objects_) { + server_object->Close(); + } + + // Ensures all service threads gracefully shutdown. + ClearServiceThreads(); + } + void InitializePhysicalCores() { exclusive_monitor = Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { - schedulers[i] = std::make_unique<Kernel::KScheduler>(system, i); + const s32 core{static_cast<s32>(i)}; + + schedulers[i] = std::make_unique<Kernel::KScheduler>(system.Kernel()); cores.emplace_back(i, system, *schedulers[i], interrupts); - } - } - void InitializeSchedulers() { - for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { - cores[i].Scheduler().Initialize(); + auto* main_thread{Kernel::KThread::Create(system.Kernel())}; + main_thread->SetName(fmt::format("MainThread:{}", core)); + main_thread->SetCurrentCore(core); + ASSERT(Kernel::KThread::InitializeMainThread(system, main_thread, core).IsSuccess()); + + auto* idle_thread{Kernel::KThread::Create(system.Kernel())}; + idle_thread->SetCurrentCore(core); + ASSERT(Kernel::KThread::InitializeIdleThread(system, idle_thread, core).IsSuccess()); + + schedulers[i]->Initialize(main_thread, idle_thread, core); } } @@ -813,6 +821,10 @@ void KernelCore::Shutdown() { impl->Shutdown(); } +void KernelCore::CloseServices() { + impl->CloseServices(); +} + const KResourceLimit* KernelCore::GetSystemResourceLimit() const { return impl->system_resource_limit; } @@ -1101,10 +1113,11 @@ void KernelCore::Suspend(bool suspended) { } void KernelCore::ShutdownCores() { + KScopedSchedulerLock lk{*this}; + for (auto* thread : impl->shutdown_threads) { void(thread->Run()); } - InterruptAllPhysicalCores(); } bool KernelCore::IsMulticore() const { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index aa0ebaa02..6c7cf6af2 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -109,6 +109,9 @@ public: /// Clears all resources in use by the kernel instance. void Shutdown(); + /// Close all active services in use by the kernel instance. + void CloseServices(); + /// Retrieves a shared pointer to the system resource limit instance. const KResourceLimit* GetSystemResourceLimit() const; diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index a5b16ae2e..6e7dacf97 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -43,6 +43,7 @@ void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) { void PhysicalCore::Run() { arm_interface->Run(); + arm_interface->ClearExclusiveState(); } void PhysicalCore::Idle() { diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 8655506b0..27e5a805d 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -887,7 +887,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han const auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); const bool same_thread = current_thread == thread.GetPointerUnsafe(); - const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks(); + const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTime(); u64 out_ticks = 0; if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { const u64 thread_ticks = current_thread->GetCpuTime(); @@ -3026,11 +3026,6 @@ void Call(Core::System& system, u32 immediate) { } kernel.ExitSVCProfile(); - - if (!thread->IsCallingSvc()) { - auto* host_context = thread->GetHostContext().get(); - host_context->Rewind(); - } } } // namespace Kernel::Svc diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index b726ac27a..def105832 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -290,7 +290,7 @@ protected: void Get(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ACC, "called user_id=0x{}", user_id.RawString()); ProfileBase profile_base{}; - ProfileData data{}; + UserData data{}; if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) { ctx.WriteBuffer(data); IPC::ResponseBuilder rb{ctx, 16}; @@ -373,18 +373,18 @@ protected: reinterpret_cast<const char*>(base.username.data()), base.username.size()), base.timestamp, base.user_uuid.RawString()); - if (user_data.size() < sizeof(ProfileData)) { - LOG_ERROR(Service_ACC, "ProfileData buffer too small!"); + if (user_data.size() < sizeof(UserData)) { + LOG_ERROR(Service_ACC, "UserData buffer too small!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERR_INVALID_BUFFER); return; } - ProfileData data; - std::memcpy(&data, user_data.data(), sizeof(ProfileData)); + UserData data; + std::memcpy(&data, user_data.data(), sizeof(UserData)); if (!profile_manager.SetProfileBaseAndData(user_id, base, data)) { - LOG_ERROR(Service_ACC, "Failed to update profile data and base!"); + LOG_ERROR(Service_ACC, "Failed to update user data and base!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERR_FAILED_SAVE_DATA); return; @@ -406,15 +406,15 @@ protected: reinterpret_cast<const char*>(base.username.data()), base.username.size()), base.timestamp, base.user_uuid.RawString()); - if (user_data.size() < sizeof(ProfileData)) { - LOG_ERROR(Service_ACC, "ProfileData buffer too small!"); + if (user_data.size() < sizeof(UserData)) { + LOG_ERROR(Service_ACC, "UserData buffer too small!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERR_INVALID_BUFFER); return; } - ProfileData data; - std::memcpy(&data, user_data.data(), sizeof(ProfileData)); + UserData data; + std::memcpy(&data, user_data.data(), sizeof(UserData)); Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Write, Common::FS::FileType::BinaryFile); diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 8118ead33..a58da4d5f 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -22,7 +22,7 @@ struct UserRaw { UUID uuid2{}; u64 timestamp{}; ProfileUsername username{}; - ProfileData extra_data{}; + UserData extra_data{}; }; static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size."); @@ -263,7 +263,7 @@ UUID ProfileManager::GetLastOpenedUser() const { /// Return the users profile base and the unknown arbitary data. bool ProfileManager::GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile, - ProfileData& data) const { + UserData& data) const { if (GetProfileBase(index, profile)) { data = profiles[*index].data; return true; @@ -272,15 +272,14 @@ bool ProfileManager::GetProfileBaseAndData(std::optional<std::size_t> index, Pro } /// Return the users profile base and the unknown arbitary data. -bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile, - ProfileData& data) const { +bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile, UserData& data) const { const auto idx = GetUserIndex(uuid); return GetProfileBaseAndData(idx, profile, data); } /// Return the users profile base and the unknown arbitary data. bool ProfileManager::GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile, - ProfileData& data) const { + UserData& data) const { return GetProfileBaseAndData(user.user_uuid, profile, data); } @@ -318,7 +317,7 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) { } bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, - const ProfileData& data_new) { + const UserData& data_new) { const auto index = GetUserIndex(uuid); if (index.has_value() && SetProfileBase(uuid, profile_new)) { profiles[*index].data = data_new; diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index 9940957f1..135f7d0d5 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h @@ -22,7 +22,7 @@ using UserIDArray = std::array<Common::UUID, MAX_USERS>; /// Contains extra data related to a user. /// TODO: RE this structure -struct ProfileData { +struct UserData { INSERT_PADDING_WORDS_NOINIT(1); u32 icon_id; u8 bg_color_id; @@ -30,7 +30,7 @@ struct ProfileData { INSERT_PADDING_BYTES_NOINIT(0x10); INSERT_PADDING_BYTES_NOINIT(0x60); }; -static_assert(sizeof(ProfileData) == 0x80, "ProfileData structure has incorrect size"); +static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size"); /// This holds general information about a users profile. This is where we store all the information /// based on a specific user @@ -38,7 +38,7 @@ struct ProfileInfo { Common::UUID user_uuid{}; ProfileUsername username{}; u64 creation_time{}; - ProfileData data{}; // TODO(ognik): Work out what this is + UserData data{}; // TODO(ognik): Work out what this is bool is_open{}; }; @@ -74,10 +74,9 @@ public: bool GetProfileBase(Common::UUID uuid, ProfileBase& profile) const; bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const; bool GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile, - ProfileData& data) const; - bool GetProfileBaseAndData(Common::UUID uuid, ProfileBase& profile, ProfileData& data) const; - bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile, - ProfileData& data) const; + UserData& data) const; + bool GetProfileBaseAndData(Common::UUID uuid, ProfileBase& profile, UserData& data) const; + bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile, UserData& data) const; std::size_t GetUserCount() const; std::size_t GetOpenUserCount() const; bool UserExists(Common::UUID uuid) const; @@ -93,7 +92,7 @@ public: bool RemoveUser(Common::UUID uuid); bool SetProfileBase(Common::UUID uuid, const ProfileBase& profile_new); bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, - const ProfileData& data_new); + const UserData& data_new); private: void ParseUserSaveFile(); diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index d35644e73..118f226e4 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -5,7 +5,6 @@ #include <array> #include <cinttypes> #include <cstring> -#include "audio_core/audio_renderer.h" #include "common/settings.h" #include "core/core.h" #include "core/file_sys/control_metadata.h" @@ -238,6 +237,7 @@ IDebugFunctions::IDebugFunctions(Core::System& system_) {130, nullptr, "FriendInvitationSetApplicationParameter"}, {131, nullptr, "FriendInvitationClearApplicationParameter"}, {132, nullptr, "FriendInvitationPushApplicationParameter"}, + {140, nullptr, "RestrictPowerOperationForSecureLaunchModeForDebug"}, {900, nullptr, "GetGrcProcessLaunchedSystemEvent"}, }; // clang-format on @@ -285,7 +285,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"}, {64, nullptr, "SetInputDetectionSourceSet"}, - {65, nullptr, "ReportUserIsActive"}, + {65, &ISelfController::ReportUserIsActive, "ReportUserIsActive"}, {66, nullptr, "GetCurrentIlluminance"}, {67, nullptr, "IsIlluminanceAvailable"}, {68, &ISelfController::SetAutoSleepDisabled, "SetAutoSleepDisabled"}, @@ -517,6 +517,13 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c rb.Push<u32>(idle_time_detection_extension); } +void ISelfController::ReportUserIsActive(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + void ISelfController::SetAutoSleepDisabled(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; is_auto_sleep_disabled = rp.Pop<bool>(); @@ -635,6 +642,10 @@ void AppletMessageQueue::RequestExit() { PushMessage(AppletMessage::Exit); } +void AppletMessageQueue::RequestResume() { + PushMessage(AppletMessage::Resume); +} + void AppletMessageQueue::FocusStateChanged() { PushMessage(AppletMessage::FocusStateChanged); } @@ -1310,6 +1321,8 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) {33, &IApplicationFunctions::EndBlockingHomeButton, "EndBlockingHomeButton"}, {34, nullptr, "SelectApplicationLicense"}, {35, nullptr, "GetDeviceSaveDataSizeMax"}, + {36, nullptr, "GetLimitedApplicationLicense"}, + {37, nullptr, "GetLimitedApplicationLicenseUpgradableEvent"}, {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"}, {50, &IApplicationFunctions::GetPseudoDeviceId, "GetPseudoDeviceId"}, {60, nullptr, "SetMediaPlaybackStateForApplication"}, diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 988ead215..bb75c6281 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -90,6 +90,7 @@ public: AppletMessage PopMessage(); std::size_t GetMessageCount() const; void RequestExit(); + void RequestResume(); void FocusStateChanged(); void OperationModeChanged(); @@ -174,6 +175,7 @@ private: void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); + void ReportUserIsActive(Kernel::HLERequestContext& ctx); void SetAutoSleepDisabled(Kernel::HLERequestContext& ctx); void IsAutoSleepDisabled(Kernel::HLERequestContext& ctx); void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 18d3ae682..48a9a73a0 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp @@ -1,68 +1,211 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "audio_core/in/audio_in_system.h" +#include "audio_core/renderer/audio_device.h" +#include "common/common_funcs.h" #include "common/logging/log.h" +#include "common/string_util.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_event.h" #include "core/hle/service/audio/audin_u.h" namespace Service::Audio { +using namespace AudioCore::AudioIn; -IAudioIn::IAudioIn(Core::System& system_) - : ServiceFramework{system_, "IAudioIn"}, service_context{system_, "IAudioIn"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "GetAudioInState"}, - {1, &IAudioIn::Start, "Start"}, - {2, nullptr, "Stop"}, - {3, nullptr, "AppendAudioInBuffer"}, - {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"}, - {5, nullptr, "GetReleasedAudioInBuffer"}, - {6, nullptr, "ContainsAudioInBuffer"}, - {7, nullptr, "AppendUacInBuffer"}, - {8, &IAudioIn::AppendAudioInBufferAuto, "AppendAudioInBufferAuto"}, - {9, nullptr, "GetReleasedAudioInBuffersAuto"}, - {10, nullptr, "AppendUacInBufferAuto"}, - {11, nullptr, "GetAudioInBufferCount"}, - {12, nullptr, "SetDeviceGain"}, - {13, nullptr, "GetDeviceGain"}, - {14, nullptr, "FlushAudioInBuffers"}, - }; - // clang-format on +class IAudioIn final : public ServiceFramework<IAudioIn> { +public: + explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id, + std::string& device_name, const AudioInParameter& in_params, u32 handle, + u64 applet_resource_user_id) + : ServiceFramework{system_, "IAudioIn"}, + service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")}, + impl{std::make_shared<In>(system_, manager, event, session_id)} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IAudioIn::GetAudioInState, "GetAudioInState"}, + {1, &IAudioIn::Start, "Start"}, + {2, &IAudioIn::Stop, "Stop"}, + {3, &IAudioIn::AppendAudioInBuffer, "AppendAudioInBuffer"}, + {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"}, + {5, &IAudioIn::GetReleasedAudioInBuffer, "GetReleasedAudioInBuffer"}, + {6, &IAudioIn::ContainsAudioInBuffer, "ContainsAudioInBuffer"}, + {7, &IAudioIn::AppendAudioInBuffer, "AppendUacInBuffer"}, + {8, &IAudioIn::AppendAudioInBuffer, "AppendAudioInBufferAuto"}, + {9, &IAudioIn::GetReleasedAudioInBuffer, "GetReleasedAudioInBuffersAuto"}, + {10, &IAudioIn::AppendAudioInBuffer, "AppendUacInBufferAuto"}, + {11, &IAudioIn::GetAudioInBufferCount, "GetAudioInBufferCount"}, + {12, &IAudioIn::SetDeviceGain, "SetDeviceGain"}, + {13, &IAudioIn::GetDeviceGain, "GetDeviceGain"}, + {14, &IAudioIn::FlushAudioInBuffers, "FlushAudioInBuffers"}, + }; + // clang-format on - RegisterHandlers(functions); + RegisterHandlers(functions); - buffer_event = service_context.CreateEvent("IAudioIn:BufferEvent"); -} + if (impl->GetSystem() + .Initialize(device_name, in_params, handle, applet_resource_user_id) + .IsError()) { + LOG_ERROR(Service_Audio, "Failed to initialize the AudioIn System!"); + } + } -IAudioIn::~IAudioIn() { - service_context.CloseEvent(buffer_event); -} + ~IAudioIn() override { + impl->Free(); + service_context.CloseEvent(event); + } -void IAudioIn::Start(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + [[nodiscard]] std::shared_ptr<In> GetImpl() { + return impl; + } - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} +private: + void GetAudioInState(Kernel::HLERequestContext& ctx) { + const auto state = static_cast<u32>(impl->GetState()); -void IAudioIn::RegisterBufferEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called. State={}", state); - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(buffer_event->GetReadableEvent()); -} + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(state); + } -void IAudioIn::AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + void Start(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} + auto result = impl->StartSystem(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void Stop(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + + auto result = impl->StopSystem(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void AppendAudioInBuffer(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + u64 tag = rp.PopRaw<u64>(); -AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} { + const auto in_buffer_size{ctx.GetReadBufferSize()}; + if (in_buffer_size < sizeof(AudioInBuffer)) { + LOG_ERROR(Service_Audio, "Input buffer is too small for an AudioInBuffer!"); + } + + const auto& in_buffer = ctx.ReadBuffer(); + AudioInBuffer buffer{}; + std::memcpy(&buffer, in_buffer.data(), sizeof(AudioInBuffer)); + + [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; + LOG_TRACE(Service_Audio, "called. Session {} Appending buffer {:08X}", sessionid, tag); + + auto result = impl->AppendBuffer(buffer, tag); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + + auto& buffer_event = impl->GetBufferEvent(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(buffer_event); + } + + void GetReleasedAudioInBuffer(Kernel::HLERequestContext& ctx) { + auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64); + std::vector<u64> released_buffers(write_buffer_size, 0); + + auto count = impl->GetReleasedBuffers(released_buffers); + + [[maybe_unused]] std::string tags{}; + for (u32 i = 0; i < count; i++) { + tags += fmt::format("{:08X}, ", released_buffers[i]); + } + [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; + LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count, + tags); + + ctx.WriteBuffer(released_buffers); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(count); + } + + void ContainsAudioInBuffer(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const u64 tag{rp.Pop<u64>()}; + const auto buffer_queued{impl->ContainsAudioBuffer(tag)}; + + LOG_DEBUG(Service_Audio, "called. Is buffer {:08X} registered? {}", tag, buffer_queued); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(buffer_queued); + } + + void GetAudioInBufferCount(Kernel::HLERequestContext& ctx) { + const auto buffer_count = impl->GetBufferCount(); + + LOG_DEBUG(Service_Audio, "called. Buffer count={}", buffer_count); + + IPC::ResponseBuilder rb{ctx, 3}; + + rb.Push(ResultSuccess); + rb.Push(buffer_count); + } + + void SetDeviceGain(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto volume{rp.Pop<f32>()}; + LOG_DEBUG(Service_Audio, "called. Gain {}", volume); + + impl->SetVolume(volume); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetDeviceGain(Kernel::HLERequestContext& ctx) { + auto volume{impl->GetVolume()}; + + LOG_DEBUG(Service_Audio, "called. Gain {}", volume); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(volume); + } + + void FlushAudioInBuffers(Kernel::HLERequestContext& ctx) { + bool flushed{impl->FlushAudioInBuffers()}; + + LOG_DEBUG(Service_Audio, "called. Were any buffers flushed? {}", flushed); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(flushed); + } + + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* event; + std::shared_ptr<AudioCore::AudioIn::In> impl; +}; + +AudInU::AudInU(Core::System& system_) + : ServiceFramework{system_, "audin:u", ServiceThreadType::CreateNew}, + service_context{system_, "AudInU"}, impl{std::make_unique<AudioCore::AudioIn::Manager>( + system_)} { // clang-format off static const FunctionInfo functions[] = { {0, &AudInU::ListAudioIns, "ListAudioIns"}, @@ -80,59 +223,152 @@ AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} { AudInU::~AudInU() = default; void AudInU::ListAudioIns(Kernel::HLERequestContext& ctx) { + using namespace AudioCore::AudioRenderer; + LOG_DEBUG(Service_Audio, "called"); - const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioInDeviceName); - const std::size_t device_count = std::min(count, audio_device_names.size()); - std::vector<AudioInDeviceName> device_names; - device_names.reserve(device_count); + const auto write_count = + static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); + std::vector<AudioDevice::AudioDeviceName> device_names{}; - for (std::size_t i = 0; i < device_count; i++) { - const auto& device_name = audio_device_names[i]; - auto& entry = device_names.emplace_back(); - device_name.copy(entry.data(), device_name.size()); + u32 out_count{0}; + if (write_count > 0) { + out_count = impl->GetDeviceNames(device_names, write_count, false); + ctx.WriteBuffer(device_names); } - ctx.WriteBuffer(device_names); - IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast<u32>(device_names.size())); + rb.Push(out_count); } void AudInU::ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx) { + using namespace AudioCore::AudioRenderer; + LOG_DEBUG(Service_Audio, "called"); - constexpr u32 device_count = 0; - // Since we don't actually use any other audio input devices, we return 0 devices. Filtered - // device listing just omits the default input device + const auto write_count = + static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); + std::vector<AudioDevice::AudioDeviceName> device_names{}; + + u32 out_count{0}; + if (write_count > 0) { + out_count = impl->GetDeviceNames(device_names, write_count, true); + ctx.WriteBuffer(device_names); + } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast<u32>(device_count)); + rb.Push(out_count); } -void AudInU::OpenInOutImpl(Kernel::HLERequestContext& ctx) { - AudInOutParams params{}; - params.channel_count = 2; - params.sample_format = SampleFormat::PCM16; - params.sample_rate = 48000; - params.state = State::Started; +void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto in_params{rp.PopRaw<AudioInParameter>()}; + auto applet_resource_user_id{rp.PopRaw<u64>()}; + const auto device_name_data{ctx.ReadBuffer()}; + auto device_name = Common::StringFromBuffer(device_name_data); + auto handle{ctx.GetCopyHandle(0)}; + + std::scoped_lock l{impl->mutex}; + auto link{impl->LinkToManager()}; + if (link.IsError()) { + LOG_ERROR(Service_Audio, "Failed to link Audio In to Audio Manager"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(link); + return; + } + + size_t new_session_id{}; + auto result{impl->AcquireSessionId(new_session_id)}; + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id, + impl->num_free_sessions); + + auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name, + in_params, handle, applet_resource_user_id); + impl->sessions[new_session_id] = audio_in->GetImpl(); + impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; + + auto& out_system = impl->sessions[new_session_id]->GetSystem(); + AudioInParameterInternal out_params{.sample_rate = out_system.GetSampleRate(), + .channel_count = out_system.GetChannelCount(), + .sample_format = + static_cast<u32>(out_system.GetSampleFormat()), + .state = static_cast<u32>(out_system.GetState())}; IPC::ResponseBuilder rb{ctx, 6, 0, 1}; - rb.Push(ResultSuccess); - rb.PushRaw<AudInOutParams>(params); - rb.PushIpcInterface<IAudioIn>(system); -} -void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); - OpenInOutImpl(ctx); + std::string out_name{out_system.GetName()}; + ctx.WriteBuffer(out_name); + + rb.Push(ResultSuccess); + rb.PushRaw<AudioInParameterInternal>(out_params); + rb.PushIpcInterface<IAudioIn>(audio_in); } void AudInU::OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); - OpenInOutImpl(ctx); + IPC::RequestParser rp{ctx}; + auto protocol_specified{rp.PopRaw<u64>()}; + auto in_params{rp.PopRaw<AudioInParameter>()}; + auto applet_resource_user_id{rp.PopRaw<u64>()}; + const auto device_name_data{ctx.ReadBuffer()}; + auto device_name = Common::StringFromBuffer(device_name_data); + auto handle{ctx.GetCopyHandle(0)}; + + std::scoped_lock l{impl->mutex}; + auto link{impl->LinkToManager()}; + if (link.IsError()) { + LOG_ERROR(Service_Audio, "Failed to link Audio In to Audio Manager"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(link); + return; + } + + size_t new_session_id{}; + auto result{impl->AcquireSessionId(new_session_id)}; + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id, + impl->num_free_sessions); + + auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name, + in_params, handle, applet_resource_user_id); + impl->sessions[new_session_id] = audio_in->GetImpl(); + impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; + + auto& out_system = impl->sessions[new_session_id]->GetSystem(); + AudioInParameterInternal out_params{.sample_rate = out_system.GetSampleRate(), + .channel_count = out_system.GetChannelCount(), + .sample_format = + static_cast<u32>(out_system.GetSampleFormat()), + .state = static_cast<u32>(out_system.GetState())}; + + IPC::ResponseBuilder rb{ctx, 6, 0, 1}; + + std::string out_name{out_system.GetName()}; + if (protocol_specified == 0) { + if (out_system.IsUac()) { + out_name = "UacIn"; + } else { + out_name = "DeviceIn"; + } + } + + ctx.WriteBuffer(out_name); + + rb.Push(ResultSuccess); + rb.PushRaw<AudioInParameterInternal>(out_params); + rb.PushIpcInterface<IAudioIn>(audio_in); } } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h index 2bfa38ecc..b45fda78a 100644 --- a/src/core/hle/service/audio/audin_u.h +++ b/src/core/hle/service/audio/audin_u.h @@ -3,6 +3,8 @@ #pragma once +#include "audio_core/audio_in_manager.h" +#include "audio_core/in/audio_in.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/service.h" @@ -14,22 +16,12 @@ namespace Kernel { class HLERequestContext; } -namespace Service::Audio { - -class IAudioIn final : public ServiceFramework<IAudioIn> { -public: - explicit IAudioIn(Core::System& system_); - ~IAudioIn() override; - -private: - void Start(Kernel::HLERequestContext& ctx); - void RegisterBufferEvent(Kernel::HLERequestContext& ctx); - void AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx); - - KernelHelpers::ServiceContext service_context; +namespace AudioCore::AudioOut { +class Manager; +class In; +} // namespace AudioCore::AudioOut - Kernel::KEvent* buffer_event; -}; +namespace Service::Audio { class AudInU final : public ServiceFramework<AudInU> { public: @@ -37,33 +29,14 @@ public: ~AudInU() override; private: - enum class SampleFormat : u32_le { - PCM16 = 2, - }; - - enum class State : u32_le { - Started = 0, - Stopped = 1, - }; - - struct AudInOutParams { - u32_le sample_rate{}; - u32_le channel_count{}; - SampleFormat sample_format{}; - State state{}; - }; - static_assert(sizeof(AudInOutParams) == 0x10, "AudInOutParams is an invalid size"); - - using AudioInDeviceName = std::array<char, 256>; - static constexpr std::array<std::string_view, 1> audio_device_names{{ - "BuiltInHeadset", - }}; - void ListAudioIns(Kernel::HLERequestContext& ctx); void ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx); void OpenInOutImpl(Kernel::HLERequestContext& ctx); void OpenAudioIn(Kernel::HLERequestContext& ctx); void OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx); + + KernelHelpers::ServiceContext service_context; + std::unique_ptr<AudioCore::AudioIn::Manager> impl; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index b0dad6053..a44dd842a 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -5,56 +5,43 @@ #include <cstring> #include <vector> -#include "audio_core/audio_out.h" -#include "audio_core/codec.h" +#include "audio_core/out/audio_out_system.h" +#include "audio_core/renderer/audio_device.h" #include "common/common_funcs.h" #include "common/logging/log.h" +#include "common/string_util.h" #include "common/swap.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_event.h" #include "core/hle/service/audio/audout_u.h" #include "core/hle/service/audio/errors.h" -#include "core/hle/service/kernel_helpers.h" #include "core/memory.h" namespace Service::Audio { - -constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}}; -constexpr int DefaultSampleRate{48000}; - -struct AudoutParams { - s32_le sample_rate; - u16_le channel_count; - INSERT_PADDING_BYTES_NOINIT(2); -}; -static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size"); - -enum class AudioState : u32 { - Started, - Stopped, -}; +using namespace AudioCore::AudioOut; class IAudioOut final : public ServiceFramework<IAudioOut> { public: - explicit IAudioOut(Core::System& system_, AudoutParams audio_params_, - AudioCore::AudioOut& audio_core_, std::string&& device_name_, - std::string&& unique_name) + explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager, + size_t session_id, std::string& device_name, + const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id) : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew}, - audio_core{audio_core_}, device_name{std::move(device_name_)}, - audio_params{audio_params_}, main_memory{system.Memory()}, service_context{system_, - "IAudioOut"} { + service_context{system_, "IAudioOut"}, event{service_context.CreateEvent( + "AudioOutEvent")}, + impl{std::make_shared<AudioCore::AudioOut::Out>(system_, manager, event, session_id)} { + // clang-format off static const FunctionInfo functions[] = { {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, - {1, &IAudioOut::StartAudioOut, "Start"}, - {2, &IAudioOut::StopAudioOut, "Stop"}, - {3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"}, + {1, &IAudioOut::Start, "Start"}, + {2, &IAudioOut::Stop, "Stop"}, + {3, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBuffer"}, {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, - {5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffers"}, + {5, &IAudioOut::GetReleasedAudioOutBuffers, "GetReleasedAudioOutBuffers"}, {6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"}, - {7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"}, - {8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"}, + {7, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBufferAuto"}, + {8, &IAudioOut::GetReleasedAudioOutBuffers, "GetReleasedAudioOutBuffersAuto"}, {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"}, {10, &IAudioOut::GetAudioOutPlayedSampleCount, "GetAudioOutPlayedSampleCount"}, {11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"}, @@ -64,241 +51,263 @@ public: // clang-format on RegisterHandlers(functions); - // This is the event handle used to check if the audio buffer was released - buffer_event = service_context.CreateEvent("IAudioOutBufferReleased"); - - stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate, - audio_params.channel_count, std::move(unique_name), [this] { - const auto guard = LockService(); - buffer_event->GetWritableEvent().Signal(); - }); + if (impl->GetSystem() + .Initialize(device_name, in_params, handle, applet_resource_user_id) + .IsError()) { + LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!"); + } } ~IAudioOut() override { - service_context.CloseEvent(buffer_event); + impl->Free(); + service_context.CloseEvent(event); } -private: - struct AudioBuffer { - u64_le next; - u64_le buffer; - u64_le buffer_capacity; - u64_le buffer_size; - u64_le offset; - }; - static_assert(sizeof(AudioBuffer) == 0x28, "AudioBuffer is an invalid size"); + [[nodiscard]] std::shared_ptr<AudioCore::AudioOut::Out> GetImpl() { + return impl; + } +private: void GetAudioOutState(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto state = static_cast<u32>(impl->GetState()); + + LOG_DEBUG(Service_Audio, "called. State={}", state); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast<u32>(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped)); + rb.Push(state); } - void StartAudioOut(Kernel::HLERequestContext& ctx) { + void Start(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); - if (stream->IsPlaying()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERR_OPERATION_FAILED); - return; - } - - audio_core.StartStream(stream); + auto result = impl->StartSystem(); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } - void StopAudioOut(Kernel::HLERequestContext& ctx) { + void Stop(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); - if (stream->IsPlaying()) { - audio_core.StopStream(stream); - } + auto result = impl->StopSystem(); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } - void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(buffer_event->GetReadableEvent()); - } - - void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description()); + void AppendAudioOutBuffer(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; + u64 tag = rp.PopRaw<u64>(); - const auto& input_buffer{ctx.ReadBuffer()}; - ASSERT_MSG(input_buffer.size() == sizeof(AudioBuffer), - "AudioBuffer input is an invalid size!"); - AudioBuffer audio_buffer{}; - std::memcpy(&audio_buffer, input_buffer.data(), sizeof(AudioBuffer)); - const u64 tag{rp.Pop<u64>()}; + const auto in_buffer_size{ctx.GetReadBufferSize()}; + if (in_buffer_size < sizeof(AudioOutBuffer)) { + LOG_ERROR(Service_Audio, "Input buffer is too small for an AudioOutBuffer!"); + } - std::vector<s16> samples(audio_buffer.buffer_size / sizeof(s16)); - main_memory.ReadBlock(audio_buffer.buffer, samples.data(), audio_buffer.buffer_size); + const auto& in_buffer = ctx.ReadBuffer(); + AudioOutBuffer buffer{}; + std::memcpy(&buffer, in_buffer.data(), sizeof(AudioOutBuffer)); - if (!audio_core.QueueBuffer(stream, tag, std::move(samples))) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERR_BUFFER_COUNT_EXCEEDED); - return; - } + [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; + LOG_TRACE(Service_Audio, "called. Session {} Appending buffer {:08X}", sessionid, tag); + + auto result = impl->AppendBuffer(buffer, tag); IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + + auto& buffer_event = impl->GetBufferEvent(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); + rb.PushCopyObjects(buffer_event); } - void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called {}", ctx.Description()); + void GetReleasedAudioOutBuffers(Kernel::HLERequestContext& ctx) { + auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64); + std::vector<u64> released_buffers(write_buffer_size, 0); - const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)}; - const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)}; + auto count = impl->GetReleasedBuffers(released_buffers); - std::vector<u64> tags{released_buffers}; - tags.resize(max_count); - ctx.WriteBuffer(tags); + [[maybe_unused]] std::string tags{}; + for (u32 i = 0; i < count; i++) { + tags += fmt::format("{:08X}, ", released_buffers[i]); + } + [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; + LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count, + tags); + ctx.WriteBuffer(released_buffers); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(released_buffers.size())); + rb.Push(count); } void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); - IPC::RequestParser rp{ctx}; + const u64 tag{rp.Pop<u64>()}; + const auto buffer_queued{impl->ContainsAudioBuffer(tag)}; + + LOG_DEBUG(Service_Audio, "called. Is buffer {:08X} registered? {}", tag, buffer_queued); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(stream->ContainsBuffer(tag)); + rb.Push(buffer_queued); } void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto buffer_count = impl->GetBufferCount(); + + LOG_DEBUG(Service_Audio, "called. Buffer count={}", buffer_count); IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); - rb.Push(static_cast<u32>(stream->GetQueueSize())); + rb.Push(buffer_count); } void GetAudioOutPlayedSampleCount(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto samples_played = impl->GetPlayedSampleCount(); + + LOG_DEBUG(Service_Audio, "called. Played samples={}", samples_played); IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); - rb.Push(stream->GetPlayedSampleCount()); + rb.Push(samples_played); } void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + bool flushed{impl->FlushAudioOutBuffers()}; + + LOG_DEBUG(Service_Audio, "called. Were any buffers flushed? {}", flushed); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(stream->Flush()); + rb.Push(flushed); } void SetAudioOutVolume(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const float volume = rp.Pop<float>(); - LOG_DEBUG(Service_Audio, "called, volume={}", volume); + const auto volume = rp.Pop<f32>(); + + LOG_DEBUG(Service_Audio, "called. Volume={}", volume); - stream->SetVolume(volume); + impl->SetVolume(volume); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void GetAudioOutVolume(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto volume = impl->GetVolume(); + + LOG_DEBUG(Service_Audio, "called. Volume={}", volume); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(stream->GetVolume()); + rb.Push(volume); } - AudioCore::AudioOut& audio_core; - AudioCore::StreamPtr stream; - std::string device_name; - - [[maybe_unused]] AudoutParams audio_params{}; - - Core::Memory::Memory& main_memory; - KernelHelpers::ServiceContext service_context; - - /// This is the event handle used to check if the audio buffer was released - Kernel::KEvent* buffer_event; + Kernel::KEvent* event; + std::shared_ptr<AudioCore::AudioOut::Out> impl; }; -AudOutU::AudOutU(Core::System& system_) : ServiceFramework{system_, "audout:u"} { +AudOutU::AudOutU(Core::System& system_) + : ServiceFramework{system_, "audout:u", ServiceThreadType::CreateNew}, + service_context{system_, "AudOutU"}, impl{std::make_unique<AudioCore::AudioOut::Manager>( + system_)} { // clang-format off static const FunctionInfo functions[] = { - {0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"}, - {1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"}, - {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"}, - {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}, + {0, &AudOutU::ListAudioOuts, "ListAudioOuts"}, + {1, &AudOutU::OpenAudioOut, "OpenAudioOut"}, + {2, &AudOutU::ListAudioOuts, "ListAudioOutsAuto"}, + {3, &AudOutU::OpenAudioOut, "OpenAudioOutAuto"}, }; // clang-format on RegisterHandlers(functions); - audio_core = std::make_unique<AudioCore::AudioOut>(); } AudOutU::~AudOutU() = default; -void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); +void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) { + using namespace AudioCore::AudioRenderer; - ctx.WriteBuffer(DefaultDevice); + std::scoped_lock l{impl->mutex}; + + const auto write_count = + static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); + std::vector<AudioDevice::AudioDeviceName> device_names{}; + std::string print_names{}; + if (write_count > 0) { + device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut")); + LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); + } else { + LOG_DEBUG(Service_Audio, "called. Empty buffer passed in."); + } + + ctx.WriteBuffer(device_names); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(1); // Amount of audio devices + rb.Push<u32>(static_cast<u32>(device_names.size())); } -void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); - +void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto in_params{rp.PopRaw<AudioOutParameter>()}; + auto applet_resource_user_id{rp.PopRaw<u64>()}; const auto device_name_data{ctx.ReadBuffer()}; - std::string device_name; - if (device_name_data[0] != '\0') { - device_name.assign(device_name_data.begin(), device_name_data.end()); - } else { - device_name.assign(DefaultDevice.begin(), DefaultDevice.end()); - } - ctx.WriteBuffer(device_name); + auto device_name = Common::StringFromBuffer(device_name_data); + auto handle{ctx.GetCopyHandle(0)}; - IPC::RequestParser rp{ctx}; - auto params{rp.PopRaw<AudoutParams>()}; - if (params.channel_count <= 2) { - // Mono does not exist for audout - params.channel_count = 2; - } else { - params.channel_count = 6; + auto link{impl->LinkToManager()}; + if (link.IsError()) { + LOG_ERROR(Service_Audio, "Failed to link Audio Out to Audio Manager"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(link); + return; } - if (!params.sample_rate) { - params.sample_rate = DefaultSampleRate; + + size_t new_session_id{}; + auto result{impl->AcquireSessionId(new_session_id)}; + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; } - std::string unique_name{fmt::format("{}-{}", device_name, audio_out_interfaces.size())}; - auto audio_out_interface = std::make_shared<IAudioOut>( - system, params, *audio_core, std::move(device_name), std::move(unique_name)); + LOG_DEBUG(Service_Audio, "Opening new AudioOut, sessionid={}, free sessions={}", new_session_id, + impl->num_free_sessions); + + auto audio_out = std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name, + in_params, handle, applet_resource_user_id); + + impl->sessions[new_session_id] = audio_out->GetImpl(); + impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; + + auto& out_system = impl->sessions[new_session_id]->GetSystem(); + AudioOutParameterInternal out_params{.sample_rate = out_system.GetSampleRate(), + .channel_count = out_system.GetChannelCount(), + .sample_format = + static_cast<u32>(out_system.GetSampleFormat()), + .state = static_cast<u32>(out_system.GetState())}; IPC::ResponseBuilder rb{ctx, 6, 0, 1}; - rb.Push(ResultSuccess); - rb.Push<u32>(DefaultSampleRate); - rb.Push<u32>(params.channel_count); - rb.Push<u32>(static_cast<u32>(AudioCore::Codec::PcmFormat::Int16)); - rb.Push<u32>(static_cast<u32>(AudioState::Stopped)); - rb.PushIpcInterface<IAudioOut>(audio_out_interface); - audio_out_interfaces.push_back(std::move(audio_out_interface)); + ctx.WriteBuffer(out_system.GetName()); + + rb.Push(ResultSuccess); + rb.PushRaw<AudioOutParameterInternal>(out_params); + rb.PushIpcInterface<IAudioOut>(audio_out); } } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index d82004c2e..fdc0ee754 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -3,13 +3,11 @@ #pragma once -#include <vector> +#include "audio_core/audio_out_manager.h" +#include "audio_core/out/audio_out.h" +#include "core/hle/service/kernel_helpers.h" #include "core/hle/service/service.h" -namespace AudioCore { -class AudioOut; -} - namespace Core { class System; } @@ -18,6 +16,11 @@ namespace Kernel { class HLERequestContext; } +namespace AudioCore::AudioOut { +class Manager; +class Out; +} // namespace AudioCore::AudioOut + namespace Service::Audio { class IAudioOut; @@ -28,11 +31,11 @@ public: ~AudOutU() override; private: - void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); - void OpenAudioOutImpl(Kernel::HLERequestContext& ctx); + void ListAudioOuts(Kernel::HLERequestContext& ctx); + void OpenAudioOut(Kernel::HLERequestContext& ctx); - std::vector<std::shared_ptr<IAudioOut>> audio_out_interfaces; - std::unique_ptr<AudioCore::AudioOut> audio_core; + KernelHelpers::ServiceContext service_context; + std::unique_ptr<AudioCore::AudioOut::Manager> impl; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 2ce63c004..381a66ba5 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -4,7 +4,12 @@ #include <array> #include <memory> -#include "audio_core/audio_renderer.h" +#include "audio_core/audio_core.h" +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/common/feature_support.h" +#include "audio_core/renderer/audio_device.h" +#include "audio_core/renderer/audio_renderer.h" +#include "audio_core/renderer/voice/voice_info.h" #include "common/alignment.h" #include "common/bit_util.h" #include "common/common_funcs.h" @@ -13,91 +18,112 @@ #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/service/audio/audren_u.h" #include "core/hle/service/audio/errors.h" +#include "core/memory.h" + +using namespace AudioCore::AudioRenderer; namespace Service::Audio { class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { public: - explicit IAudioRenderer(Core::System& system_, - const AudioCommon::AudioRendererParameter& audren_params, - const std::size_t instance_number) + explicit IAudioRenderer(Core::System& system_, Manager& manager_, + AudioCore::AudioRendererParameterInternal& params, + Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, + u32 process_handle, u64 applet_resource_user_id, s32 session_id) : ServiceFramework{system_, "IAudioRenderer", ServiceThreadType::CreateNew}, - service_context{system_, "IAudioRenderer"} { + service_context{system_, "IAudioRenderer"}, rendered_event{service_context.CreateEvent( + "IAudioRendererEvent")}, + manager{manager_}, impl{std::make_unique<Renderer>(system_, manager, rendered_event)} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"}, {1, &IAudioRenderer::GetSampleCount, "GetSampleCount"}, {2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"}, {3, &IAudioRenderer::GetState, "GetState"}, - {4, &IAudioRenderer::RequestUpdateImpl, "RequestUpdate"}, + {4, &IAudioRenderer::RequestUpdate, "RequestUpdate"}, {5, &IAudioRenderer::Start, "Start"}, {6, &IAudioRenderer::Stop, "Stop"}, {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, {8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"}, {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"}, - {10, &IAudioRenderer::RequestUpdateImpl, "RequestUpdateAuto"}, - {11, &IAudioRenderer::ExecuteAudioRendererRendering, "ExecuteAudioRendererRendering"}, + {10, nullptr, "RequestUpdateAuto"}, + {11, nullptr, "ExecuteAudioRendererRendering"}, }; // clang-format on RegisterHandlers(functions); - system_event = service_context.CreateEvent("IAudioRenderer:SystemEvent"); - renderer = std::make_unique<AudioCore::AudioRenderer>( - system.CoreTiming(), system.Memory(), audren_params, - [this]() { - const auto guard = LockService(); - system_event->GetWritableEvent().Signal(); - }, - instance_number); + impl->Initialize(params, transfer_memory, transfer_memory_size, process_handle, + applet_resource_user_id, session_id); } ~IAudioRenderer() override { - service_context.CloseEvent(system_event); + impl->Finalize(); + service_context.CloseEvent(rendered_event); } private: void GetSampleRate(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto sample_rate{impl->GetSystem().GetSampleRate()}; + + LOG_DEBUG(Service_Audio, "called. Sample rate {}", sample_rate); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(renderer->GetSampleRate()); + rb.Push(sample_rate); } void GetSampleCount(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto sample_count{impl->GetSystem().GetSampleCount()}; + + LOG_DEBUG(Service_Audio, "called. Sample count {}", sample_count); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(renderer->GetSampleCount()); + rb.Push(sample_count); } void GetState(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const u32 state{!impl->GetSystem().IsActive()}; + + LOG_DEBUG(Service_Audio, "called, state {}", state); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(renderer->GetStreamState())); + rb.Push(state); } void GetMixBufferCount(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); + const auto buffer_count{impl->GetSystem().GetMixBufferCount()}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(renderer->GetMixBufferCount()); + rb.Push(buffer_count); } - void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "(STUBBED) called"); + void RequestUpdate(Kernel::HLERequestContext& ctx) { + LOG_TRACE(Service_Audio, "called"); - std::vector<u8> output_params(ctx.GetWriteBufferSize(), 0); - auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params); + std::vector<u8> input{ctx.ReadBuffer(0)}; + + // These buffers are written manually to avoid an issue with WriteBuffer throwing errors for + // checking size 0. Performance size is 0 for most games. + const auto buffers{ctx.BufferDescriptorB()}; + std::vector<u8> output(buffers[0].Size(), 0); + std::vector<u8> performance(buffers[1].Size(), 0); + + auto result = impl->RequestUpdate(input, performance, output); if (result.IsSuccess()) { - ctx.WriteBuffer(output_params); + ctx.WriteBufferB(output.data(), output.size(), 0); + ctx.WriteBufferB(performance.data(), performance.size(), 1); + } else { + LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description); } IPC::ResponseBuilder rb{ctx, 2}; @@ -105,38 +131,45 @@ private: } void Start(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); - const auto result = renderer->Start(); + impl->Start(); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); + rb.Push(ResultSuccess); } void Stop(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); - const auto result = renderer->Stop(); + impl->Stop(); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); + rb.Push(ResultSuccess); } void QuerySystemEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); + + if (impl->GetSystem().GetExecutionMode() == AudioCore::ExecutionMode::Manual) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NOT_SUPPORTED); + return; + } IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(system_event->GetReadableEvent()); + rb.PushCopyObjects(rendered_event->GetReadableEvent()); } void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::RequestParser rp{ctx}; - rendering_time_limit_percent = rp.Pop<u32>(); - LOG_DEBUG(Service_Audio, "called. rendering_time_limit_percent={}", - rendering_time_limit_percent); + auto limit = rp.PopRaw<u32>(); - ASSERT(rendering_time_limit_percent <= 100); + auto& system_ = impl->GetSystem(); + system_.SetRenderingTimeLimit(limit); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -145,34 +178,34 @@ private: void GetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); + auto& system_ = impl->GetSystem(); + auto time = system_.GetRenderingTimeLimit(); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(rendering_time_limit_percent); + rb.Push(time); } void ExecuteAudioRendererRendering(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); - - // This service command currently only reports an unsupported operation - // error code, or aborts. Given that, we just always return an error - // code in this case. - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERR_NOT_SUPPORTED); } KernelHelpers::ServiceContext service_context; - - Kernel::KEvent* system_event; - std::unique_ptr<AudioCore::AudioRenderer> renderer; - u32 rendering_time_limit_percent = 100; + Kernel::KEvent* rendered_event; + Manager& manager; + std::unique_ptr<Renderer> impl; }; class IAudioDevice final : public ServiceFramework<IAudioDevice> { + public: - explicit IAudioDevice(Core::System& system_, Kernel::KEvent* buffer_event_, u32_le revision_) - : ServiceFramework{system_, "IAudioDevice"}, buffer_event{buffer_event_}, revision{ - revision_} { + explicit IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u32 revision, + u32 device_num) + : ServiceFramework{system_, "IAudioDevice", ServiceThreadType::CreateNew}, + service_context{system_, "IAudioDevice"}, impl{std::make_unique<AudioDevice>( + system_, applet_resource_user_id, + revision)}, + event{service_context.CreateEvent(fmt::format("IAudioDeviceEvent-{}", device_num))} { static const FunctionInfo functions[] = { {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, @@ -186,54 +219,45 @@ public: {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, - {13, nullptr, "GetActiveAudioOutputDeviceName"}, - {14, nullptr, "ListAudioOutputDeviceName"}, + {13, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioOutputDeviceName"}, + {14, &IAudioDevice::ListAudioOutputDeviceName, "ListAudioOutputDeviceName"}, }; RegisterHandlers(functions); + + event->GetWritableEvent().Signal(); } -private: - using AudioDeviceName = std::array<char, 256>; - static constexpr std::array<std::string_view, 4> audio_device_names{{ - "AudioStereoJackOutput", - "AudioBuiltInSpeakerOutput", - "AudioTvOutput", - "AudioUsbDeviceOutput", - }}; - enum class DeviceType { - AHUBHeadphones, - AHUBSpeakers, - HDA, - USBOutput, - }; + ~IAudioDevice() override { + service_context.CloseEvent(event); + } +private: void ListAudioDeviceName(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName); - const bool usb_output_supported = - IsFeatureSupported(AudioFeatures::AudioUSBDeviceOutput, revision); - const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioDeviceName); + std::vector<AudioDevice::AudioDeviceName> out_names{}; - std::vector<AudioDeviceName> name_buffer; - name_buffer.reserve(audio_device_names.size()); + u32 out_count = impl->ListAudioDeviceName(out_names, in_count); - for (std::size_t i = 0; i < count && i < audio_device_names.size(); i++) { - const auto type = static_cast<DeviceType>(i); - - if (!usb_output_supported && type == DeviceType::USBOutput) { - continue; + std::string out{}; + for (u32 i = 0; i < out_count; i++) { + std::string a{}; + u32 j = 0; + while (out_names[i].name[j] != '\0') { + a += out_names[i].name[j]; + j++; } - - const auto& device_name = audio_device_names[i]; - auto& entry = name_buffer.emplace_back(); - device_name.copy(entry.data(), device_name.size()); + out += "\n\t" + a; } - ctx.WriteBuffer(name_buffer); + LOG_DEBUG(Service_Audio, "called.\nNames={}", out); IPC::ResponseBuilder rb{ctx, 3}; + + ctx.WriteBuffer(out_names); + rb.Push(ResultSuccess); - rb.Push(static_cast<u32>(name_buffer.size())); + rb.Push(out_count); } void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { @@ -243,7 +267,11 @@ private: const auto device_name_buffer = ctx.ReadBuffer(); const std::string name = Common::StringFromBuffer(device_name_buffer); - LOG_WARNING(Service_Audio, "(STUBBED) called. name={}, volume={}", name, volume); + LOG_DEBUG(Service_Audio, "called. name={}, volume={}", name, volume); + + if (name == "AudioTvOutput") { + impl->SetDeviceVolumes(volume); + } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -253,53 +281,60 @@ private: const auto device_name_buffer = ctx.ReadBuffer(); const std::string name = Common::StringFromBuffer(device_name_buffer); - LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name); + LOG_DEBUG(Service_Audio, "called. Name={}", name); + + f32 volume{1.0f}; + if (name == "AudioTvOutput") { + volume = impl->GetDeviceVolume(name); + } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(1.0f); + rb.Push(volume); } void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + const auto write_size = ctx.GetWriteBufferSize() / sizeof(char); + std::string out_name{"AudioTvOutput"}; - // Currently set to always be TV audio output. - const auto& device_name = audio_device_names[2]; + LOG_DEBUG(Service_Audio, "(STUBBED) called. Name={}", out_name); - AudioDeviceName out_device_name{}; - device_name.copy(out_device_name.data(), device_name.size()); + out_name.resize(write_size); - ctx.WriteBuffer(out_device_name); + ctx.WriteBuffer(out_name); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "(STUBBED) called"); - buffer_event->GetWritableEvent().Signal(); + event->GetWritableEvent().Signal(); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(buffer_event->GetReadableEvent()); + rb.PushCopyObjects(event->GetReadableEvent()); } void GetActiveChannelCount(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + const auto& sink{system.AudioCore().GetOutputSink()}; + u32 channel_count{sink.GetDeviceChannels()}; + + LOG_DEBUG(Service_Audio, "(STUBBED) called. Channels={}", channel_count); IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); - rb.Push<u32>(2); + rb.Push<u32>(channel_count); } - // Should be similar to QueryAudioDeviceOutputEvent void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(buffer_event->GetReadableEvent()); + rb.PushCopyObjects(event->GetReadableEvent()); } void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { @@ -307,402 +342,167 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(buffer_event->GetReadableEvent()); + rb.PushCopyObjects(event->GetReadableEvent()); } - Kernel::KEvent* buffer_event; - u32_le revision = 0; + void ListAudioOutputDeviceName(Kernel::HLERequestContext& ctx) { + const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName); + + std::vector<AudioDevice::AudioDeviceName> out_names{}; + + u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); + + std::string out{}; + for (u32 i = 0; i < out_count; i++) { + std::string a{}; + u32 j = 0; + while (out_names[i].name[j] != '\0') { + a += out_names[i].name[j]; + j++; + } + out += "\n\t" + a; + } + + LOG_DEBUG(Service_Audio, "called.\nNames={}", out); + + IPC::ResponseBuilder rb{ctx, 3}; + + ctx.WriteBuffer(out_names); + + rb.Push(ResultSuccess); + rb.Push(out_count); + } + + KernelHelpers::ServiceContext service_context; + std::unique_ptr<AudioDevice> impl; + Kernel::KEvent* event; }; AudRenU::AudRenU(Core::System& system_) - : ServiceFramework{system_, "audren:u"}, service_context{system_, "audren:u"} { + : ServiceFramework{system_, "audren:u", ServiceThreadType::CreateNew}, + service_context{system_, "audren:u"}, impl{std::make_unique<Manager>(system_)} { // clang-format off static const FunctionInfo functions[] = { {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, - {1, &AudRenU::GetAudioRendererWorkBufferSize, "GetWorkBufferSize"}, + {1, &AudRenU::GetWorkBufferSize, "GetWorkBufferSize"}, {2, &AudRenU::GetAudioDeviceService, "GetAudioDeviceService"}, - {3, &AudRenU::OpenAudioRendererForManualExecution, "OpenAudioRendererForManualExecution"}, + {3, nullptr, "OpenAudioRendererForManualExecution"}, {4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo, "GetAudioDeviceServiceWithRevisionInfo"}, }; // clang-format on RegisterHandlers(functions); - - buffer_event = service_context.CreateEvent("IAudioOutBufferReleasedEvent"); } -AudRenU::~AudRenU() { - service_context.CloseEvent(buffer_event); -} +AudRenU::~AudRenU() = default; void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); - - OpenAudioRendererImpl(ctx); -} - -static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) { - // +1 represents the final mix. - return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count + - 1; -} - -void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); - - // Several calculations below align the sizes being calculated - // onto a 64 byte boundary. - static constexpr u64 buffer_alignment_size = 64; - - // Some calculations that calculate portions of the buffer - // that will contain information, on the other hand, align - // the result of some of their calcularions on a 16 byte boundary. - static constexpr u64 info_field_alignment_size = 16; - - // Maximum detail entries that may exist at one time for performance - // frame statistics. - static constexpr u64 max_perf_detail_entries = 100; - - // Size of the data structure representing the bulk of the voice-related state. - static constexpr u64 voice_state_size_bytes = 0x100; - - // Size of the upsampler manager data structure - constexpr u64 upsampler_manager_size = 0x48; - - // Calculates the part of the size that relates to mix buffers. - const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) { - // As of 8.0.0 this is the maximum on voice channels. - constexpr u64 max_voice_channels = 6; - - // The service expects the sample_count member of the parameters to either be - // a value of 160 or 240, so the maximum sample count is assumed in order - // to adequately handle all values at runtime. - constexpr u64 default_max_sample_count = 240; - - const u64 total_mix_buffers = params.mix_buffer_count + max_voice_channels; - - u64 size = 0; - size += total_mix_buffers * (sizeof(s32) * params.sample_count); - size += total_mix_buffers * (sizeof(s32) * default_max_sample_count); - size += u64{params.submix_count} + params.sink_count; - size = Common::AlignUp(size, buffer_alignment_size); - size += Common::AlignUp(params.unknown_30, buffer_alignment_size); - size += Common::AlignUp(sizeof(s32) * params.mix_buffer_count, buffer_alignment_size); - return size; - }; - - // Calculates the portion of the size related to the mix data (and the sorting thereof). - const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) { - // The size of the mixing info data structure. - constexpr u64 mix_info_size = 0x940; - - // Consists of total submixes with the final mix included. - const u64 total_mix_count = u64{params.submix_count} + 1; - - // The total number of effects that may be available to the audio renderer at any time. - constexpr u64 max_effects = 256; - - // Calculates the part of the size related to the audio node state. - // This will only be used if the audio revision supports the splitter. - const auto calculate_node_state_size = [](std::size_t num_nodes) { - // Internally within a nodestate, it appears to use a data structure - // similar to a std::bitset<64> twice. - constexpr u64 bit_size = Common::BitSize<u64>(); - constexpr u64 num_bitsets = 2; - - // Node state instances have three states internally for performing - // depth-first searches of nodes. Initialized, Found, and Done Sorting. - constexpr u64 num_states = 3; - - u64 size = 0; - size += (num_nodes * num_nodes) * sizeof(s32); - size += num_states * (num_nodes * sizeof(s32)); - size += num_bitsets * (Common::AlignUp(num_nodes, bit_size) / Common::BitSize<u8>()); - return size; - }; - - // Calculates the part of the size related to the adjacency (aka edge) matrix. - const auto calculate_edge_matrix_size = [](std::size_t num_nodes) { - return (num_nodes * num_nodes) * sizeof(s32); - }; - - u64 size = 0; - size += Common::AlignUp(sizeof(void*) * total_mix_count, info_field_alignment_size); - size += Common::AlignUp(mix_info_size * total_mix_count, info_field_alignment_size); - size += Common::AlignUp(sizeof(s32) * max_effects * params.submix_count, - info_field_alignment_size); - - if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { - size += Common::AlignUp(calculate_node_state_size(total_mix_count) + - calculate_edge_matrix_size(total_mix_count), - info_field_alignment_size); - } - - return size; - }; - - // Calculates the part of the size related to voice channel info. - const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) { - constexpr u64 voice_info_size = 0x220; - constexpr u64 voice_resource_size = 0xD0; - - u64 size = 0; - size += Common::AlignUp(sizeof(void*) * params.voice_count, info_field_alignment_size); - size += Common::AlignUp(voice_info_size * params.voice_count, info_field_alignment_size); - size += - Common::AlignUp(voice_resource_size * params.voice_count, info_field_alignment_size); - size += - Common::AlignUp(voice_state_size_bytes * params.voice_count, info_field_alignment_size); - return size; - }; - - // Calculates the part of the size related to memory pools. - const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) { - const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count); - const u64 memory_pool_info_size = 0x20; - return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size); - }; - - // Calculates the part of the size related to the splitter context. - const auto calculate_splitter_context_size = - [](const AudioCommon::AudioRendererParameter& params) -> u64 { - if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { - return 0; - } - - constexpr u64 splitter_info_size = 0x20; - constexpr u64 splitter_destination_data_size = 0xE0; - - u64 size = 0; - size += params.num_splitter_send_channels; - size += - Common::AlignUp(splitter_info_size * params.splitter_count, info_field_alignment_size); - size += Common::AlignUp(splitter_destination_data_size * params.num_splitter_send_channels, - info_field_alignment_size); - - return size; - }; - - // Calculates the part of the size related to the upsampler info. - const auto calculate_upsampler_info_size = - [](const AudioCommon::AudioRendererParameter& params) { - constexpr u64 upsampler_info_size = 0x280; - // Yes, using the buffer size over info alignment size is intentional here. - return Common::AlignUp(upsampler_info_size * - (u64{params.submix_count} + params.sink_count), - buffer_alignment_size); - }; - - // Calculates the part of the size related to effect info. - const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) { - constexpr u64 effect_info_size = 0x2B0; - return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size); - }; - - // Calculates the part of the size related to audio sink info. - const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) { - const u64 sink_info_size = 0x170; - return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size); - }; - - // Calculates the part of the size related to voice state info. - const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) { - const u64 voice_state_size = 0x100; - const u64 additional_size = buffer_alignment_size - 1; - return Common::AlignUp(voice_state_size * params.voice_count + additional_size, - info_field_alignment_size); - }; - - // Calculates the part of the size related to performance statistics. - const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) { - // Extra size value appended to the end of the calculation. - constexpr u64 appended = 128; - - // Whether or not we assume the newer version of performance metrics data structures. - const bool is_v2 = - IsFeatureSupported(AudioFeatures::PerformanceMetricsVersion2, params.revision); - - // Data structure sizes - constexpr u64 perf_statistics_size = 0x0C; - const u64 header_size = is_v2 ? 0x30 : 0x18; - const u64 entry_size = is_v2 ? 0x18 : 0x10; - const u64 detail_size = is_v2 ? 0x18 : 0x10; - - const u64 entry_count = CalculateNumPerformanceEntries(params); - const u64 size_per_frame = - header_size + (entry_size * entry_count) + (detail_size * max_perf_detail_entries); - - u64 size = 0; - size += Common::AlignUp(size_per_frame * params.performance_frame_count + 1, - buffer_alignment_size); - size += Common::AlignUp(perf_statistics_size, buffer_alignment_size); - size += appended; - return size; - }; - - // Calculates the part of the size that relates to the audio command buffer. - const auto calculate_command_buffer_size = - [](const AudioCommon::AudioRendererParameter& params) { - constexpr u64 alignment = (buffer_alignment_size - 1) * 2; - - if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { - constexpr u64 command_buffer_size = 0x18000; - - return command_buffer_size + alignment; - } - - // When the variadic command buffer is supported, this means - // the command generator for the audio renderer can issue commands - // that are (as one would expect), variable in size. So what we need to do - // is determine the maximum possible size for a few command data structures - // then multiply them by the amount of present commands indicated by the given - // respective audio parameters. - - constexpr u64 max_biquad_filters = 2; - constexpr u64 max_mix_buffers = 24; - - constexpr u64 biquad_filter_command_size = 0x2C; - - constexpr u64 depop_mix_command_size = 0x24; - constexpr u64 depop_setup_command_size = 0x50; - - constexpr u64 effect_command_max_size = 0x540; - - constexpr u64 mix_command_size = 0x1C; - constexpr u64 mix_ramp_command_size = 0x24; - constexpr u64 mix_ramp_grouped_command_size = 0x13C; - - constexpr u64 perf_command_size = 0x28; - - constexpr u64 sink_command_size = 0x130; - - constexpr u64 submix_command_max_size = - depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; - - constexpr u64 volume_command_size = 0x1C; - constexpr u64 volume_ramp_command_size = 0x20; - - constexpr u64 voice_biquad_filter_command_size = - biquad_filter_command_size * max_biquad_filters; - constexpr u64 voice_data_command_size = 0x9C; - const u64 voice_command_max_size = - (params.splitter_count * depop_setup_command_size) + - (voice_data_command_size + voice_biquad_filter_command_size + - volume_ramp_command_size + mix_ramp_grouped_command_size); + IPC::RequestParser rp{ctx}; - // Now calculate the individual elements that comprise the size and add them together. - const u64 effect_commands_size = params.effect_count * effect_command_max_size; + AudioCore::AudioRendererParameterInternal params; + rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params); + auto transfer_memory_handle = ctx.GetCopyHandle(0); + auto process_handle = ctx.GetCopyHandle(1); + auto transfer_memory_size = rp.Pop<u64>(); + auto applet_resource_user_id = rp.Pop<u64>(); - const u64 final_mix_commands_size = - depop_mix_command_size + volume_command_size * max_mix_buffers; + if (impl->GetSessionCount() + 1 > AudioCore::MaxRendererSessions) { + LOG_ERROR(Service_Audio, "Too many AudioRenderer sessions open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_MAXIMUM_SESSIONS_REACHED); + return; + } - const u64 perf_commands_size = - perf_command_size * - (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); + const auto& handle_table{system.CurrentProcess()->GetHandleTable()}; + auto process{handle_table.GetObject<Kernel::KProcess>(process_handle)}; + auto transfer_memory{ + process->GetHandleTable().GetObject<Kernel::KTransferMemory>(transfer_memory_handle)}; - const u64 sink_commands_size = params.sink_count * sink_command_size; + const auto session_id{impl->GetSessionId()}; + if (session_id == -1) { + LOG_ERROR(Service_Audio, "Tried to open a session that's already in use!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_MAXIMUM_SESSIONS_REACHED); + return; + } - const u64 splitter_commands_size = - params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; + LOG_DEBUG(Service_Audio, "Opened new AudioRenderer session {} sessions open {}", session_id, + impl->GetSessionCount()); - const u64 submix_commands_size = params.submix_count * submix_command_max_size; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IAudioRenderer>(system, *impl, params, transfer_memory.GetPointerUnsafe(), + transfer_memory_size, process_handle, + applet_resource_user_id, session_id); +} - const u64 voice_commands_size = params.voice_count * voice_command_max_size; - - return effect_commands_size + final_mix_commands_size + perf_commands_size + - sink_commands_size + splitter_commands_size + submix_commands_size + - voice_commands_size + alignment; - }; +void AudRenU::GetWorkBufferSize(Kernel::HLERequestContext& ctx) { + AudioCore::AudioRendererParameterInternal params; IPC::RequestParser rp{ctx}; - const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>(); - - u64 size = 0; - size += calculate_mix_buffer_sizes(params); - size += calculate_mix_info_size(params); - size += calculate_voice_info_size(params); - size += upsampler_manager_size; - size += calculate_memory_pools_size(params); - size += calculate_splitter_context_size(params); - - size = Common::AlignUp(size, buffer_alignment_size); - - size += calculate_upsampler_info_size(params); - size += calculate_effect_info_size(params); - size += calculate_sink_info_size(params); - size += calculate_voice_state_size(params); - size += calculate_perf_size(params); - size += calculate_command_buffer_size(params); - - // finally, 4KB page align the size, and we're done. - size = Common::AlignUp(size, 4096); + rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params); + + u64 size{0}; + auto result = impl->GetWorkBufferSize(params, size); + + std::string output_info{}; + output_info += fmt::format("\tRevision {}", AudioCore::GetRevisionNum(params.revision)); + output_info += + fmt::format("\n\tSample Rate {}, Sample Count {}", params.sample_rate, params.sample_count); + output_info += fmt::format("\n\tExecution Mode {}, Voice Drop Enabled {}", + static_cast<u32>(params.execution_mode), params.voice_drop_enabled); + output_info += fmt::format( + "\n\tSizes: Effects {:04X}, Mixes {:04X}, Sinks {:04X}, Submixes {:04X}, Splitter Infos " + "{:04X}, Splitter Destinations {:04X}, Voices {:04X}, Performance Frames {:04X} External " + "Context {:04X}", + params.effects, params.mixes, params.sinks, params.sub_mixes, params.splitter_infos, + params.splitter_destinations, params.voices, params.perf_frames, + params.external_context_size); + + LOG_DEBUG(Service_Audio, "called.\nInput params:\n{}\nOutput params:\n\tWorkbuffer size {:08X}", + output_info, size); IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(ResultSuccess); + rb.Push(result); rb.Push<u64>(size); - - LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", size); } void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const u64 aruid = rp.Pop<u64>(); - LOG_DEBUG(Service_Audio, "called. aruid={:016X}", aruid); + const auto applet_resource_user_id = rp.Pop<u64>(); + + LOG_DEBUG(Service_Audio, "called. Applet resource id {}", applet_resource_user_id); - // Revisionless variant of GetAudioDeviceServiceWithRevisionInfo that - // always assumes the initial release revision (REV1). IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); - rb.PushIpcInterface<IAudioDevice>(system, buffer_event, Common::MakeMagic('R', 'E', 'V', '1')); + rb.PushIpcInterface<IAudioDevice>(system, applet_resource_user_id, + ::Common::MakeMagic('R', 'E', 'V', '1'), num_audio_devices++); } void AudRenU::OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); - - OpenAudioRendererImpl(ctx); } void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) { struct Parameters { u32 revision; - u64 aruid; + u64 applet_resource_user_id; }; IPC::RequestParser rp{ctx}; - const auto [revision, aruid] = rp.PopRaw<Parameters>(); - LOG_DEBUG(Service_Audio, "called. revision={:08X}, aruid={:016X}", revision, aruid); + const auto [revision, applet_resource_user_id] = rp.PopRaw<Parameters>(); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IAudioDevice>(system, buffer_event, revision); -} + LOG_DEBUG(Service_Audio, "called. Revision {} Applet resource id {}", + AudioCore::GetRevisionNum(revision), applet_resource_user_id); -void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<IAudioRenderer>(system, params, audren_instance_count++); -} - -bool IsFeatureSupported(AudioFeatures feature, u32_le revision) { - // Byte swap - const u32_be version_num = revision - Common::MakeMagic('R', 'E', 'V', '0'); - - switch (feature) { - case AudioFeatures::AudioUSBDeviceOutput: - return version_num >= 4U; - case AudioFeatures::Splitter: - return version_num >= 2U; - case AudioFeatures::PerformanceMetricsVersion2: - case AudioFeatures::VariadicCommandBuffer: - return version_num >= 5U; - default: - return false; - } + rb.PushIpcInterface<IAudioDevice>(system, applet_resource_user_id, revision, + num_audio_devices++); } } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index 869d39002..4384a9b3c 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -3,6 +3,7 @@ #pragma once +#include "audio_core/audio_render_manager.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/service.h" @@ -15,6 +16,7 @@ class HLERequestContext; } namespace Service::Audio { +class IAudioRenderer; class AudRenU final : public ServiceFramework<AudRenU> { public: @@ -23,28 +25,14 @@ public: private: void OpenAudioRenderer(Kernel::HLERequestContext& ctx); - void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx); + void GetWorkBufferSize(Kernel::HLERequestContext& ctx); void GetAudioDeviceService(Kernel::HLERequestContext& ctx); void OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx); void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx); - void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx); - KernelHelpers::ServiceContext service_context; - - std::size_t audren_instance_count = 0; - Kernel::KEvent* buffer_event; + std::unique_ptr<AudioCore::AudioRenderer::Manager> impl; + u32 num_audio_devices{0}; }; -// Describes a particular audio feature that may be supported in a particular revision. -enum class AudioFeatures : u32 { - AudioUSBDeviceOutput, - Splitter, - PerformanceMetricsVersion2, - VariadicCommandBuffer, -}; - -// Tests if a particular audio feature is supported with a given audio revision. -bool IsFeatureSupported(AudioFeatures feature, u32_le revision); - } // namespace Service::Audio diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index ac6c514af..d706978cb 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h @@ -7,8 +7,17 @@ namespace Service::Audio { +constexpr Result ERR_INVALID_DEVICE_NAME{ErrorModule::Audio, 1}; constexpr Result ERR_OPERATION_FAILED{ErrorModule::Audio, 2}; +constexpr Result ERR_INVALID_SAMPLE_RATE{ErrorModule::Audio, 3}; +constexpr Result ERR_INSUFFICIENT_BUFFER_SIZE{ErrorModule::Audio, 4}; +constexpr Result ERR_MAXIMUM_SESSIONS_REACHED{ErrorModule::Audio, 5}; constexpr Result ERR_BUFFER_COUNT_EXCEEDED{ErrorModule::Audio, 8}; +constexpr Result ERR_INVALID_CHANNEL_COUNT{ErrorModule::Audio, 10}; +constexpr Result ERR_INVALID_UPDATE_DATA{ErrorModule::Audio, 41}; +constexpr Result ERR_POOL_MAPPING_FAILED{ErrorModule::Audio, 42}; constexpr Result ERR_NOT_SUPPORTED{ErrorModule::Audio, 513}; +constexpr Result ERR_INVALID_PROCESS_HANDLE{ErrorModule::Audio, 1536}; +constexpr Result ERR_INVALID_REVISION{ErrorModule::Audio, 1537}; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 75da659e5..4f2ed2d52 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -298,7 +298,7 @@ void HwOpus::OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx) { const auto sample_rate = rp.Pop<u32>(); const auto channel_count = rp.Pop<u32>(); - LOG_CRITICAL(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); + LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || sample_rate == 12000 || sample_rate == 8000, diff --git a/src/core/hle/service/bcat/bcat_module.cpp b/src/core/hle/service/bcat/bcat_module.cpp index d928e37fb..bc08ac487 100644 --- a/src/core/hle/service/bcat/bcat_module.cpp +++ b/src/core/hle/service/bcat/bcat_module.cpp @@ -140,8 +140,8 @@ public: {20401, nullptr, "UnregisterSystemApplicationDeliveryTask"}, {20410, nullptr, "SetSystemApplicationDeliveryTaskTimer"}, {30100, &IBcatService::SetPassphrase, "SetPassphrase"}, - {30101, nullptr, "Unknown"}, - {30102, nullptr, "Unknown2"}, + {30101, nullptr, "Unknown30101"}, + {30102, nullptr, "Unknown30102"}, {30200, nullptr, "RegisterBackgroundDeliveryTask"}, {30201, nullptr, "UnregisterBackgroundDeliveryTask"}, {30202, nullptr, "BlockDeliveryTask"}, diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp index f9ee2b624..ec7e5320c 100644 --- a/src/core/hle/service/btdrv/btdrv.cpp +++ b/src/core/hle/service/btdrv/btdrv.cpp @@ -181,6 +181,11 @@ public: {147, nullptr, "RegisterAudioControlNotification"}, {148, nullptr, "SendAudioControlPassthroughCommand"}, {149, nullptr, "SendAudioControlSetAbsoluteVolumeCommand"}, + {150, nullptr, "AcquireAudioSinkVolumeLocallyChangedEvent"}, + {151, nullptr, "AcquireAudioSinkVolumeUpdateRequestCompletedEvent"}, + {152, nullptr, "GetAudioSinkVolume"}, + {153, nullptr, "RequestUpdateAudioSinkVolume"}, + {154, nullptr, "IsAudioSinkVolumeSupported"}, {256, nullptr, "IsManufacturingMode"}, {257, nullptr, "EmulateBluetoothCrash"}, {258, nullptr, "GetBleChannelMap"}, diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp index 3fa88cbd3..eebf85e03 100644 --- a/src/core/hle/service/btm/btm.cpp +++ b/src/core/hle/service/btm/btm.cpp @@ -214,8 +214,12 @@ public: {76, nullptr, "Unknown76"}, {100, nullptr, "Unknown100"}, {101, nullptr, "Unknown101"}, - {110, nullptr, "Unknown102"}, - {111, nullptr, "Unknown103"}, + {110, nullptr, "Unknown110"}, + {111, nullptr, "Unknown111"}, + {112, nullptr, "Unknown112"}, + {113, nullptr, "Unknown113"}, + {114, nullptr, "Unknown114"}, + {115, nullptr, "Unknown115"}, }; // clang-format on diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp index 7d35b4208..4a81bb5e2 100644 --- a/src/core/hle/service/fatal/fatal_p.cpp +++ b/src/core/hle/service/fatal/fatal_p.cpp @@ -6,7 +6,13 @@ namespace Service::Fatal { Fatal_P::Fatal_P(std::shared_ptr<Module> module_, Core::System& system_) - : Interface(std::move(module_), system_, "fatal:p") {} + : Interface(std::move(module_), system_, "fatal:p") { + static const FunctionInfo functions[] = { + {0, nullptr, "GetFatalEvent"}, + {10, nullptr, "GetFatalContext"}, + }; + RegisterHandlers(functions); +} Fatal_P::~Fatal_P() = default; diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index aa7189bda..3c28dee76 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -838,11 +838,11 @@ bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id, const auto now = steady_clock::now(); - // Filter out non-zero vibrations that are within 10ms of each other. + // Filter out non-zero vibrations that are within 15ms of each other. if ((vibration_value.low_amplitude != 0.0f || vibration_value.high_amplitude != 0.0f) && duration_cast<milliseconds>( now - controller.vibration[device_index].last_vibration_timepoint) < - milliseconds(10)) { + milliseconds(15)) { return false; } diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h index 46282f42e..4613a4e60 100644 --- a/src/core/hle/service/hid/errors.h +++ b/src/core/hle/service/hid/errors.h @@ -19,3 +19,10 @@ constexpr Result InvalidNpadId{ErrorModule::HID, 709}; constexpr Result NpadNotConnected{ErrorModule::HID, 710}; } // namespace Service::HID + +namespace Service::IRS { + +constexpr Result InvalidProcessorState{ErrorModule::Irsensor, 78}; +constexpr Result InvalidIrCameraHandle{ErrorModule::Irsensor, 204}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 89bb12442..5ecbddf94 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2345,8 +2345,8 @@ void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system std::make_shared<HidSys>(system)->InstallAsService(service_manager); std::make_shared<HidTmp>(system)->InstallAsService(service_manager); - std::make_shared<IRS>(system)->InstallAsService(service_manager); - std::make_shared<IRS_SYS>(system)->InstallAsService(service_manager); + std::make_shared<Service::IRS::IRS>(system)->InstallAsService(service_manager); + std::make_shared<Service::IRS::IRS_SYS>(system)->InstallAsService(service_manager); std::make_shared<XCD_SYS>(system)->InstallAsService(service_manager); } diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index d2a91d913..d5107e41f 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -1,16 +1,28 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <algorithm> +#include <random> + #include "core/core.h" #include "core/core_timing.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_shared_memory.h" #include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/kernel/kernel.h" #include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/irs.h" +#include "core/hle/service/hid/irsensor/clustering_processor.h" +#include "core/hle/service/hid/irsensor/image_transfer_processor.h" +#include "core/hle/service/hid/irsensor/ir_led_processor.h" +#include "core/hle/service/hid/irsensor/moment_processor.h" +#include "core/hle/service/hid/irsensor/pointing_processor.h" +#include "core/hle/service/hid/irsensor/tera_plugin_processor.h" +#include "core/memory.h" -namespace Service::HID { +namespace Service::IRS { IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} { // clang-format off @@ -36,14 +48,19 @@ IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} { }; // clang-format on + u8* raw_shared_memory = system.Kernel().GetIrsSharedMem().GetPointer(); RegisterHandlers(functions); + shared_memory = std::construct_at(reinterpret_cast<StatusManager*>(raw_shared_memory)); + + npad_device = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); } +IRS::~IRS() = default; void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -54,7 +71,7 @@ void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -75,7 +92,7 @@ void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) { void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -88,17 +105,23 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsSuccess()) { + // TODO: Stop Image processor + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedMomentProcessorConfig processor_config; + Core::IrSensor::PackedMomentProcessorConfig processor_config; }; static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size."); @@ -109,19 +132,28 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessor<MomentProcessor>(parameters.camera_handle, device); + auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedClusteringProcessorConfig processor_config; + Core::IrSensor::PackedClusteringProcessorConfig processor_config; }; - static_assert(sizeof(Parameters) == 0x40, "Parameters has incorrect size."); + static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size."); const auto parameters{rp.PopRaw<Parameters>()}; @@ -130,17 +162,27 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessor<ClusteringProcessor>(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor<ClusteringProcessor>(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedImageTransferProcessorConfig processor_config; + Core::IrSensor::PackedImageTransferProcessorConfig processor_config; u32 transfer_memory_size; }; static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size."); @@ -151,20 +193,42 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { auto t_mem = system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, " - "applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.transfer_memory_size, parameters.applet_resource_user_id); + if (t_mem.IsNull()) { + LOG_ERROR(Service_IRS, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + ASSERT_MSG(t_mem->GetSize() == parameters.transfer_memory_size, "t_mem has incorrect size"); + + u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress()); + + LOG_INFO(Service_IRS, + "called, npad_type={}, npad_id={}, transfer_memory_size={}, transfer_memory_size={}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.transfer_memory_size, t_mem->GetSize(), parameters.applet_resource_user_id); + + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessorWithCoreContext<ImageTransferProcessor>(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor<ImageTransferProcessor>(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + image_transfer_processor.SetTransferMemoryPointer(transfer_memory); + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -172,32 +236,68 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.applet_resource_user_id); + LOG_DEBUG(Service_IRS, "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.applet_resource_user_id); + + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + const auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + + if (device.mode != Core::IrSensor::IrSensorMode::ImageTransferProcessor) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(InvalidProcessorState); + return; + } - IPC::ResponseBuilder rb{ctx, 5}; + std::vector<u8> data{}; + const auto& image_transfer_processor = + GetProcessor<ImageTransferProcessor>(parameters.camera_handle); + const auto& state = image_transfer_processor.GetState(data); + + ctx.WriteBuffer(data); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); - rb.PushRaw<u64>(system.CoreTiming().GetCPUTicks()); - rb.PushRaw<u32>(0); + rb.PushRaw(state); } void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; - const auto processor_config{rp.PopRaw<PackedTeraPluginProcessorConfig>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Core::IrSensor::IrCameraHandle camera_handle; + Core::IrSensor::PackedTeraPluginProcessorConfig processor_config; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, " - "applet_resource_user_id={}", - camera_handle.npad_type, camera_handle.npad_id, processor_config.mode, - processor_config.required_mcu_version.major, - processor_config.required_mcu_version.minor, applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING( + Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.processor_config.mode, parameters.processor_config.required_mcu_version.major, + parameters.processor_config.required_mcu_version.minor, parameters.applet_resource_user_id); + + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessor<TeraPluginProcessor>(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor<TeraPluginProcessor>(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { @@ -207,17 +307,17 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { if (npad_id > Core::HID::NpadIdType::Player8 && npad_id != Core::HID::NpadIdType::Invalid && npad_id != Core::HID::NpadIdType::Handheld) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(InvalidNpadId); + rb.Push(Service::HID::InvalidNpadId); return; } - IrCameraHandle camera_handle{ + Core::IrSensor::IrCameraHandle camera_handle{ .npad_id = static_cast<u8>(NpadIdTypeToIndex(npad_id)), .npad_type = Core::HID::NpadStyleIndex::None, }; - LOG_WARNING(Service_IRS, "(STUBBED) called, npad_id={}, camera_npad_id={}, camera_npad_type={}", - npad_id, camera_handle.npad_id, camera_handle.npad_type); + LOG_INFO(Service_IRS, "called, npad_id={}, camera_npad_id={}, camera_npad_type={}", npad_id, + camera_handle.npad_id, camera_handle.npad_type); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); @@ -226,8 +326,8 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; - const auto processor_config{rp.PopRaw<PackedPointingProcessorConfig>()}; + const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()}; + const auto processor_config{rp.PopRaw<Core::IrSensor::PackedPointingProcessorConfig>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; LOG_WARNING( @@ -236,14 +336,23 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { camera_handle.npad_type, camera_handle.npad_id, processor_config.required_mcu_version.major, processor_config.required_mcu_version.minor, applet_resource_user_id); + auto result = IsIrCameraHandleValid(camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle); + MakeProcessor<PointingProcessor>(camera_handle, device); + auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle); + image_transfer_processor.SetConfig(processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -256,14 +365,20 @@ void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsSuccess()) { + // TODO: Suspend image processor + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; - const auto mcu_version{rp.PopRaw<PackedMcuVersion>()}; + const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()}; + const auto mcu_version{rp.PopRaw<Core::IrSensor::PackedMcuVersion>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; LOG_WARNING( @@ -272,37 +387,45 @@ void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) { camera_handle.npad_type, camera_handle.npad_id, applet_resource_user_id, mcu_version.major, mcu_version.minor); + auto result = IsIrCameraHandleValid(camera_handle); + if (result.IsSuccess()) { + // TODO: Check firmware version + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - struct Parameters { - IrCameraHandle camera_handle; - PackedFunctionLevel function_level; - u64 applet_resource_user_id; - }; - static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); - - const auto parameters{rp.PopRaw<Parameters>()}; + const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()}; + const auto function_level{rp.PopRaw<Core::IrSensor::PackedFunctionLevel>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.applet_resource_user_id); + LOG_WARNING( + Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, function_level={}, applet_resource_user_id={}", + camera_handle.npad_type, camera_handle.npad_id, function_level.function_level, + applet_resource_user_id); + + auto result = IsIrCameraHandleValid(camera_handle); + if (result.IsSuccess()) { + // TODO: Set Function level + result = ResultSuccess; + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedImageTransferProcessorExConfig processor_config; + Core::IrSensor::PackedImageTransferProcessorExConfig processor_config; u64 transfer_memory_size; }; static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size."); @@ -313,20 +436,33 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { auto t_mem = system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, " - "applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.transfer_memory_size, parameters.applet_resource_user_id); + u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress()); + + LOG_INFO(Service_IRS, + "called, npad_type={}, npad_id={}, transfer_memory_size={}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.transfer_memory_size, parameters.applet_resource_user_id); + + auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessorWithCoreContext<ImageTransferProcessor>(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor<ImageTransferProcessor>(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + image_transfer_processor.SetTransferMemoryPointer(transfer_memory); + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; - const auto processor_config{rp.PopRaw<PackedIrLedProcessorConfig>()}; + const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()}; + const auto processor_config{rp.PopRaw<Core::IrSensor::PackedIrLedProcessorConfig>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; LOG_WARNING(Service_IRS, @@ -336,14 +472,23 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { processor_config.required_mcu_version.major, processor_config.required_mcu_version.minor, applet_resource_user_id); + auto result = IsIrCameraHandleValid(camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle); + MakeProcessor<IrLedProcessor>(camera_handle, device); + auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle); + image_transfer_processor.SetConfig(processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -356,14 +501,20 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsSuccess()) { + // TODO: Stop image processor async + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - PackedFunctionLevel function_level; + Core::IrSensor::PackedFunctionLevel function_level; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -378,7 +529,22 @@ void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { rb.Push(ResultSuccess); } -IRS::~IRS() = default; +Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const { + if (camera_handle.npad_id > + static_cast<u8>(NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld))) { + return InvalidIrCameraHandle; + } + if (camera_handle.npad_type != Core::HID::NpadStyleIndex::None) { + return InvalidIrCameraHandle; + } + return ResultSuccess; +} + +Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry( + const Core::IrSensor::IrCameraHandle& camera_handle) { + ASSERT_MSG(sizeof(StatusManager::device) > camera_handle.npad_id, "invalid npad_id"); + return shared_memory->device[camera_handle.npad_id]; +} IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} { // clang-format off @@ -395,4 +561,4 @@ IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} { IRS_SYS::~IRS_SYS() = default; -} // namespace Service::HID +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h index 361dc2213..2e6115c73 100644 --- a/src/core/hle/service/hid/irs.h +++ b/src/core/hle/service/hid/irs.h @@ -4,13 +4,19 @@ #pragma once #include "core/hid/hid_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" #include "core/hle/service/service.h" namespace Core { class System; } -namespace Service::HID { +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + +namespace Service::IRS { class IRS final : public ServiceFramework<IRS> { public: @@ -18,234 +24,19 @@ public: ~IRS() override; private: - // This is nn::irsensor::IrCameraStatus - enum IrCameraStatus : u32 { - Available, - Unsupported, - Unconnected, - }; - - // This is nn::irsensor::IrCameraInternalStatus - enum IrCameraInternalStatus : u32 { - Stopped, - FirmwareUpdateNeeded, - Unkown2, - Unkown3, - Unkown4, - FirmwareVersionRequested, - FirmwareVersionIsInvalid, - Ready, - Setting, - }; - - // This is nn::irsensor::detail::StatusManager::IrSensorMode - enum IrSensorMode : u64 { - None, - MomentProcessor, - ClusteringProcessor, - ImageTransferProcessor, - PointingProcessorMarker, - TeraPluginProcessor, - IrLedProcessor, - }; - - // This is nn::irsensor::ImageProcessorStatus - enum ImageProcessorStatus : u8 { - stopped, - running, - }; - - // This is nn::irsensor::ImageTransferProcessorFormat - enum ImageTransferProcessorFormat : u8 { - Size320x240, - Size160x120, - Size80x60, - Size40x30, - Size20x15, - }; - - // This is nn::irsensor::AdaptiveClusteringMode - enum AdaptiveClusteringMode : u8 { - StaticFov, - DynamicFov, - }; - - // This is nn::irsensor::AdaptiveClusteringTargetDistance - enum AdaptiveClusteringTargetDistance : u8 { - Near, - Middle, - Far, - }; - - // This is nn::irsensor::IrsHandAnalysisMode - enum IrsHandAnalysisMode : u8 { - Silhouette, - Image, - SilhoueteAndImage, - SilhuetteOnly, - }; - - // This is nn::irsensor::IrSensorFunctionLevel - enum IrSensorFunctionLevel : u8 { - unknown0, - unknown1, - unknown2, - unknown3, - unknown4, - }; - - // This is nn::irsensor::IrCameraHandle - struct IrCameraHandle { - u8 npad_id{}; - Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None}; - INSERT_PADDING_BYTES(2); - }; - static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size"); - - struct IrsRect { - s16 x; - s16 y; - s16 width; - s16 height; + // This is nn::irsensor::detail::AruidFormat + struct AruidFormat { + u64 sensor_aruid; + u64 sensor_aruid_status; }; + static_assert(sizeof(AruidFormat) == 0x10, "AruidFormat is an invalid size"); - // This is nn::irsensor::PackedMcuVersion - struct PackedMcuVersion { - u16 major; - u16 minor; + // This is nn::irsensor::detail::StatusManager + struct StatusManager { + std::array<Core::IrSensor::DeviceFormat, 9> device; + std::array<AruidFormat, 5> aruid; }; - static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size"); - - // This is nn::irsensor::MomentProcessorConfig - struct MomentProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(7); - IrsRect window_of_interest; - u8 preprocess; - u8 preprocess_intensity_threshold; - INSERT_PADDING_BYTES(5); - }; - static_assert(sizeof(MomentProcessorConfig) == 0x28, - "MomentProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedMomentProcessorConfig - struct PackedMomentProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - IrsRect window_of_interest; - PackedMcuVersion required_mcu_version; - u8 preprocess; - u8 preprocess_intensity_threshold; - INSERT_PADDING_BYTES(2); - }; - static_assert(sizeof(PackedMomentProcessorConfig) == 0x20, - "PackedMomentProcessorConfig is an invalid size"); - - // This is nn::irsensor::ClusteringProcessorConfig - struct ClusteringProcessorConfig { - u64 exposire_time; - u32 light_target; - u32 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(7); - IrsRect window_of_interest; - u32 pixel_count_min; - u32 pixel_count_max; - u32 object_intensity_min; - u8 is_external_light_filter_enabled; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(ClusteringProcessorConfig) == 0x30, - "ClusteringProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedClusteringProcessorConfig - struct PackedClusteringProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - IrsRect window_of_interest; - PackedMcuVersion required_mcu_version; - u32 pixel_count_min; - u32 pixel_count_max; - u32 object_intensity_min; - u8 is_external_light_filter_enabled; - INSERT_PADDING_BYTES(2); - }; - static_assert(sizeof(PackedClusteringProcessorConfig) == 0x30, - "PackedClusteringProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedImageTransferProcessorConfig - struct PackedImageTransferProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - PackedMcuVersion required_mcu_version; - u8 format; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18, - "PackedImageTransferProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedTeraPluginProcessorConfig - struct PackedTeraPluginProcessorConfig { - PackedMcuVersion required_mcu_version; - u8 mode; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8, - "PackedTeraPluginProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedPointingProcessorConfig - struct PackedPointingProcessorConfig { - IrsRect window_of_interest; - PackedMcuVersion required_mcu_version; - }; - static_assert(sizeof(PackedPointingProcessorConfig) == 0xC, - "PackedPointingProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedFunctionLevel - struct PackedFunctionLevel { - IrSensorFunctionLevel function_level; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size"); - - // This is nn::irsensor::PackedImageTransferProcessorExConfig - struct PackedImageTransferProcessorExConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - PackedMcuVersion required_mcu_version; - ImageTransferProcessorFormat origin_format; - ImageTransferProcessorFormat trimming_format; - u16 trimming_start_x; - u16 trimming_start_y; - u8 is_external_light_filter_enabled; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20, - "PackedImageTransferProcessorExConfig is an invalid size"); - - // This is nn::irsensor::PackedIrLedProcessorConfig - struct PackedIrLedProcessorConfig { - PackedMcuVersion required_mcu_version; - u8 light_target; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8, - "PackedIrLedProcessorConfig is an invalid size"); + static_assert(sizeof(StatusManager) == 0x8000, "StatusManager is an invalid size"); void ActivateIrsensor(Kernel::HLERequestContext& ctx); void DeactivateIrsensor(Kernel::HLERequestContext& ctx); @@ -265,6 +56,56 @@ private: void RunIrLedProcessor(Kernel::HLERequestContext& ctx); void StopImageProcessorAsync(Kernel::HLERequestContext& ctx); void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx); + + Result IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const; + Core::IrSensor::DeviceFormat& GetIrCameraSharedMemoryDeviceEntry( + const Core::IrSensor::IrCameraHandle& camera_handle); + + template <typename T> + void MakeProcessor(const Core::IrSensor::IrCameraHandle& handle, + Core::IrSensor::DeviceFormat& device_state) { + const auto index = static_cast<std::size_t>(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return; + } + processors[index] = std::make_unique<T>(device_state); + } + + template <typename T> + void MakeProcessorWithCoreContext(const Core::IrSensor::IrCameraHandle& handle, + Core::IrSensor::DeviceFormat& device_state) { + const auto index = static_cast<std::size_t>(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return; + } + processors[index] = std::make_unique<T>(system.HIDCore(), device_state, index); + } + + template <typename T> + T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) { + const auto index = static_cast<std::size_t>(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return static_cast<T&>(*processors[0]); + } + return static_cast<T&>(*processors[index]); + } + + template <typename T> + const T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) const { + const auto index = static_cast<std::size_t>(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return static_cast<T&>(*processors[0]); + } + return static_cast<T&>(*processors[index]); + } + + Core::HID::EmulatedController* npad_device = nullptr; + StatusManager* shared_memory = nullptr; + std::array<std::unique_ptr<ProcessorBase>, 9> processors{}; }; class IRS_SYS final : public ServiceFramework<IRS_SYS> { @@ -273,4 +114,4 @@ public: ~IRS_SYS() override; }; -} // namespace Service::HID +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.cpp b/src/core/hle/service/hid/irsensor/clustering_processor.cpp new file mode 100644 index 000000000..6479af212 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/clustering_processor.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/clustering_processor.h" + +namespace Service::IRS { +ClusteringProcessor::ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +ClusteringProcessor::~ClusteringProcessor() = default; + +void ClusteringProcessor::StartProcessor() {} + +void ClusteringProcessor::SuspendProcessor() {} + +void ClusteringProcessor::StopProcessor() {} + +void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target); + current_config.pixel_count_min = config.pixel_count_min; + current_config.pixel_count_max = config.pixel_count_max; + current_config.is_external_light_filter_enabled = config.is_external_light_filter_enabled; + current_config.object_intensity_min = config.object_intensity_min; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.h b/src/core/hle/service/hid/irsensor/clustering_processor.h new file mode 100644 index 000000000..6e2ba8846 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/clustering_processor.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class ClusteringProcessor final : public ProcessorBase { +public: + explicit ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format); + ~ClusteringProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config); + +private: + // This is nn::irsensor::ClusteringProcessorConfig + struct ClusteringProcessorConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::IrsRect window_of_interest; + u32 pixel_count_min; + u32 pixel_count_max; + u32 object_intensity_min; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(ClusteringProcessorConfig) == 0x30, + "ClusteringProcessorConfig is an invalid size"); + + // This is nn::irsensor::AdaptiveClusteringProcessorConfig + struct AdaptiveClusteringProcessorConfig { + Core::IrSensor::AdaptiveClusteringMode mode; + Core::IrSensor::AdaptiveClusteringTargetDistance target_distance; + }; + static_assert(sizeof(AdaptiveClusteringProcessorConfig) == 0x8, + "AdaptiveClusteringProcessorConfig is an invalid size"); + + // This is nn::irsensor::ClusteringData + struct ClusteringData { + f32 average_intensity; + Core::IrSensor::IrsCentroid centroid; + u32 pixel_count; + Core::IrSensor::IrsRect bound; + }; + static_assert(sizeof(ClusteringData) == 0x18, "ClusteringData is an invalid size"); + + // This is nn::irsensor::ClusteringProcessorState + struct ClusteringProcessorState { + s64 sampling_number; + u64 timestamp; + u8 object_count; + INSERT_PADDING_BYTES(3); + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + std::array<ClusteringData, 0x10> data; + }; + static_assert(sizeof(ClusteringProcessorState) == 0x198, + "ClusteringProcessorState is an invalid size"); + + ClusteringProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp new file mode 100644 index 000000000..98f0c579d --- /dev/null +++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hle/service/hid/irsensor/image_transfer_processor.h" + +namespace Service::IRS { +ImageTransferProcessor::ImageTransferProcessor(Core::HID::HIDCore& hid_core_, + Core::IrSensor::DeviceFormat& device_format, + std::size_t npad_index) + : device{device_format} { + npad_device = hid_core_.GetEmulatedControllerByIndex(npad_index); + + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); }, + .is_npad_service = true, + }; + callback_key = npad_device->SetCallback(engine_callback); + + device.mode = Core::IrSensor::IrSensorMode::ImageTransferProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +ImageTransferProcessor::~ImageTransferProcessor() { + npad_device->DeleteCallback(callback_key); +}; + +void ImageTransferProcessor::StartProcessor() { + is_active = true; + device.camera_status = Core::IrSensor::IrCameraStatus::Available; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready; + processor_state.sampling_number = 0; + processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low; +} + +void ImageTransferProcessor::SuspendProcessor() {} + +void ImageTransferProcessor::StopProcessor() {} + +void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) { + if (type != Core::HID::ControllerTriggerType::IrSensor) { + return; + } + if (!is_transfer_memory_set) { + return; + } + + const auto camera_data = npad_device->GetCamera(); + + // This indicates how much ambient light is precent + processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low; + processor_state.sampling_number = camera_data.sample; + + if (camera_data.format != current_config.origin_format) { + LOG_WARNING(Service_IRS, "Wrong Input format {} expected {}", camera_data.format, + current_config.origin_format); + memset(transfer_memory, 0, GetDataSize(current_config.trimming_format)); + return; + } + + if (current_config.origin_format > current_config.trimming_format) { + LOG_WARNING(Service_IRS, "Origin format {} is smaller than trimming format {}", + current_config.origin_format, current_config.trimming_format); + memset(transfer_memory, 0, GetDataSize(current_config.trimming_format)); + return; + } + + std::vector<u8> window_data{}; + const auto origin_width = GetDataWidth(current_config.origin_format); + const auto origin_height = GetDataHeight(current_config.origin_format); + const auto trimming_width = GetDataWidth(current_config.trimming_format); + const auto trimming_height = GetDataHeight(current_config.trimming_format); + window_data.resize(GetDataSize(current_config.trimming_format)); + + if (trimming_width + current_config.trimming_start_x > origin_width || + trimming_height + current_config.trimming_start_y > origin_height) { + LOG_WARNING(Service_IRS, + "Trimming area ({}, {}, {}, {}) is outside of origin area ({}, {})", + current_config.trimming_start_x, current_config.trimming_start_y, + trimming_width, trimming_height, origin_width, origin_height); + memset(transfer_memory, 0, GetDataSize(current_config.trimming_format)); + return; + } + + for (std::size_t y = 0; y < trimming_height; y++) { + for (std::size_t x = 0; x < trimming_width; x++) { + const std::size_t window_index = (y * trimming_width) + x; + const std::size_t origin_index = + ((y + current_config.trimming_start_y) * origin_width) + x + + current_config.trimming_start_x; + window_data[window_index] = camera_data.data[origin_index]; + } + } + + memcpy(transfer_memory, window_data.data(), GetDataSize(current_config.trimming_format)); + + if (!IsProcessorActive()) { + StartProcessor(); + } +} + +void ImageTransferProcessor::SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target); + current_config.origin_format = + static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format); + current_config.trimming_format = + static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format); + current_config.trimming_start_x = 0; + current_config.trimming_start_y = 0; + + npad_device->SetCameraFormat(current_config.origin_format); +} + +void ImageTransferProcessor::SetConfig( + Core::IrSensor::PackedImageTransferProcessorExConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target); + current_config.origin_format = + static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.origin_format); + current_config.trimming_format = + static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.trimming_format); + current_config.trimming_start_x = config.trimming_start_x; + current_config.trimming_start_y = config.trimming_start_y; + + npad_device->SetCameraFormat(current_config.origin_format); +} + +void ImageTransferProcessor::SetTransferMemoryPointer(u8* t_mem) { + is_transfer_memory_set = true; + transfer_memory = t_mem; +} + +Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState( + std::vector<u8>& data) const { + const auto size = GetDataSize(current_config.trimming_format); + data.resize(size); + memcpy(data.data(), transfer_memory, size); + return processor_state; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.h b/src/core/hle/service/hid/irsensor/image_transfer_processor.h new file mode 100644 index 000000000..393df492d --- /dev/null +++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + +namespace Service::IRS { +class ImageTransferProcessor final : public ProcessorBase { +public: + explicit ImageTransferProcessor(Core::HID::HIDCore& hid_core_, + Core::IrSensor::DeviceFormat& device_format, + std::size_t npad_index); + ~ImageTransferProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config); + void SetConfig(Core::IrSensor::PackedImageTransferProcessorExConfig config); + + // Transfer memory where the image data will be stored + void SetTransferMemoryPointer(u8* t_mem); + + Core::IrSensor::ImageTransferProcessorState GetState(std::vector<u8>& data) const; + +private: + // This is nn::irsensor::ImageTransferProcessorConfig + struct ImageTransferProcessorConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::ImageTransferProcessorFormat format; + }; + static_assert(sizeof(ImageTransferProcessorConfig) == 0x20, + "ImageTransferProcessorConfig is an invalid size"); + + // This is nn::irsensor::ImageTransferProcessorExConfig + struct ImageTransferProcessorExConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::ImageTransferProcessorFormat origin_format; + Core::IrSensor::ImageTransferProcessorFormat trimming_format; + u16 trimming_start_x; + u16 trimming_start_y; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(ImageTransferProcessorExConfig) == 0x28, + "ImageTransferProcessorExConfig is an invalid size"); + + void OnControllerUpdate(Core::HID::ControllerTriggerType type); + + ImageTransferProcessorExConfig current_config{}; + Core::IrSensor::ImageTransferProcessorState processor_state{}; + Core::IrSensor::DeviceFormat& device; + Core::HID::EmulatedController* npad_device; + int callback_key{}; + + u8* transfer_memory = nullptr; + bool is_transfer_memory_set = false; +}; +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.cpp b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp new file mode 100644 index 000000000..8e6dd99e4 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/ir_led_processor.h" + +namespace Service::IRS { +IrLedProcessor::IrLedProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::IrLedProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +IrLedProcessor::~IrLedProcessor() = default; + +void IrLedProcessor::StartProcessor() {} + +void IrLedProcessor::SuspendProcessor() {} + +void IrLedProcessor::StopProcessor() {} + +void IrLedProcessor::SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config) { + current_config.light_target = + static_cast<Core::IrSensor::CameraLightTarget>(config.light_target); +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.h b/src/core/hle/service/hid/irsensor/ir_led_processor.h new file mode 100644 index 000000000..c3d8693c9 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/ir_led_processor.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class IrLedProcessor final : public ProcessorBase { +public: + explicit IrLedProcessor(Core::IrSensor::DeviceFormat& device_format); + ~IrLedProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config); + +private: + // This is nn::irsensor::IrLedProcessorConfig + struct IrLedProcessorConfig { + Core::IrSensor::CameraLightTarget light_target; + }; + static_assert(sizeof(IrLedProcessorConfig) == 0x4, "IrLedProcessorConfig is an invalid size"); + + struct IrLedProcessorState { + s64 sampling_number; + u64 timestamp; + std::array<u8, 0x8> data; + }; + static_assert(sizeof(IrLedProcessorState) == 0x18, "IrLedProcessorState is an invalid size"); + + IrLedProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/moment_processor.cpp b/src/core/hle/service/hid/irsensor/moment_processor.cpp new file mode 100644 index 000000000..dbaca420a --- /dev/null +++ b/src/core/hle/service/hid/irsensor/moment_processor.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/moment_processor.h" + +namespace Service::IRS { +MomentProcessor::MomentProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::MomentProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +MomentProcessor::~MomentProcessor() = default; + +void MomentProcessor::StartProcessor() {} + +void MomentProcessor::SuspendProcessor() {} + +void MomentProcessor::StopProcessor() {} + +void MomentProcessor::SetConfig(Core::IrSensor::PackedMomentProcessorConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target); + current_config.window_of_interest = config.window_of_interest; + current_config.preprocess = + static_cast<Core::IrSensor::MomentProcessorPreprocess>(config.preprocess); + current_config.preprocess_intensity_threshold = config.preprocess_intensity_threshold; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/moment_processor.h b/src/core/hle/service/hid/irsensor/moment_processor.h new file mode 100644 index 000000000..d4bd22e0f --- /dev/null +++ b/src/core/hle/service/hid/irsensor/moment_processor.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class MomentProcessor final : public ProcessorBase { +public: + explicit MomentProcessor(Core::IrSensor::DeviceFormat& device_format); + ~MomentProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedMomentProcessorConfig config); + +private: + // This is nn::irsensor::MomentProcessorConfig + struct MomentProcessorConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::IrsRect window_of_interest; + Core::IrSensor::MomentProcessorPreprocess preprocess; + u32 preprocess_intensity_threshold; + }; + static_assert(sizeof(MomentProcessorConfig) == 0x28, + "MomentProcessorConfig is an invalid size"); + + // This is nn::irsensor::MomentStatistic + struct MomentStatistic { + f32 average_intensity; + Core::IrSensor::IrsCentroid centroid; + }; + static_assert(sizeof(MomentStatistic) == 0xC, "MomentStatistic is an invalid size"); + + // This is nn::irsensor::MomentProcessorState + struct MomentProcessorState { + s64 sampling_number; + u64 timestamp; + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + INSERT_PADDING_BYTES(4); + std::array<MomentStatistic, 0x30> stadistic; + }; + static_assert(sizeof(MomentProcessorState) == 0x258, "MomentProcessorState is an invalid size"); + + MomentProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.cpp b/src/core/hle/service/hid/irsensor/pointing_processor.cpp new file mode 100644 index 000000000..929f177fc --- /dev/null +++ b/src/core/hle/service/hid/irsensor/pointing_processor.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/pointing_processor.h" + +namespace Service::IRS { +PointingProcessor::PointingProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::PointingProcessorMarker; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +PointingProcessor::~PointingProcessor() = default; + +void PointingProcessor::StartProcessor() {} + +void PointingProcessor::SuspendProcessor() {} + +void PointingProcessor::StopProcessor() {} + +void PointingProcessor::SetConfig(Core::IrSensor::PackedPointingProcessorConfig config) { + current_config.window_of_interest = config.window_of_interest; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.h b/src/core/hle/service/hid/irsensor/pointing_processor.h new file mode 100644 index 000000000..cf4930794 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/pointing_processor.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class PointingProcessor final : public ProcessorBase { +public: + explicit PointingProcessor(Core::IrSensor::DeviceFormat& device_format); + ~PointingProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedPointingProcessorConfig config); + +private: + // This is nn::irsensor::PointingProcessorConfig + struct PointingProcessorConfig { + Core::IrSensor::IrsRect window_of_interest; + }; + static_assert(sizeof(PointingProcessorConfig) == 0x8, + "PointingProcessorConfig is an invalid size"); + + struct PointingProcessorMarkerData { + u8 pointing_status; + INSERT_PADDING_BYTES(3); + u32 unknown; + float unkown_float1; + float position_x; + float position_y; + float unkown_float2; + Core::IrSensor::IrsRect window_of_interest; + }; + static_assert(sizeof(PointingProcessorMarkerData) == 0x20, + "PointingProcessorMarkerData is an invalid size"); + + struct PointingProcessorMarkerState { + s64 sampling_number; + u64 timestamp; + std::array<PointingProcessorMarkerData, 0x3> data; + }; + static_assert(sizeof(PointingProcessorMarkerState) == 0x70, + "PointingProcessorMarkerState is an invalid size"); + + PointingProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/processor_base.cpp b/src/core/hle/service/hid/irsensor/processor_base.cpp new file mode 100644 index 000000000..4d43ca17a --- /dev/null +++ b/src/core/hle/service/hid/irsensor/processor_base.cpp @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { + +ProcessorBase::ProcessorBase() {} +ProcessorBase::~ProcessorBase() = default; + +bool ProcessorBase::IsProcessorActive() const { + return is_active; +} + +std::size_t ProcessorBase::GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const { + switch (format) { + case Core::IrSensor::ImageTransferProcessorFormat::Size320x240: + return 320 * 240; + case Core::IrSensor::ImageTransferProcessorFormat::Size160x120: + return 160 * 120; + case Core::IrSensor::ImageTransferProcessorFormat::Size80x60: + return 80 * 60; + case Core::IrSensor::ImageTransferProcessorFormat::Size40x30: + return 40 * 30; + case Core::IrSensor::ImageTransferProcessorFormat::Size20x15: + return 20 * 15; + default: + return 0; + } +} + +std::size_t ProcessorBase::GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const { + switch (format) { + case Core::IrSensor::ImageTransferProcessorFormat::Size320x240: + return 320; + case Core::IrSensor::ImageTransferProcessorFormat::Size160x120: + return 160; + case Core::IrSensor::ImageTransferProcessorFormat::Size80x60: + return 80; + case Core::IrSensor::ImageTransferProcessorFormat::Size40x30: + return 40; + case Core::IrSensor::ImageTransferProcessorFormat::Size20x15: + return 20; + default: + return 0; + } +} + +std::size_t ProcessorBase::GetDataHeight( + Core::IrSensor::ImageTransferProcessorFormat format) const { + switch (format) { + case Core::IrSensor::ImageTransferProcessorFormat::Size320x240: + return 240; + case Core::IrSensor::ImageTransferProcessorFormat::Size160x120: + return 120; + case Core::IrSensor::ImageTransferProcessorFormat::Size80x60: + return 60; + case Core::IrSensor::ImageTransferProcessorFormat::Size40x30: + return 30; + case Core::IrSensor::ImageTransferProcessorFormat::Size20x15: + return 15; + default: + return 0; + } +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/processor_base.h b/src/core/hle/service/hid/irsensor/processor_base.h new file mode 100644 index 000000000..bc0d2977b --- /dev/null +++ b/src/core/hle/service/hid/irsensor/processor_base.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" + +namespace Service::IRS { +class ProcessorBase { +public: + explicit ProcessorBase(); + virtual ~ProcessorBase(); + + virtual void StartProcessor() = 0; + virtual void SuspendProcessor() = 0; + virtual void StopProcessor() = 0; + + bool IsProcessorActive() const; + +protected: + /// Returns the number of bytes the image uses + std::size_t GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const; + + /// Returns the width of the image + std::size_t GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const; + + /// Returns the height of the image + std::size_t GetDataHeight(Core::IrSensor::ImageTransferProcessorFormat format) const; + + bool is_active{false}; +}; +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp new file mode 100644 index 000000000..e691c840a --- /dev/null +++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/tera_plugin_processor.h" + +namespace Service::IRS { +TeraPluginProcessor::TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::TeraPluginProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +TeraPluginProcessor::~TeraPluginProcessor() = default; + +void TeraPluginProcessor::StartProcessor() {} + +void TeraPluginProcessor::SuspendProcessor() {} + +void TeraPluginProcessor::StopProcessor() {} + +void TeraPluginProcessor::SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config) { + current_config.mode = config.mode; + current_config.unknown_1 = config.unknown_1; + current_config.unknown_2 = config.unknown_2; + current_config.unknown_3 = config.unknown_3; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.h b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h new file mode 100644 index 000000000..bbea7ed0b --- /dev/null +++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class TeraPluginProcessor final : public ProcessorBase { +public: + explicit TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format); + ~TeraPluginProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config); + +private: + // This is nn::irsensor::TeraPluginProcessorConfig + struct TeraPluginProcessorConfig { + u8 mode; + u8 unknown_1; + u8 unknown_2; + u8 unknown_3; + }; + static_assert(sizeof(TeraPluginProcessorConfig) == 0x4, + "TeraPluginProcessorConfig is an invalid size"); + + struct TeraPluginProcessorState { + s64 sampling_number; + u64 timestamp; + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + std::array<u8, 0x12c> data; + }; + static_assert(sizeof(TeraPluginProcessorState) == 0x140, + "TeraPluginProcessorState is an invalid size"); + + TeraPluginProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 0310ce883..7055ea93e 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -30,6 +30,19 @@ enum class RequestState : u32 { Connected = 3, }; +enum class InternetConnectionType : u8 { + WiFi = 1, + Ethernet = 2, +}; + +enum class InternetConnectionStatus : u8 { + ConnectingUnknown1, + ConnectingUnknown2, + ConnectingUnknown3, + ConnectingUnknown4, + Connected, +}; + struct IpAddressSetting { bool is_automatic{}; Network::IPv4Address current_address{}; @@ -271,6 +284,7 @@ private: rb.Push(ResultSuccess); rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid } + void CreateScanRequest(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIFM, "called"); @@ -279,6 +293,7 @@ private: rb.Push(ResultSuccess); rb.PushIpcInterface<IScanRequest>(system); } + void CreateRequest(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIFM, "called"); @@ -287,6 +302,7 @@ private: rb.Push(ResultSuccess); rb.PushIpcInterface<IRequest>(system); } + void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -335,12 +351,14 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } + void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } + void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -354,6 +372,7 @@ private: rb.Push(ResultSuccess); rb.PushRaw(*ipv4); } + void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIFM, "called"); @@ -369,6 +388,7 @@ private: rb.PushIpcInterface<INetworkProfile>(system); rb.PushRaw<u128>(uuid); } + void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -405,6 +425,7 @@ private: rb.Push(ResultSuccess); rb.PushRaw<IpConfigInfo>(ip_config_info); } + void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -412,6 +433,24 @@ private: rb.Push(ResultSuccess); rb.Push<u8>(0); } + + void GetInternetConnectionStatus(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + + struct Output { + InternetConnectionType type{InternetConnectionType::WiFi}; + u8 wifi_strength{3}; + InternetConnectionStatus state{InternetConnectionStatus::Connected}; + }; + static_assert(sizeof(Output) == 0x3, "Output has incorrect size."); + + constexpr Output out{}; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushRaw(out); + } + void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -423,6 +462,7 @@ private: rb.Push<u8>(0); } } + void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -456,7 +496,7 @@ IGeneralService::IGeneralService(Core::System& system_) {15, &IGeneralService::GetCurrentIpConfigInfo, "GetCurrentIpConfigInfo"}, {16, nullptr, "SetWirelessCommunicationEnabled"}, {17, &IGeneralService::IsWirelessCommunicationEnabled, "IsWirelessCommunicationEnabled"}, - {18, nullptr, "GetInternetConnectionStatus"}, + {18, &IGeneralService::GetInternetConnectionStatus, "GetInternetConnectionStatus"}, {19, nullptr, "SetEthernetCommunicationEnabled"}, {20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"}, {21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"}, diff --git a/src/core/hle/service/nvflinger/binder.h b/src/core/hle/service/nvflinger/binder.h index 21aaa40cd..157333ff8 100644 --- a/src/core/hle/service/nvflinger/binder.h +++ b/src/core/hle/service/nvflinger/binder.h @@ -34,6 +34,7 @@ enum class TransactionId { class IBinder { public: + virtual ~IBinder() = default; virtual void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) = 0; virtual Kernel::KReadableEvent& GetNativeHandle() = 0; diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 5f69c8c2c..5574269eb 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -287,9 +287,21 @@ s64 NVFlinger::GetNextTicks() const { static constexpr s64 max_hertz = 120LL; const auto& settings = Settings::values; - const bool unlocked_fps = settings.disable_fps_limit.GetValue(); - const s64 fps_cap = unlocked_fps ? static_cast<s64>(settings.fps_cap.GetValue()) : 1; - return (1000000000 * (1LL << swap_interval)) / (max_hertz * fps_cap); + auto speed_scale = 1.f; + if (settings.use_multi_core.GetValue()) { + if (settings.use_speed_limit.GetValue()) { + // Scales the speed based on speed_limit setting on MC. SC is handled by + // SpeedLimiter::DoSpeedLimiting. + speed_scale = 100.f / settings.speed_limit.GetValue(); + } else { + // Run at unlocked framerate. + speed_scale = 0.01f; + } + } + + const auto next_ticks = ((1000000000 * (1LL << swap_interval)) / max_hertz); + + return static_cast<s64>(speed_scale * static_cast<float>(next_ticks)); } } // namespace Service::NVFlinger diff --git a/src/core/hle/service/ptm/psm.cpp b/src/core/hle/service/ptm/psm.cpp index 9e0eb6ac0..2c31e9485 100644 --- a/src/core/hle/service/ptm/psm.cpp +++ b/src/core/hle/service/ptm/psm.cpp @@ -9,10 +9,8 @@ #include "core/hle/kernel/k_event.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/ptm/psm.h" -#include "core/hle/service/service.h" -#include "core/hle/service/sm/sm.h" -namespace Service::PSM { +namespace Service::PTM { class IPsmSession final : public ServiceFramework<IPsmSession> { public: @@ -57,7 +55,7 @@ public: private: void BindStateChangeEvent(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PSM, "called"); + LOG_DEBUG(Service_PTM, "called"); should_signal = true; @@ -67,7 +65,7 @@ private: } void UnbindStateChangeEvent(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PSM, "called"); + LOG_DEBUG(Service_PTM, "called"); should_signal = false; @@ -78,7 +76,7 @@ private: void SetChargerTypeChangeEventEnabled(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto state = rp.Pop<bool>(); - LOG_DEBUG(Service_PSM, "called, state={}", state); + LOG_DEBUG(Service_PTM, "called, state={}", state); should_signal_charger_type = state; @@ -89,7 +87,7 @@ private: void SetPowerSupplyChangeEventEnabled(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto state = rp.Pop<bool>(); - LOG_DEBUG(Service_PSM, "called, state={}", state); + LOG_DEBUG(Service_PTM, "called, state={}", state); should_signal_power_supply = state; @@ -100,7 +98,7 @@ private: void SetBatteryVoltageStateChangeEventEnabled(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto state = rp.Pop<bool>(); - LOG_DEBUG(Service_PSM, "called, state={}", state); + LOG_DEBUG(Service_PTM, "called, state={}", state); should_signal_battery_voltage = state; @@ -117,76 +115,58 @@ private: Kernel::KEvent* state_change_event; }; -class PSM final : public ServiceFramework<PSM> { -public: - explicit PSM(Core::System& system_) : ServiceFramework{system_, "psm"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"}, - {1, &PSM::GetChargerType, "GetChargerType"}, - {2, nullptr, "EnableBatteryCharging"}, - {3, nullptr, "DisableBatteryCharging"}, - {4, nullptr, "IsBatteryChargingEnabled"}, - {5, nullptr, "AcquireControllerPowerSupply"}, - {6, nullptr, "ReleaseControllerPowerSupply"}, - {7, &PSM::OpenSession, "OpenSession"}, - {8, nullptr, "EnableEnoughPowerChargeEmulation"}, - {9, nullptr, "DisableEnoughPowerChargeEmulation"}, - {10, nullptr, "EnableFastBatteryCharging"}, - {11, nullptr, "DisableFastBatteryCharging"}, - {12, nullptr, "GetBatteryVoltageState"}, - {13, nullptr, "GetRawBatteryChargePercentage"}, - {14, nullptr, "IsEnoughPowerSupplied"}, - {15, nullptr, "GetBatteryAgePercentage"}, - {16, nullptr, "GetBatteryChargeInfoEvent"}, - {17, nullptr, "GetBatteryChargeInfoFields"}, - {18, nullptr, "GetBatteryChargeCalibratedEvent"}, - }; - // clang-format on - - RegisterHandlers(functions); - } - - ~PSM() override = default; - -private: - void GetBatteryChargePercentage(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PSM, "called"); +PSM::PSM(Core::System& system_) : ServiceFramework{system_, "psm"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"}, + {1, &PSM::GetChargerType, "GetChargerType"}, + {2, nullptr, "EnableBatteryCharging"}, + {3, nullptr, "DisableBatteryCharging"}, + {4, nullptr, "IsBatteryChargingEnabled"}, + {5, nullptr, "AcquireControllerPowerSupply"}, + {6, nullptr, "ReleaseControllerPowerSupply"}, + {7, &PSM::OpenSession, "OpenSession"}, + {8, nullptr, "EnableEnoughPowerChargeEmulation"}, + {9, nullptr, "DisableEnoughPowerChargeEmulation"}, + {10, nullptr, "EnableFastBatteryCharging"}, + {11, nullptr, "DisableFastBatteryCharging"}, + {12, nullptr, "GetBatteryVoltageState"}, + {13, nullptr, "GetRawBatteryChargePercentage"}, + {14, nullptr, "IsEnoughPowerSupplied"}, + {15, nullptr, "GetBatteryAgePercentage"}, + {16, nullptr, "GetBatteryChargeInfoEvent"}, + {17, nullptr, "GetBatteryChargeInfoFields"}, + {18, nullptr, "GetBatteryChargeCalibratedEvent"}, + }; + // clang-format on - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(battery_charge_percentage); - } + RegisterHandlers(functions); +} - void GetChargerType(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PSM, "called"); +PSM::~PSM() = default; - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(charger_type); - } +void PSM::GetBatteryChargePercentage(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PTM, "called"); - void OpenSession(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_PSM, "called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u32>(battery_charge_percentage); +} - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IPsmSession>(system); - } +void PSM::GetChargerType(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PTM, "called"); - enum class ChargerType : u32 { - Unplugged = 0, - RegularCharger = 1, - LowPowerCharger = 2, - Unknown = 3, - }; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(charger_type); +} - u32 battery_charge_percentage{100}; // 100% - ChargerType charger_type{ChargerType::RegularCharger}; -}; +void PSM::OpenSession(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PTM, "called"); -void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { - std::make_shared<PSM>(system)->InstallAsService(sm); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IPsmSession>(system); } -} // namespace Service::PSM +} // namespace Service::PTM diff --git a/src/core/hle/service/ptm/psm.h b/src/core/hle/service/ptm/psm.h index 94a1044db..f674ba8bc 100644 --- a/src/core/hle/service/ptm/psm.h +++ b/src/core/hle/service/ptm/psm.h @@ -3,16 +3,29 @@ #pragma once -namespace Core { -class System; -} +#include "core/hle/service/service.h" -namespace Service::SM { -class ServiceManager; -} +namespace Service::PTM { -namespace Service::PSM { +class PSM final : public ServiceFramework<PSM> { +public: + explicit PSM(Core::System& system_); + ~PSM() override; -void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); +private: + enum class ChargerType : u32 { + Unplugged = 0, + RegularCharger = 1, + LowPowerCharger = 2, + Unknown = 3, + }; -} // namespace Service::PSM + void GetBatteryChargePercentage(Kernel::HLERequestContext& ctx); + void GetChargerType(Kernel::HLERequestContext& ctx); + void OpenSession(Kernel::HLERequestContext& ctx); + + u32 battery_charge_percentage{100}; + ChargerType charger_type{ChargerType::RegularCharger}; +}; + +} // namespace Service::PTM diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp new file mode 100644 index 000000000..4bea995c6 --- /dev/null +++ b/src/core/hle/service/ptm/ptm.cpp @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <memory> + +#include "core/core.h" +#include "core/hle/service/ptm/psm.h" +#include "core/hle/service/ptm/ptm.h" +#include "core/hle/service/ptm/ts.h" + +namespace Service::PTM { + +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<PSM>(system)->InstallAsService(sm); + std::make_shared<TS>(system)->InstallAsService(sm); +} + +} // namespace Service::PTM diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h new file mode 100644 index 000000000..06224a24e --- /dev/null +++ b/src/core/hle/service/ptm/ptm.h @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +namespace Core { +class System; +} + +namespace Service::SM { +class ServiceManager; +} + +namespace Service::PTM { + +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); + +} // namespace Service::PTM diff --git a/src/core/hle/service/ptm/ts.cpp b/src/core/hle/service/ptm/ts.cpp new file mode 100644 index 000000000..65c3f135f --- /dev/null +++ b/src/core/hle/service/ptm/ts.cpp @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <memory> + +#include "core/core.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/ptm/ts.h" + +namespace Service::PTM { + +TS::TS(Core::System& system_) : ServiceFramework{system_, "ts"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetTemperatureRange"}, + {1, &TS::GetTemperature, "GetTemperature"}, + {2, nullptr, "SetMeasurementMode"}, + {3, nullptr, "GetTemperatureMilliC"}, + {4, nullptr, "OpenSession"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +TS::~TS() = default; + +void TS::GetTemperature(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto location{rp.PopEnum<Location>()}; + + LOG_WARNING(Service_HID, "(STUBBED) called. location={}", location); + + const s32 temperature = location == Location::Internal ? 35 : 20; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(temperature); +} + +} // namespace Service::PTM diff --git a/src/core/hle/service/ptm/ts.h b/src/core/hle/service/ptm/ts.h new file mode 100644 index 000000000..39a734ef7 --- /dev/null +++ b/src/core/hle/service/ptm/ts.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hle/service/service.h" + +namespace Service::PTM { + +class TS final : public ServiceFramework<TS> { +public: + explicit TS(Core::System& system_); + ~TS() override; + +private: + enum class Location : u8 { + Internal, + External, + }; + + void GetTemperature(Kernel::HLERequestContext& ctx); +}; + +} // namespace Service::PTM diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 318009e4f..c64291e7f 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -58,7 +58,7 @@ #include "core/hle/service/pm/pm.h" #include "core/hle/service/prepo/prepo.h" #include "core/hle/service/psc/psc.h" -#include "core/hle/service/ptm/psm.h" +#include "core/hle/service/ptm/ptm.h" #include "core/hle/service/service.h" #include "core/hle/service/set/settings.h" #include "core/hle/service/sm/sm.h" @@ -287,7 +287,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system PlayReport::InstallInterfaces(*sm, system); PM::InstallInterfaces(system); PSC::InstallInterfaces(*sm, system); - PSM::InstallInterfaces(*sm, system); + PTM::InstallInterfaces(*sm, system); Set::InstallInterfaces(*sm, system); Sockets::InstallInterfaces(*sm, system); SPL::InstallInterfaces(*sm, system); diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 5114b8be2..3e9dc4a13 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -720,7 +720,25 @@ std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message) if (!IsFileDescriptorValid(fd)) { return {-1, Errno::BADF}; } - return Translate(file_descriptors[fd]->socket->Recv(flags, message)); + + FileDescriptor& descriptor = *file_descriptors[fd]; + + // Apply flags + if ((flags & FLAG_MSG_DONTWAIT) != 0) { + flags &= ~FLAG_MSG_DONTWAIT; + if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { + descriptor.socket->SetNonBlock(true); + } + } + + const auto [ret, bsd_errno] = Translate(descriptor.socket->Recv(flags, message)); + + // Restore original state + if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { + descriptor.socket->SetNonBlock(false); + } + + return {ret, bsd_errno}; } std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message, diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 584808d50..635449fce 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -511,7 +511,7 @@ struct Memory::Impl { [[nodiscard]] u8* GetPointerImpl(VAddr vaddr, auto on_unmapped, auto on_rasterizer) const { // AARCH64 masks the upper 16 bit of all memory accesses - vaddr &= 0xffffffffffffLL; + vaddr &= 0xffffffffffffULL; if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) { on_unmapped(); @@ -776,6 +776,10 @@ void Memory::CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr s impl->CopyBlock(process, dest_addr, src_addr, size); } +void Memory::ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, const std::size_t size) { + impl->ZeroBlock(process, dest_addr, size); +} + void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { impl->RasterizerMarkRegionCached(vaddr, size, cached); } diff --git a/src/core/memory.h b/src/core/memory.h index f22c0a2d8..780c45385 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -437,6 +437,19 @@ public: std::size_t size); /** + * Zeros a range of bytes within the current process' address space at the specified + * virtual address. + * + * @param process The process that will have data zeroed within its address space. + * @param dest_addr The destination virtual address to zero the data from. + * @param size The size of the range to zero out, in bytes. + * + * @post The range [dest_addr, size) within the process' address space contains the + * value 0. + */ + void ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size); + + /** * Marks each page within the specified address range as cached or uncached. * * @param vaddr The virtual address indicating the start of the address range. diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 48e799cf5..90dd629c6 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -1,4 +1,6 @@ add_library(input_common STATIC + drivers/camera.cpp + drivers/camera.h drivers/gc_adapter.cpp drivers/gc_adapter.h drivers/keyboard.cpp diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp new file mode 100644 index 000000000..dceea67e0 --- /dev/null +++ b/src/input_common/drivers/camera.cpp @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <fmt/format.h> + +#include "common/param_package.h" +#include "input_common/drivers/camera.h" + +namespace InputCommon { +constexpr PadIdentifier identifier = { + .guid = Common::UUID{}, + .port = 0, + .pad = 0, +}; + +Camera::Camera(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + PreSetController(identifier); +} + +void Camera::SetCameraData(std::size_t width, std::size_t height, std::vector<u32> data) { + const std::size_t desired_width = getImageWidth(); + const std::size_t desired_height = getImageHeight(); + status.data.resize(desired_width * desired_height); + + // Resize image to desired format + for (std::size_t y = 0; y < desired_height; y++) { + for (std::size_t x = 0; x < desired_width; x++) { + const std::size_t pixel_index = y * desired_width + x; + const std::size_t old_x = width * x / desired_width; + const std::size_t old_y = height * y / desired_height; + const std::size_t data_pixel_index = old_y * width + old_x; + status.data[pixel_index] = static_cast<u8>(data[data_pixel_index] & 0xFF); + } + } + + SetCamera(identifier, status); +} + +std::size_t Camera::getImageWidth() const { + switch (status.format) { + case Common::Input::CameraFormat::Size320x240: + return 320; + case Common::Input::CameraFormat::Size160x120: + return 160; + case Common::Input::CameraFormat::Size80x60: + return 80; + case Common::Input::CameraFormat::Size40x30: + return 40; + case Common::Input::CameraFormat::Size20x15: + return 20; + case Common::Input::CameraFormat::None: + default: + return 0; + } +} + +std::size_t Camera::getImageHeight() const { + switch (status.format) { + case Common::Input::CameraFormat::Size320x240: + return 240; + case Common::Input::CameraFormat::Size160x120: + return 120; + case Common::Input::CameraFormat::Size80x60: + return 60; + case Common::Input::CameraFormat::Size40x30: + return 30; + case Common::Input::CameraFormat::Size20x15: + return 15; + case Common::Input::CameraFormat::None: + default: + return 0; + } +} + +Common::Input::CameraError Camera::SetCameraFormat( + [[maybe_unused]] const PadIdentifier& identifier_, + const Common::Input::CameraFormat camera_format) { + status.format = camera_format; + return Common::Input::CameraError::None; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h new file mode 100644 index 000000000..b8a7c75e5 --- /dev/null +++ b/src/input_common/drivers/camera.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Camera final : public InputEngine { +public: + explicit Camera(std::string input_engine_); + + void SetCameraData(std::size_t width, std::size_t height, std::vector<u32> data); + + std::size_t getImageWidth() const; + std::size_t getImageHeight() const; + + Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_, + Common::Input::CameraFormat camera_format) override; + + Common::Input::CameraStatus status{}; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 446c027d3..00474ac77 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -438,10 +438,17 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en using namespace std::chrono_literals; while (initialized) { SDL_PumpEvents(); - SendVibrations(); std::this_thread::sleep_for(1ms); } }); + vibration_thread = std::thread([this] { + Common::SetCurrentThreadName("yuzu:input:SDL_Vibration"); + using namespace std::chrono_literals; + while (initialized) { + SendVibrations(); + std::this_thread::sleep_for(10ms); + } + }); } // Because the events for joystick connection happens before we have our event watcher added, we // can just open all the joysticks right here @@ -457,6 +464,7 @@ SDLDriver::~SDLDriver() { initialized = false; if (start_thread) { poll_thread.join(); + vibration_thread.join(); SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); } } diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index 0846fbb50..7dc7a93c7 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -128,5 +128,6 @@ private: std::atomic<bool> initialized = false; std::thread poll_thread; + std::thread vibration_thread; }; } // namespace InputCommon diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp index 12214d146..6ede0e4b0 100644 --- a/src/input_common/input_engine.cpp +++ b/src/input_common/input_engine.cpp @@ -90,6 +90,18 @@ void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const B TriggerOnMotionChange(identifier, motion, value); } +void InputEngine::SetCamera(const PadIdentifier& identifier, + const Common::Input::CameraStatus& value) { + { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.camera = value; + } + } + TriggerOnCameraChange(identifier, value); +} + bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const { std::scoped_lock lock{mutex}; const auto controller_iter = controller_list.find(identifier); @@ -165,6 +177,18 @@ BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) return controller.motions.at(motion); } +Common::Input::CameraStatus InputEngine::GetCamera(const PadIdentifier& identifier) const { + std::scoped_lock lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(), + identifier.pad, identifier.port); + return {}; + } + const ControllerData& controller = controller_iter->second; + return controller.camera; +} + void InputEngine::ResetButtonState() { for (const auto& controller : controller_list) { for (const auto& button : controller.second.buttons) { @@ -317,6 +341,20 @@ void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int mot }); } +void InputEngine::TriggerOnCameraChange(const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::CameraStatus& value) { + std::scoped_lock lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Camera, 0)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } +} + bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier, const PadIdentifier& identifier, EngineInputType type, int index) const { diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index 13295bd49..f6b3c4610 100644 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h @@ -36,11 +36,12 @@ struct BasicMotion { // Types of input that are stored in the engine enum class EngineInputType { None, + Analog, + Battery, Button, + Camera, HatButton, - Analog, Motion, - Battery, }; namespace std { @@ -115,10 +116,17 @@ public: // Sets polling mode to a controller virtual Common::Input::PollingError SetPollingMode( [[maybe_unused]] const PadIdentifier& identifier, - [[maybe_unused]] const Common::Input::PollingMode vibration) { + [[maybe_unused]] const Common::Input::PollingMode polling_mode) { return Common::Input::PollingError::NotSupported; } + // Sets camera format to a controller + virtual Common::Input::CameraError SetCameraFormat( + [[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] Common::Input::CameraFormat camera_format) { + return Common::Input::CameraError::NotSupported; + } + // Returns the engine name [[nodiscard]] const std::string& GetEngineName() const; @@ -174,6 +182,7 @@ public: f32 GetAxis(const PadIdentifier& identifier, int axis) const; Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; + Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const; int SetCallback(InputIdentifier input_identifier); void SetMappingCallback(MappingCallback callback); @@ -185,6 +194,7 @@ protected: void SetAxis(const PadIdentifier& identifier, int axis, f32 value); void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); + void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value); virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const { return "Unknown"; @@ -197,6 +207,7 @@ private: std::unordered_map<int, float> axes; std::unordered_map<int, BasicMotion> motions; Common::Input::BatteryLevel battery{}; + Common::Input::CameraStatus camera{}; }; void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value); @@ -205,6 +216,8 @@ private: void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value); void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, const BasicMotion& value); + void TriggerOnCameraChange(const PadIdentifier& identifier, + const Common::Input::CameraStatus& value); bool IsInputIdentifierEqual(const InputIdentifier& input_identifier, const PadIdentifier& identifier, EngineInputType type, diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 49ccb4422..133422d5c 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -664,6 +664,47 @@ private: InputEngine* input_engine; }; +class InputFromCamera final : public Common::Input::InputDevice { +public: + explicit InputFromCamera(PadIdentifier identifier_, InputEngine* input_engine_) + : identifier(identifier_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Camera, + .index = 0, + .callback = engine_callback, + }; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromCamera() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::CameraStatus GetStatus() const { + return input_engine->GetCamera(identifier); + } + + void ForceUpdate() override { + OnChange(); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::IrSensor, + .camera_status = GetStatus(), + }; + + TriggerOnChange(status); + } + +private: + const PadIdentifier identifier; + int callback_key; + InputEngine* input_engine; +}; + class OutputFromIdentifier final : public Common::Input::OutputDevice { public: explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) @@ -682,6 +723,10 @@ public: return input_engine->SetPollingMode(identifier, polling_mode); } + Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override { + return input_engine->SetCameraFormat(identifier, camera_format); + } + private: const PadIdentifier identifier; InputEngine* input_engine; @@ -920,6 +965,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice( properties_y, properties_z, input_engine.get()); } +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateCameraDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + input_engine->PreSetController(identifier); + return std::make_unique<InputFromCamera>(identifier, input_engine.get()); +} + InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_) : input_engine(std::move(input_engine_)) {} @@ -928,6 +985,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create( if (params.Has("battery")) { return CreateBatteryDevice(params); } + if (params.Has("camera")) { + return CreateCameraDevice(params); + } if (params.Has("button") && params.Has("axis")) { return CreateTriggerDevice(params); } diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h index 6ebe0dbf5..4410a8415 100644 --- a/src/input_common/input_poller.h +++ b/src/input_common/input_poller.h @@ -211,6 +211,17 @@ private: */ std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params); + /** + * Creates a camera device from the parameters given. + * @param params contains parameters for creating the device: + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> CreateCameraDevice( + const Common::ParamPackage& params); + std::shared_ptr<InputEngine> input_engine; }; } // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 21834fb6b..ca1cb9542 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -5,6 +5,7 @@ #include <memory> #include "common/input.h" #include "common/param_package.h" +#include "input_common/drivers/camera.h" #include "input_common/drivers/gc_adapter.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" @@ -78,6 +79,15 @@ struct InputSubsystem::Impl { Common::Input::RegisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName(), tas_output_factory); + camera = std::make_shared<Camera>("camera"); + camera->SetMappingCallback(mapping_callback); + camera_input_factory = std::make_shared<InputFactory>(camera); + camera_output_factory = std::make_shared<OutputFactory>(camera); + Common::Input::RegisterFactory<Common::Input::InputDevice>(camera->GetEngineName(), + camera_input_factory); + Common::Input::RegisterFactory<Common::Input::OutputDevice>(camera->GetEngineName(), + camera_output_factory); + #ifdef HAVE_SDL2 sdl = std::make_shared<SDLDriver>("sdl"); sdl->SetMappingCallback(mapping_callback); @@ -317,6 +327,7 @@ struct InputSubsystem::Impl { std::shared_ptr<TouchScreen> touch_screen; std::shared_ptr<TasInput::Tas> tas_input; std::shared_ptr<CemuhookUDP::UDPClient> udp_client; + std::shared_ptr<Camera> camera; std::shared_ptr<InputFactory> keyboard_factory; std::shared_ptr<InputFactory> mouse_factory; @@ -324,12 +335,14 @@ struct InputSubsystem::Impl { std::shared_ptr<InputFactory> touch_screen_factory; std::shared_ptr<InputFactory> udp_client_input_factory; std::shared_ptr<InputFactory> tas_input_factory; + std::shared_ptr<InputFactory> camera_input_factory; std::shared_ptr<OutputFactory> keyboard_output_factory; std::shared_ptr<OutputFactory> mouse_output_factory; std::shared_ptr<OutputFactory> gcadapter_output_factory; std::shared_ptr<OutputFactory> udp_client_output_factory; std::shared_ptr<OutputFactory> tas_output_factory; + std::shared_ptr<OutputFactory> camera_output_factory; #ifdef HAVE_SDL2 std::shared_ptr<SDLDriver> sdl; @@ -382,6 +395,14 @@ const TasInput::Tas* InputSubsystem::GetTas() const { return impl->tas_input.get(); } +Camera* InputSubsystem::GetCamera() { + return impl->camera.get(); +} + +const Camera* InputSubsystem::GetCamera() const { + return impl->camera.get(); +} + std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { return impl->GetInputDevices(); } diff --git a/src/input_common/main.h b/src/input_common/main.h index 147c310c4..b756bb5c6 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -30,6 +30,7 @@ enum Values : int; } namespace InputCommon { +class Camera; class Keyboard; class Mouse; class TouchScreen; @@ -92,9 +93,15 @@ public: /// Retrieves the underlying tas input device. [[nodiscard]] TasInput::Tas* GetTas(); - /// Retrieves the underlying tas input device. + /// Retrieves the underlying tas input device. [[nodiscard]] const TasInput::Tas* GetTas() const; + /// Retrieves the underlying camera input device. + [[nodiscard]] Camera* GetCamera(); + + /// Retrieves the underlying camera input device. + [[nodiscard]] const Camera* GetCamera() const; + /** * Returns all available input devices that this Factory can create a new device with. * Each returned ParamPackage should have a `display` field used for display, a `engine` field diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 1ec8392e1..4a1d96322 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -87,8 +87,12 @@ u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) { } std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) { - return static_cast<std::size_t>(framebuffer.stride) * - static_cast<std::size_t>(framebuffer.height) * GetBytesPerPixel(framebuffer); + // TODO(Rodrigo): Read this from HLE + constexpr u32 block_height_log2 = 4; + const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer); + const u64 size_bytes{Tegra::Texture::CalculateSize( + true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)}; + return size_bytes; } VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { @@ -169,9 +173,8 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, // TODO(Rodrigo): Read this from HLE constexpr u32 block_height_log2 = 4; const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer); - const u64 size_bytes{Tegra::Texture::CalculateSize(true, bytes_per_pixel, - framebuffer.stride, framebuffer.height, - 1, block_height_log2, 0)}; + const u64 size_bytes{GetSizeInBytes(framebuffer)}; + Tegra::Texture::UnswizzleTexture( mapped_span.subspan(image_offset, size_bytes), std::span(host_ptr, size_bytes), bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index a0c26a72a..fa8efd22e 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -38,7 +38,7 @@ VkPresentModeKHR ChooseSwapPresentMode(vk::Span<VkPresentModeKHR> modes) { if (found_mailbox != modes.end()) { return VK_PRESENT_MODE_MAILBOX_KHR; } - if (Settings::values.disable_fps_limit.GetValue()) { + if (!Settings::values.use_speed_limit.GetValue()) { // FIFO present mode locks the framerate to the monitor's refresh rate, // Find an alternative to surpass this limitation if FPS is unlocked. const auto found_imm = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_IMMEDIATE_KHR); @@ -205,7 +205,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, u3 extent = swapchain_ci.imageExtent; current_srgb = srgb; - current_fps_unlocked = Settings::values.disable_fps_limit.GetValue(); + current_fps_unlocked = !Settings::values.use_speed_limit.GetValue(); images = swapchain.GetImages(); image_count = static_cast<u32>(images.size()); @@ -259,7 +259,7 @@ void Swapchain::Destroy() { } bool Swapchain::HasFpsUnlockChanged() const { - return current_fps_unlocked != Settings::values.disable_fps_limit.GetValue(); + return current_fps_unlocked != !Settings::values.use_speed_limit.GetValue(); } bool Swapchain::NeedsPresentModeUpdate() const { diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 534e55355..57e0e7025 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -41,6 +41,9 @@ add_executable(yuzu configuration/configure_audio.cpp configuration/configure_audio.h configuration/configure_audio.ui + configuration/configure_camera.cpp + configuration/configure_camera.h + configuration/configure_camera.ui configuration/configure_cpu.cpp configuration/configure_cpu.h configuration/configure_cpu.ui @@ -254,7 +257,7 @@ endif() create_target_directory_groups(yuzu) target_link_libraries(yuzu PRIVATE common core input_common video_core) -target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets) +target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets Qt::Multimedia) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp index 790edbb2a..89bd482e0 100644 --- a/src/yuzu/applets/qt_web_browser.cpp +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #ifdef YUZU_USE_QT_WEB_ENGINE +#include <bit> + #include <QApplication> #include <QKeyEvent> @@ -211,8 +213,10 @@ template <Core::HID::NpadButton... T> void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { const auto f = [this](Core::HID::NpadButton button) { if (input_interpreter->IsButtonPressedOnce(button)) { + const auto button_index = std::countr_zero(static_cast<u64>(button)); + page()->runJavaScript( - QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)), + QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(button_index), [this, button](const QVariant& variant) { if (variant.toBool()) { switch (button) { @@ -236,7 +240,7 @@ void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { page()->runJavaScript( QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }") - .arg(static_cast<u8>(button))); + .arg(button_index)); } }; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 01acda22b..0ee3820a2 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -5,6 +5,8 @@ #include <glad/glad.h> #include <QApplication> +#include <QCameraImageCapture> +#include <QCameraInfo> #include <QHBoxLayout> #include <QMessageBox> #include <QPainter> @@ -31,6 +33,7 @@ #include "core/core.h" #include "core/cpu_manager.h" #include "core/frontend/framebuffer_layout.h" +#include "input_common/drivers/camera.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" #include "input_common/drivers/tas_input.h" @@ -801,6 +804,85 @@ void GRenderWindow::TouchEndEvent() { input_subsystem->GetTouchScreen()->ReleaseAllTouch(); } +void GRenderWindow::InitializeCamera() { + if (!Settings::values.enable_ir_sensor) { + return; + } + + bool camera_found = false; + const QList<QCameraInfo> cameras = QCameraInfo::availableCameras(); + for (const QCameraInfo& cameraInfo : cameras) { + if (Settings::values.ir_sensor_device.GetValue() == cameraInfo.deviceName().toStdString() || + Settings::values.ir_sensor_device.GetValue() == "Auto") { + camera = std::make_unique<QCamera>(cameraInfo); + camera_found = true; + break; + } + } + + if (!camera_found) { + return; + } + + camera_capture = std::make_unique<QCameraImageCapture>(camera.get()); + connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, + &GRenderWindow::OnCameraCapture); + camera->unload(); + camera->setCaptureMode(QCamera::CaptureViewfinder); + camera->load(); + camera->start(); + + pending_camera_snapshots = 0; + is_virtual_camera = false; + + camera_timer = std::make_unique<QTimer>(); + connect(camera_timer.get(), &QTimer::timeout, [this] { RequestCameraCapture(); }); + // This timer should be dependent of camera resolution 5ms for every 100 pixels + camera_timer->start(100); +} + +void GRenderWindow::FinalizeCamera() { + if (camera_timer) { + camera_timer->stop(); + } + if (camera) { + camera->unload(); + } +} + +void GRenderWindow::RequestCameraCapture() { + if (!Settings::values.enable_ir_sensor) { + return; + } + + // If the camera doesn't capture, test for virtual cameras + if (pending_camera_snapshots > 5) { + is_virtual_camera = true; + } + // Virtual cameras like obs need to reset the camera every capture + if (is_virtual_camera) { + camera->stop(); + camera->start(); + } + + pending_camera_snapshots++; + camera_capture->capture(); +} + +void GRenderWindow::OnCameraCapture(int requestId, const QImage& img) { + constexpr std::size_t camera_width = 320; + constexpr std::size_t camera_height = 240; + const auto converted = + img.scaled(camera_width, camera_height, Qt::AspectRatioMode::IgnoreAspectRatio, + Qt::TransformationMode::SmoothTransformation) + .mirrored(false, true); + std::vector<u32> camera_data{}; + camera_data.resize(camera_width * camera_height); + std::memcpy(camera_data.data(), converted.bits(), camera_width * camera_height * sizeof(u32)); + input_subsystem->GetCamera()->SetCameraData(camera_width, camera_height, camera_data); + pending_camera_snapshots = 0; +} + bool GRenderWindow::event(QEvent* event) { if (event->type() == QEvent::TouchBegin) { TouchBeginEvent(static_cast<QTouchEvent*>(event)); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 81fe52c0e..b4781e697 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -20,6 +20,8 @@ class GRenderWindow; class GMainWindow; +class QCamera; +class QCameraImageCapture; class QKeyEvent; namespace Core { @@ -164,6 +166,9 @@ public: void mouseReleaseEvent(QMouseEvent* event) override; void wheelEvent(QWheelEvent* event) override; + void InitializeCamera(); + void FinalizeCamera(); + bool event(QEvent* event) override; void focusOutEvent(QFocusEvent* event) override; @@ -207,6 +212,9 @@ private: void TouchUpdateEvent(const QTouchEvent* event); void TouchEndEvent(); + void RequestCameraCapture(); + void OnCameraCapture(int requestId, const QImage& img); + void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; bool InitializeOpenGL(); @@ -232,6 +240,12 @@ private: bool first_frame = false; InputCommon::TasInput::TasState last_tas_state; + bool is_virtual_camera; + int pending_camera_snapshots; + std::unique_ptr<QCamera> camera; + std::unique_ptr<QCameraImageCapture> camera_capture; + std::unique_ptr<QTimer> camera_timer; + Core::System& system; protected: diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index ca10e9f23..c841843f0 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -143,8 +143,8 @@ void Config::ReadBasicSetting(Settings::Setting<std::string>& setting) { } } -template <typename Type> -void Config::ReadBasicSetting(Settings::Setting<Type>& setting) { +template <typename Type, bool ranged> +void Config::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const Type default_value = setting.GetDefault(); if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) { @@ -164,16 +164,16 @@ void Config::WriteBasicSetting(const Settings::Setting<std::string>& setting) { qt_config->setValue(name, QString::fromStdString(value)); } -template <typename Type> -void Config::WriteBasicSetting(const Settings::Setting<Type>& setting) { +template <typename Type, bool ranged> +void Config::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const Type value = setting.GetValue(); qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); qt_config->setValue(name, value); } -template <typename Type> -void Config::WriteGlobalSetting(const Settings::SwitchableSetting<Type>& setting) { +template <typename Type, bool ranged> +void Config::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const Type& value = setting.GetValue(global); if (!global) { @@ -368,12 +368,18 @@ void Config::ReadHidbusValues() { } } +void Config::ReadIrCameraValues() { + ReadBasicSetting(Settings::values.enable_ir_sensor); + ReadBasicSetting(Settings::values.ir_sensor_device); +} + void Config::ReadAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); if (global) { - ReadBasicSetting(Settings::values.audio_device_id); ReadBasicSetting(Settings::values.sink_id); + ReadBasicSetting(Settings::values.audio_output_device_id); + ReadBasicSetting(Settings::values.audio_input_device_id); } ReadGlobalSetting(Settings::values.volume); @@ -392,6 +398,7 @@ void Config::ReadControlValues() { ReadTouchscreenValues(); ReadMotionTouchValues(); ReadHidbusValues(); + ReadIrCameraValues(); #ifdef _WIN32 ReadBasicSetting(Settings::values.enable_raw_input); @@ -668,7 +675,6 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.max_anisotropy); ReadGlobalSetting(Settings::values.use_speed_limit); ReadGlobalSetting(Settings::values.speed_limit); - ReadGlobalSetting(Settings::values.fps_cap); ReadGlobalSetting(Settings::values.use_disk_shader_cache); ReadGlobalSetting(Settings::values.gpu_accuracy); ReadGlobalSetting(Settings::values.use_asynchronous_gpu_emulation); @@ -998,6 +1004,11 @@ void Config::SaveHidbusValues() { QString::fromStdString(default_param)); } +void Config::SaveIrCameraValues() { + WriteBasicSetting(Settings::values.enable_ir_sensor); + WriteBasicSetting(Settings::values.ir_sensor_device); +} + void Config::SaveValues() { if (global) { SaveControlValues(); @@ -1021,7 +1032,8 @@ void Config::SaveAudioValues() { if (global) { WriteBasicSetting(Settings::values.sink_id); - WriteBasicSetting(Settings::values.audio_device_id); + WriteBasicSetting(Settings::values.audio_output_device_id); + WriteBasicSetting(Settings::values.audio_input_device_id); } WriteGlobalSetting(Settings::values.volume); @@ -1039,6 +1051,7 @@ void Config::SaveControlValues() { SaveTouchscreenValues(); SaveMotionTouchValues(); SaveHidbusValues(); + SaveIrCameraValues(); WriteGlobalSetting(Settings::values.use_docked_mode); WriteGlobalSetting(Settings::values.vibration_enabled); @@ -1230,7 +1243,6 @@ void Config::SaveRendererValues() { WriteGlobalSetting(Settings::values.max_anisotropy); WriteGlobalSetting(Settings::values.use_speed_limit); WriteGlobalSetting(Settings::values.speed_limit); - WriteGlobalSetting(Settings::values.fps_cap); WriteGlobalSetting(Settings::values.use_disk_shader_cache); WriteSetting(QString::fromStdString(Settings::values.gpu_accuracy.GetLabel()), static_cast<u32>(Settings::values.gpu_accuracy.GetValue(global)), @@ -1413,8 +1425,8 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) return result; } -template <typename Type> -void Config::ReadGlobalSetting(Settings::SwitchableSetting<Type>& setting) { +template <typename Type, bool ranged> +void Config::ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting) { QString name = QString::fromStdString(setting.GetLabel()); const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); setting.SetGlobal(use_global); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 9ca878d23..a71eabe8e 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -68,6 +68,7 @@ private: void ReadTouchscreenValues(); void ReadMotionTouchValues(); void ReadHidbusValues(); + void ReadIrCameraValues(); // Read functions bases off the respective config section names. void ReadAudioValues(); @@ -96,6 +97,7 @@ private: void SaveTouchscreenValues(); void SaveMotionTouchValues(); void SaveHidbusValues(); + void SaveIrCameraValues(); // Save functions based off the respective config section names. void SaveAudioValues(); @@ -159,8 +161,8 @@ private: * * @param The setting */ - template <typename Type> - void ReadGlobalSetting(Settings::SwitchableSetting<Type>& setting); + template <typename Type, bool ranged> + void ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting); /** * Sets a value to the qt_config using the setting's label and default value. If the config is a @@ -168,8 +170,8 @@ private: * * @param The setting */ - template <typename Type> - void WriteGlobalSetting(const Settings::SwitchableSetting<Type>& setting); + template <typename Type, bool ranged> + void WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting); /** * Reads a value from the qt_config using the setting's label and default value and applies the @@ -177,15 +179,15 @@ private: * * @param The setting */ - template <typename Type> - void ReadBasicSetting(Settings::Setting<Type>& setting); + template <typename Type, bool ranged> + void ReadBasicSetting(Settings::Setting<Type, ranged>& setting); /** Sets a value from the setting in the qt_config using the setting's label and default value. * * @param The setting */ - template <typename Type> - void WriteBasicSetting(const Settings::Setting<Type>& setting); + template <typename Type, bool ranged> + void WriteBasicSetting(const Settings::Setting<Type, ranged>& setting); ConfigType type; std::unique_ptr<QSettings> qt_config; diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h index 77802a367..56800b6ff 100644 --- a/src/yuzu/configuration/configuration_shared.h +++ b/src/yuzu/configuration/configuration_shared.h @@ -27,8 +27,9 @@ enum class CheckState { // ApplyPerGameSetting, given a Settings::Setting and a Qt UI element, properly applies a Setting void ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting, const QCheckBox* checkbox, const CheckState& tracker); -template <typename Type> -void ApplyPerGameSetting(Settings::SwitchableSetting<Type>* setting, const QComboBox* combobox) { +template <typename Type, bool ranged> +void ApplyPerGameSetting(Settings::SwitchableSetting<Type, ranged>* setting, + const QComboBox* combobox) { if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) { setting->SetValue(static_cast<Type>(combobox->currentIndex())); } else if (!Settings::IsConfiguringGlobal()) { @@ -45,8 +46,9 @@ void ApplyPerGameSetting(Settings::SwitchableSetting<Type>* setting, const QComb // Sets a Qt UI element given a Settings::Setting void SetPerGameSetting(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>* setting); -template <typename Type> -void SetPerGameSetting(QComboBox* combobox, const Settings::SwitchableSetting<Type>* setting) { +template <typename Type, bool ranged> +void SetPerGameSetting(QComboBox* combobox, + const Settings::SwitchableSetting<Type, ranged>* setting) { combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX : static_cast<int>(setting->GetValue()) + ConfigurationShared::USE_GLOBAL_OFFSET); diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index 512bdfc22..19b8b15ef 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -3,8 +3,8 @@ #include <memory> -#include "audio_core/sink.h" -#include "audio_core/sink_details.h" +#include "audio_core/sink/sink.h" +#include "audio_core/sink/sink_details.h" #include "common/settings.h" #include "core/core.h" #include "ui_configure_audio.h" @@ -15,11 +15,11 @@ ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} { ui->setupUi(this); - InitializeAudioOutputSinkComboBox(); + InitializeAudioSinkComboBox(); connect(ui->volume_slider, &QSlider::valueChanged, this, &ConfigureAudio::SetVolumeIndicatorText); - connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, + connect(ui->sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, &ConfigureAudio::UpdateAudioDevices); ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); @@ -30,8 +30,9 @@ ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent) SetConfiguration(); const bool is_powered_on = system_.IsPoweredOn(); - ui->output_sink_combo_box->setEnabled(!is_powered_on); - ui->audio_device_combo_box->setEnabled(!is_powered_on); + ui->sink_combo_box->setEnabled(!is_powered_on); + ui->output_combo_box->setEnabled(!is_powered_on); + ui->input_combo_box->setEnabled(!is_powered_on); } ConfigureAudio::~ConfigureAudio() = default; @@ -40,9 +41,9 @@ void ConfigureAudio::SetConfiguration() { SetOutputSinkFromSinkID(); // The device list cannot be pre-populated (nor listed) until the output sink is known. - UpdateAudioDevices(ui->output_sink_combo_box->currentIndex()); + UpdateAudioDevices(ui->sink_combo_box->currentIndex()); - SetAudioDeviceFromDeviceID(); + SetAudioDevicesFromDeviceID(); const auto volume_value = static_cast<int>(Settings::values.volume.GetValue()); ui->volume_slider->setValue(volume_value); @@ -62,32 +63,45 @@ void ConfigureAudio::SetConfiguration() { } void ConfigureAudio::SetOutputSinkFromSinkID() { - [[maybe_unused]] const QSignalBlocker blocker(ui->output_sink_combo_box); + [[maybe_unused]] const QSignalBlocker blocker(ui->sink_combo_box); int new_sink_index = 0; const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue()); - for (int index = 0; index < ui->output_sink_combo_box->count(); index++) { - if (ui->output_sink_combo_box->itemText(index) == sink_id) { + for (int index = 0; index < ui->sink_combo_box->count(); index++) { + if (ui->sink_combo_box->itemText(index) == sink_id) { new_sink_index = index; break; } } - ui->output_sink_combo_box->setCurrentIndex(new_sink_index); + ui->sink_combo_box->setCurrentIndex(new_sink_index); } -void ConfigureAudio::SetAudioDeviceFromDeviceID() { +void ConfigureAudio::SetAudioDevicesFromDeviceID() { int new_device_index = -1; - const QString device_id = QString::fromStdString(Settings::values.audio_device_id.GetValue()); - for (int index = 0; index < ui->audio_device_combo_box->count(); index++) { - if (ui->audio_device_combo_box->itemText(index) == device_id) { + const QString output_device_id = + QString::fromStdString(Settings::values.audio_output_device_id.GetValue()); + for (int index = 0; index < ui->output_combo_box->count(); index++) { + if (ui->output_combo_box->itemText(index) == output_device_id) { new_device_index = index; break; } } - ui->audio_device_combo_box->setCurrentIndex(new_device_index); + ui->output_combo_box->setCurrentIndex(new_device_index); + + new_device_index = -1; + const QString input_device_id = + QString::fromStdString(Settings::values.audio_input_device_id.GetValue()); + for (int index = 0; index < ui->input_combo_box->count(); index++) { + if (ui->input_combo_box->itemText(index) == input_device_id) { + new_device_index = index; + break; + } + } + + ui->input_combo_box->setCurrentIndex(new_device_index); } void ConfigureAudio::SetVolumeIndicatorText(int percentage) { @@ -95,14 +109,13 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) { } void ConfigureAudio::ApplyConfiguration() { - if (Settings::IsConfiguringGlobal()) { Settings::values.sink_id = - ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) - .toStdString(); - Settings::values.audio_device_id.SetValue( - ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) - .toStdString()); + ui->sink_combo_box->itemText(ui->sink_combo_box->currentIndex()).toStdString(); + Settings::values.audio_output_device_id.SetValue( + ui->output_combo_box->itemText(ui->output_combo_box->currentIndex()).toStdString()); + Settings::values.audio_input_device_id.SetValue( + ui->input_combo_box->itemText(ui->input_combo_box->currentIndex()).toStdString()); // Guard if during game and set to game-specific value if (Settings::values.volume.UsingGlobal()) { @@ -129,21 +142,27 @@ void ConfigureAudio::changeEvent(QEvent* event) { } void ConfigureAudio::UpdateAudioDevices(int sink_index) { - ui->audio_device_combo_box->clear(); - ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); + ui->output_combo_box->clear(); + ui->output_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); + + const std::string sink_id = ui->sink_combo_box->itemText(sink_index).toStdString(); + for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, false)) { + ui->output_combo_box->addItem(QString::fromStdString(device)); + } - const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); - for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { - ui->audio_device_combo_box->addItem(QString::fromStdString(device)); + ui->input_combo_box->clear(); + ui->input_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); + for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, true)) { + ui->input_combo_box->addItem(QString::fromStdString(device)); } } -void ConfigureAudio::InitializeAudioOutputSinkComboBox() { - ui->output_sink_combo_box->clear(); - ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); +void ConfigureAudio::InitializeAudioSinkComboBox() { + ui->sink_combo_box->clear(); + ui->sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); - for (const char* id : AudioCore::GetSinkIDs()) { - ui->output_sink_combo_box->addItem(QString::fromUtf8(id)); + for (const char* id : AudioCore::Sink::GetSinkIDs()) { + ui->sink_combo_box->addItem(QString::fromUtf8(id)); } } @@ -164,8 +183,10 @@ void ConfigureAudio::SetupPerGameUI() { ConfigurationShared::SetHighlight(ui->volume_layout, index == 1); }); - ui->output_sink_combo_box->setVisible(false); - ui->output_sink_label->setVisible(false); - ui->audio_device_combo_box->setVisible(false); - ui->audio_device_label->setVisible(false); + ui->sink_combo_box->setVisible(false); + ui->sink_label->setVisible(false); + ui->output_combo_box->setVisible(false); + ui->output_label->setVisible(false); + ui->input_combo_box->setVisible(false); + ui->input_label->setVisible(false); } diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h index 08c278eeb..0d03aae1d 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -31,14 +31,14 @@ public: private: void changeEvent(QEvent* event) override; - void InitializeAudioOutputSinkComboBox(); + void InitializeAudioSinkComboBox(); void RetranslateUI(); void UpdateAudioDevices(int sink_index); void SetOutputSinkFromSinkID(); - void SetAudioDeviceFromDeviceID(); + void SetAudioDevicesFromDeviceID(); void SetVolumeIndicatorText(int percentage); void SetupPerGameUI(); diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index d1ac8ad02..a5bcee415 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -21,30 +21,44 @@ </property> <layout class="QVBoxLayout"> <item> - <layout class="QHBoxLayout" name="_3"> + <layout class="QHBoxLayout" name="engine_layout"> <item> - <widget class="QLabel" name="output_sink_label"> + <widget class="QLabel" name="sink_label"> <property name="text"> <string>Output Engine:</string> </property> </widget> </item> <item> - <widget class="QComboBox" name="output_sink_combo_box"/> + <widget class="QComboBox" name="sink_combo_box"/> </item> </layout> </item> <item> - <layout class="QHBoxLayout" name="_2"> + <layout class="QHBoxLayout" name="output_layout"> <item> - <widget class="QLabel" name="audio_device_label"> + <widget class="QLabel" name="output_label"> <property name="text"> - <string>Audio Device:</string> + <string>Output Device</string> </property> </widget> </item> <item> - <widget class="QComboBox" name="audio_device_combo_box"/> + <widget class="QComboBox" name="output_combo_box"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="input_layout"> + <item> + <widget class="QLabel" name="input_label"> + <property name="text"> + <string>Input Device</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="input_combo_box"/> </item> </layout> </item> diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp new file mode 100644 index 000000000..73cdcf3f2 --- /dev/null +++ b/src/yuzu/configuration/configure_camera.cpp @@ -0,0 +1,138 @@ +// Text : Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <memory> +#include <QCameraImageCapture> +#include <QCameraInfo> +#include <QStandardItemModel> +#include <QTimer> + +#include "input_common/drivers/camera.h" +#include "input_common/main.h" +#include "ui_configure_camera.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_camera.h" + +ConfigureCamera::ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_) + : QDialog(parent), input_subsystem{input_subsystem_}, + ui(std::make_unique<Ui::ConfigureCamera>()) { + ui->setupUi(this); + + connect(ui->restore_defaults_button, &QPushButton::clicked, this, + &ConfigureCamera::RestoreDefaults); + connect(ui->preview_button, &QPushButton::clicked, this, &ConfigureCamera::PreviewCamera); + + auto blank_image = QImage(320, 240, QImage::Format::Format_RGB32); + blank_image.fill(Qt::black); + DisplayCapturedFrame(0, blank_image); + + LoadConfiguration(); + resize(0, 0); +} + +ConfigureCamera::~ConfigureCamera() = default; + +void ConfigureCamera::PreviewCamera() { + const auto index = ui->ir_sensor_combo_box->currentIndex(); + bool camera_found = false; + const QList<QCameraInfo> cameras = QCameraInfo::availableCameras(); + for (const QCameraInfo& cameraInfo : cameras) { + if (input_devices[index] == cameraInfo.deviceName().toStdString() || + input_devices[index] == "Auto") { + LOG_INFO(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(), + cameraInfo.deviceName().toStdString()); + camera = std::make_unique<QCamera>(cameraInfo); + camera_found = true; + break; + } + } + + // Clear previous frame + auto blank_image = QImage(320, 240, QImage::Format::Format_RGB32); + blank_image.fill(Qt::black); + DisplayCapturedFrame(0, blank_image); + + if (!camera_found) { + return; + } + + camera_capture = std::make_unique<QCameraImageCapture>(camera.get()); + connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, + &ConfigureCamera::DisplayCapturedFrame); + camera->unload(); + camera->setCaptureMode(QCamera::CaptureViewfinder); + camera->load(); + camera->start(); + + pending_snapshots = 0; + is_virtual_camera = false; + + camera_timer = std::make_unique<QTimer>(); + connect(camera_timer.get(), &QTimer::timeout, [this] { + // If the camera doesn't capture, test for virtual cameras + if (pending_snapshots > 5) { + is_virtual_camera = true; + } + // Virtual cameras like obs need to reset the camera every capture + if (is_virtual_camera) { + camera->stop(); + camera->start(); + } + pending_snapshots++; + camera_capture->capture(); + }); + + camera_timer->start(250); +} + +void ConfigureCamera::DisplayCapturedFrame(int requestId, const QImage& img) { + LOG_INFO(Frontend, "ImageCaptured {} {}", img.width(), img.height()); + const auto converted = img.scaled(320, 240, Qt::AspectRatioMode::IgnoreAspectRatio, + Qt::TransformationMode::SmoothTransformation); + ui->preview_box->setPixmap(QPixmap::fromImage(converted)); + pending_snapshots = 0; +} + +void ConfigureCamera::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureCamera::RetranslateUI() { + ui->retranslateUi(this); +} + +void ConfigureCamera::ApplyConfiguration() { + const auto index = ui->ir_sensor_combo_box->currentIndex(); + Settings::values.ir_sensor_device.SetValue(input_devices[index]); +} + +void ConfigureCamera::LoadConfiguration() { + input_devices.clear(); + ui->ir_sensor_combo_box->clear(); + input_devices.push_back("Auto"); + ui->ir_sensor_combo_box->addItem(tr("Auto")); + const auto cameras = QCameraInfo::availableCameras(); + for (const QCameraInfo& cameraInfo : cameras) { + input_devices.push_back(cameraInfo.deviceName().toStdString()); + ui->ir_sensor_combo_box->addItem(cameraInfo.description()); + } + + const auto current_device = Settings::values.ir_sensor_device.GetValue(); + + const auto devices_it = std::find_if( + input_devices.begin(), input_devices.end(), + [current_device](const std::string& device) { return device == current_device; }); + const int device_index = + devices_it != input_devices.end() + ? static_cast<int>(std::distance(input_devices.begin(), devices_it)) + : 0; + ui->ir_sensor_combo_box->setCurrentIndex(device_index); +} + +void ConfigureCamera::RestoreDefaults() { + ui->ir_sensor_combo_box->setCurrentIndex(0); +} diff --git a/src/yuzu/configuration/configure_camera.h b/src/yuzu/configuration/configure_camera.h new file mode 100644 index 000000000..db9833b5c --- /dev/null +++ b/src/yuzu/configuration/configure_camera.h @@ -0,0 +1,54 @@ +// Text : Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <memory> +#include <QDialog> + +class QTimer; +class QCamera; +class QCameraImageCapture; + +namespace InputCommon { +class InputSubsystem; +} // namespace InputCommon + +namespace Ui { +class ConfigureCamera; +} + +class ConfigureCamera : public QDialog { + Q_OBJECT + +public: + explicit ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_); + ~ConfigureCamera() override; + + void ApplyConfiguration(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + /// Load configuration settings. + void LoadConfiguration(); + + /// Restore all buttons to their default values. + void RestoreDefaults(); + + void DisplayCapturedFrame(int requestId, const QImage& img); + + /// Loads and signals the current selected camera to display a frame + void PreviewCamera(); + + InputCommon::InputSubsystem* input_subsystem; + + bool is_virtual_camera; + int pending_snapshots; + std::unique_ptr<QCamera> camera; + std::unique_ptr<QCameraImageCapture> camera_capture; + std::unique_ptr<QTimer> camera_timer; + std::vector<std::string> input_devices; + std::unique_ptr<Ui::ConfigureCamera> ui; +}; diff --git a/src/yuzu/configuration/configure_camera.ui b/src/yuzu/configuration/configure_camera.ui new file mode 100644 index 000000000..976a9b1ec --- /dev/null +++ b/src/yuzu/configuration/configure_camera.ui @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureCamera</class> + <widget class="QDialog" name="ConfigureCamera"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>298</width> + <height>339</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Infrared Camera</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="minimumSize"> + <size> + <width>280</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Select where the image of the emulated camera comes from. It may be a virtual camera or a real camera.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="gridGroupBox"> + <property name="title"> + <string>Camera Image Source:</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Input device:</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QComboBox" name="ir_sensor_combo_box"/> + </item> + <item row="0" column="3"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item><item> + <widget class="QGroupBox" name="previewBox"> + <property name="title"> + <string>Preview</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QLabel" name="preview_box"> + <property name="minimumSize"> + <size> + <width>320</width> + <height>240</height> + </size> + </property> + <property name="toolTip"> + <string>Resolution: 320*240</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="preview_button"> + <property name="text"> + <string>Click to preview</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="restore_defaults_button"> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureCamera</receiver> + <slot>accept()</slot> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureCamera</receiver> + <slot>reject()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 343d2aee1..84808f678 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -44,6 +44,7 @@ void ConfigureDebug::SetConfiguration() { ui->fs_access_log->setEnabled(runtime_lock); ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log.GetValue()); ui->reporting_services->setChecked(Settings::values.reporting_services.GetValue()); + ui->dump_audio_commands->setChecked(Settings::values.dump_audio_commands.GetValue()); ui->quest_flag->setChecked(Settings::values.quest_flag.GetValue()); ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); @@ -83,6 +84,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); Settings::values.reporting_services = ui->reporting_services->isChecked(); + Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); Settings::values.quest_flag = ui->quest_flag->isChecked(); Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 1152fa6c6..4c16274fc 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -235,6 +235,16 @@ </widget> </item> <item row="1" column="0"> + <widget class="QCheckBox" name="dump_audio_commands"> + <property name="text"> + <string>Dump Audio Commands To Console**</string> + </property> + <property name="toolTip"> + <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string> + </property> + </widget> + </item> + <item row="2" column="0"> <widget class="QCheckBox" name="reporting_services"> <property name="text"> <string>Enable Verbose Reporting Services**</string> @@ -325,6 +335,7 @@ <tabstop>disable_loop_safety_checks</tabstop> <tabstop>fs_access_log</tabstop> <tabstop>reporting_services</tabstop> + <tabstop>dump_audio_commands</tabstop> <tabstop>quest_flag</tabstop> <tabstop>enable_cpu_debugging</tabstop> <tabstop>use_debug_asserts</tabstop> diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index a31fabd3f..2a446205b 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -27,9 +27,6 @@ ConfigureGeneral::ConfigureGeneral(const Core::System& system_, QWidget* parent) connect(ui->button_reset_defaults, &QPushButton::clicked, this, &ConfigureGeneral::ResetDefaults); - - ui->fps_cap_label->setVisible(Settings::IsConfiguringGlobal()); - ui->fps_cap_combobox->setVisible(!Settings::IsConfiguringGlobal()); } ConfigureGeneral::~ConfigureGeneral() = default; @@ -52,8 +49,6 @@ void ConfigureGeneral::SetConfiguration() { ui->toggle_speed_limit->setChecked(Settings::values.use_speed_limit.GetValue()); ui->speed_limit->setValue(Settings::values.speed_limit.GetValue()); - ui->fps_cap->setValue(Settings::values.fps_cap.GetValue()); - ui->button_reset_defaults->setEnabled(runtime_lock); if (Settings::IsConfiguringGlobal()) { @@ -61,11 +56,6 @@ void ConfigureGeneral::SetConfiguration() { } else { ui->speed_limit->setEnabled(Settings::values.use_speed_limit.GetValue() && use_speed_limit != ConfigurationShared::CheckState::Global); - - ui->fps_cap_combobox->setCurrentIndex(Settings::values.fps_cap.UsingGlobal() ? 0 : 1); - ui->fps_cap->setEnabled(!Settings::values.fps_cap.UsingGlobal()); - ConfigurationShared::SetHighlight(ui->fps_cap_layout, - !Settings::values.fps_cap.UsingGlobal()); } } @@ -102,8 +92,6 @@ void ConfigureGeneral::ApplyConfiguration() { UISettings::values.mute_when_in_background = ui->toggle_background_mute->isChecked(); UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked(); - Settings::values.fps_cap.SetValue(ui->fps_cap->value()); - // Guard if during game and set to game-specific value if (Settings::values.use_speed_limit.UsingGlobal()) { Settings::values.use_speed_limit.SetValue(ui->toggle_speed_limit->checkState() == @@ -119,13 +107,6 @@ void ConfigureGeneral::ApplyConfiguration() { Qt::Checked); Settings::values.speed_limit.SetValue(ui->speed_limit->value()); } - - if (ui->fps_cap_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { - Settings::values.fps_cap.SetGlobal(true); - } else { - Settings::values.fps_cap.SetGlobal(false); - Settings::values.fps_cap.SetValue(ui->fps_cap->value()); - } } } @@ -171,9 +152,4 @@ void ConfigureGeneral::SetupPerGameUI() { ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked() && (use_speed_limit != ConfigurationShared::CheckState::Global)); }); - - connect(ui->fps_cap_combobox, qOverload<int>(&QComboBox::activated), this, [this](int index) { - ui->fps_cap->setEnabled(index == 1); - ConfigurationShared::SetHighlight(ui->fps_cap_layout, index == 1); - }); } diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index c6ef2ab70..5b90b1109 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -28,87 +28,6 @@ <item> <layout class="QVBoxLayout" name="GeneralVerticalLayout"> <item> - <widget class="QWidget" name="fps_cap_layout" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_4"> - <item> - <widget class="QComboBox" name="fps_cap_combobox"> - <property name="currentText"> - <string>Use global framerate cap</string> - </property> - <property name="currentIndex"> - <number>0</number> - </property> - <item> - <property name="text"> - <string>Use global framerate cap</string> - </property> - </item> - <item> - <property name="text"> - <string>Set framerate cap:</string> - </property> - </item> - </widget> - </item> - <item> - <widget class="QLabel" name="fps_cap_label"> - <property name="toolTip"> - <string>Requires the use of the FPS Limiter Toggle hotkey to take effect.</string> - </property> - <property name="text"> - <string>Framerate Cap</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item> - <widget class="QSpinBox" name="fps_cap"> - <property name="suffix"> - <string>x</string> - </property> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>1000</number> - </property> - <property name="value"> - <number>500</number> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QCheckBox" name="toggle_speed_limit"> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 73d7ba24b..f1b061b13 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -15,6 +15,7 @@ #include "ui_configure_input.h" #include "ui_configure_input_advanced.h" #include "ui_configure_input_player.h" +#include "yuzu/configuration/configure_camera.h" #include "yuzu/configuration/configure_debug_controller.h" #include "yuzu/configuration/configure_input.h" #include "yuzu/configuration/configure_input_advanced.h" @@ -163,6 +164,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, [this, input_subsystem, &hid_core] { CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core); }); + connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, + [this, input_subsystem, &hid_core] { + CallConfigureDialog<ConfigureCamera>(*this, input_subsystem); + }); connect(ui->vibrationButton, &QPushButton::clicked, [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); }); diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 2707025e7..d51774028 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -166,7 +166,7 @@ <item> <widget class="QRadioButton" name="radioUndocked"> <property name="text"> - <string>Undocked</string> + <string>Handheld</string> </property> </widget> </item> diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index f14bdc831..10f841b98 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -89,6 +89,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) [this] { CallMotionTouchConfigDialog(); }); connect(ui->ring_controller_configure, &QPushButton::clicked, this, [this] { CallRingControllerDialog(); }); + connect(ui->camera_configure, &QPushButton::clicked, this, [this] { CallCameraDialog(); }); #ifndef _WIN32 ui->enable_raw_input->setVisible(false); @@ -136,6 +137,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); Settings::values.controller_navigation = ui->controller_navigation->isChecked(); Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked(); + Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked(); } void ConfigureInputAdvanced::LoadConfiguration() { @@ -169,6 +171,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue()); ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue()); + ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue()); UpdateUIEnabled(); } diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h index 644e56dd8..fc1230284 100644 --- a/src/yuzu/configuration/configure_input_advanced.h +++ b/src/yuzu/configuration/configure_input_advanced.h @@ -29,6 +29,7 @@ signals: void CallTouchscreenConfigDialog(); void CallMotionTouchConfigDialog(); void CallRingControllerDialog(); + void CallCameraDialog(); private: void changeEvent(QEvent* event) override; diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index 14403cb10..fac8cf827 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2617,6 +2617,20 @@ </property> </widget> </item> + <item row="5" column="0"> + <widget class="QCheckBox" name="enable_ir_sensor"> + <property name="text"> + <string>Infrared Camera</string> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QPushButton" name="camera_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 69f9cdd9c..2814548eb 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1057,7 +1057,7 @@ void GMainWindow::InitializeHotkeys() { Settings::values.volume.SetValue(static_cast<u8>(new_volume)); }); connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { - Settings::values.disable_fps_limit.SetValue(!Settings::values.disable_fps_limit.GetValue()); + Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); }); connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { Settings::values.mouse_panning = !Settings::values.mouse_panning; @@ -1129,6 +1129,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) { OnPauseGame(); } else if (!emu_thread->IsRunning() && auto_paused && state == Qt::ApplicationActive) { auto_paused = false; + RequestGameResume(); OnStartGame(); } } @@ -1481,9 +1482,6 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig); } - // Disable fps limit toggle when booting a new title - Settings::values.disable_fps_limit.SetValue(false); - // Save configurations UpdateUISettings(); game_list->SaveInterfaceLayout(); @@ -1498,6 +1496,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t if (!LoadROM(filename, program_id, program_index)) return; + system->SetShuttingDown(false); + // Create and start the emulation thread emu_thread = std::make_unique<EmuThread>(*system); emit EmulationStarting(emu_thread.get()); @@ -1540,6 +1540,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t mouse_hide_timer.start(); } + render_window->InitializeCamera(); + std::string title_name; std::string title_version; const auto res = system->GetGameName(title_name); @@ -1588,6 +1590,7 @@ void GMainWindow::ShutdownGame() { AllowOSSleep(); + system->SetShuttingDown(true); system->DetachDebugger(); discord_rpc->Pause(); emu_thread->RequestStop(); @@ -1620,6 +1623,7 @@ void GMainWindow::ShutdownGame() { tas_label->clear(); input_subsystem->GetTas()->Stop(); OnTasStateChanged(); + render_window->FinalizeCamera(); // Enable all controllers system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); @@ -2571,6 +2575,7 @@ void GMainWindow::OnPauseContinueGame() { if (emu_thread->IsRunning()) { OnPauseGame(); } else { + RequestGameResume(); OnStartGame(); } } @@ -2858,6 +2863,12 @@ void GMainWindow::OnConfigure() { mouse_hide_timer.start(); } + // Restart camera config + if (emulation_running) { + render_window->FinalizeCamera(); + render_window->InitializeCamera(); + } + if (!UISettings::values.has_broken_vulkan) { renderer_status_button->setEnabled(!emulation_running); } @@ -3275,7 +3286,7 @@ void GMainWindow::UpdateStatusBar() { } else { emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); } - if (Settings::values.disable_fps_limit) { + if (!Settings::values.use_speed_limit) { game_fps_label->setText( tr("Game: %1 FPS (Unlocked)").arg(results.average_game_fps, 0, 'f', 0)); } else { @@ -3750,6 +3761,21 @@ void GMainWindow::RequestGameExit() { } } +void GMainWindow::RequestGameResume() { + auto& sm{system->ServiceManager()}; + auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); + auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); + + if (applet_oe != nullptr) { + applet_oe->GetMessageQueue()->RequestResume(); + return; + } + + if (applet_ae != nullptr) { + applet_ae->GetMessageQueue()->RequestResume(); + } +} + void GMainWindow::filterBarSetChecked(bool state) { ui->action_Show_Filter_Bar->setChecked(state); emit(OnToggleFilterBar()); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 7df656002..27204f5a2 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -244,6 +244,7 @@ private: bool ConfirmChangeGame(); bool ConfirmForceLockedExit(); void RequestGameExit(); + void RequestGameResume(); void closeEvent(QCloseEvent* event) override; private slots: diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 903e02297..ad7f9d239 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -99,8 +99,8 @@ void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& sett setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); } -template <typename Type> -void Config::ReadSetting(const std::string& group, Settings::Setting<Type>& setting) { +template <typename Type, bool ranged> +void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) { setting = static_cast<Type>(sdl2_config->GetInteger(group, setting.GetLabel(), static_cast<long>(setting.GetDefault()))); } @@ -310,8 +310,6 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.gpu_accuracy); ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation); ReadSetting("Renderer", Settings::values.use_vsync); - ReadSetting("Renderer", Settings::values.fps_cap); - ReadSetting("Renderer", Settings::values.disable_fps_limit); ReadSetting("Renderer", Settings::values.shader_backend); ReadSetting("Renderer", Settings::values.use_asynchronous_shaders); ReadSetting("Renderer", Settings::values.nvdec_emulation); @@ -324,7 +322,7 @@ void Config::ReadValues() { // Audio ReadSetting("Audio", Settings::values.sink_id); - ReadSetting("Audio", Settings::values.audio_device_id); + ReadSetting("Audio", Settings::values.audio_output_device_id); ReadSetting("Audio", Settings::values.volume); // Miscellaneous diff --git a/src/yuzu_cmd/config.h b/src/yuzu_cmd/config.h index ccf77d668..32c03075f 100644 --- a/src/yuzu_cmd/config.h +++ b/src/yuzu_cmd/config.h @@ -33,6 +33,6 @@ private: * @param group The name of the INI group * @param setting The yuzu setting to modify */ - template <typename Type> - void ReadSetting(const std::string& group, Settings::Setting<Type>& setting); + template <typename Type, bool ranged> + void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting); }; diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index a3b8432f5..d9a2a460c 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -330,10 +330,6 @@ bg_red = bg_blue = bg_green = -# Caps the unlocked framerate to a multiple of the title's target FPS. -# 1 - 1000: Target FPS multiple cap. 1000 (default) -fps_cap = - [Audio] # Which audio output engine to use. # auto (default): Auto-select @@ -434,9 +430,6 @@ use_debug_asserts = use_auto_stub = # Enables/Disables the macro JIT compiler disable_macro_jit=false -# Presents guest frames as they become available. Experimental. -# false: Disabled (default), true: Enabled -disable_fps_limit=false # Determines whether to enable the GDB stub and wait for the debugger to attach before running. # false: Disabled (default), true: Enabled use_gdbstub=false diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 000000000..8d3c5919a --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", + "name": "yuzu", + "version": "1.0", + "dependencies": [ + "boost-algorithm", + "boost-asio", + "boost-bind", + "boost-config", + "boost-container", + "boost-context", + "boost-crc", + "boost-functional", + "boost-icl", + "boost-intrusive", + "boost-mpl", + "boost-process", + "boost-range", + "boost-spirit", + "boost-test", + "boost-timer", + "boost-variant", + "fmt", + "lz4", + "nlohmann-json", + "zlib", + "zstd" + ] +} |