Kaynağa Gözat

Merge branch 'master' into mipmap

Feng Chen 3 yıl önce
ebeveyn
işleme
c864cb5772
100 değiştirilmiş dosya ile 980 ekleme ve 1257 silme
  1. 19 6
      .ci/scripts/linux/upload.sh
  2. 1 0
      .ci/scripts/windows/docker.sh
  3. 2 2
      .ci/scripts/windows/upload.ps1
  4. 1 1
      .ci/templates/build-msvc.yml
  5. 1 1
      .ci/yuzu-mainline-step2.yml
  6. 24 3
      .ci/yuzu-patreon-step2.yml
  7. 2 2
      .github/workflows/verify.yml
  8. 1 1
      .gitignore
  9. 6 5
      .reuse/dep5
  10. 12 0
      CMakeLists.txt
  11. 0 0
      dist/qt_themes/colorful/icons/16x16/checked.png
  12. 0 0
      dist/qt_themes/colorful/icons/16x16/failed.png
  13. BIN
      dist/qt_themes/colorful/icons/16x16/info.png
  14. BIN
      dist/qt_themes/colorful/icons/16x16/sync.png
  15. 0 0
      dist/qt_themes/colorful/icons/16x16/view-refresh.png
  16. 0 0
      dist/qt_themes/colorful/icons/48x48/no_avatar.png
  17. BIN
      dist/qt_themes/colorful/icons/48x48/sd_card.png
  18. 0 1
      dist/qt_themes/colorful/icons/index.theme
  19. 6 0
      dist/qt_themes/colorful/style.qrc
  20. 0 10
      dist/qt_themes/colorful_dark/style.qrc
  21. 4 7
      dist/qt_themes/default/default.qrc
  22. BIN
      dist/qt_themes/default/icons/48x48/sd_card.png
  23. 2 1
      dist/qt_themes/default/icons/index.theme
  24. BIN
      dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png
  25. 2 2
      dist/qt_themes/qdarkstyle/icons/index.theme
  26. 1 1
      dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme
  27. 0 1
      externals/CMakeLists.txt
  28. 1 0
      src/audio_core/CMakeLists.txt
  29. 4 14
      src/audio_core/audio_core.cpp
  30. 8 18
      src/audio_core/audio_core.h
  31. 2 2
      src/audio_core/audio_event.h
  32. 1 1
      src/audio_core/audio_in_manager.cpp
  33. 3 2
      src/audio_core/audio_in_manager.h
  34. 1 1
      src/audio_core/audio_manager.h
  35. 1 1
      src/audio_core/audio_out_manager.cpp
  36. 3 3
      src/audio_core/audio_render_manager.cpp
  37. 6 6
      src/audio_core/audio_render_manager.h
  38. 4 0
      src/audio_core/device/audio_buffer.h
  39. 17 4
      src/audio_core/device/audio_buffers.h
  40. 41 23
      src/audio_core/device/device_session.cpp
  41. 28 6
      src/audio_core/device/device_session.h
  42. 4 4
      src/audio_core/in/audio_in.cpp
  43. 4 4
      src/audio_core/in/audio_in.h
  44. 15 7
      src/audio_core/in/audio_in_system.cpp
  45. 6 6
      src/audio_core/in/audio_in_system.h
  46. 4 4
      src/audio_core/out/audio_out.cpp
  47. 4 4
      src/audio_core/out/audio_out.h
  48. 15 6
      src/audio_core/out/audio_out_system.cpp
  49. 5 5
      src/audio_core/out/audio_out_system.h
  50. 1 1
      src/audio_core/renderer/adsp/adsp.cpp
  51. 1 3
      src/audio_core/renderer/adsp/adsp.h
  52. 2 9
      src/audio_core/renderer/adsp/audio_renderer.cpp
  53. 4 4
      src/audio_core/renderer/adsp/audio_renderer.h
  54. 7 6
      src/audio_core/renderer/adsp/command_list_processor.h
  55. 28 6
      src/audio_core/renderer/audio_device.cpp
  56. 7 15
      src/audio_core/renderer/audio_device.h
  57. 9 7
      src/audio_core/renderer/behavior/behavior_info.cpp
  58. 2 2
      src/audio_core/renderer/behavior/behavior_info.h
  59. 1 1
      src/audio_core/renderer/behavior/info_updater.cpp
  60. 1 1
      src/audio_core/renderer/behavior/info_updater.h
  61. 7 5
      src/audio_core/renderer/command/command_buffer.h
  62. 23 23
      src/audio_core/renderer/command/command_generator.h
  63. 5 6
      src/audio_core/renderer/command/effect/compressor.cpp
  64. 3 15
      src/audio_core/renderer/command/mix/mix_ramp.cpp
  65. 4 4
      src/audio_core/renderer/command/mix/mix_ramp.h
  66. 2 2
      src/audio_core/renderer/command/mix/mix_ramp_grouped.h
  67. 4 0
      src/audio_core/renderer/command/sink/device.cpp
  68. 7 7
      src/audio_core/renderer/effect/effect_context.h
  69. 2 2
      src/audio_core/renderer/effect/effect_info_base.h
  70. 2 2
      src/audio_core/renderer/effect/i3dl2.h
  71. 2 2
      src/audio_core/renderer/effect/reverb.h
  72. 2 3
      src/audio_core/renderer/memory/address_info.h
  73. 4 4
      src/audio_core/renderer/nodes/node_states.h
  74. 5 3
      src/audio_core/renderer/performance/performance_manager.h
  75. 5 41
      src/audio_core/renderer/system_manager.cpp
  76. 0 9
      src/audio_core/renderer/system_manager.h
  77. 1 1
      src/audio_core/renderer/upsampler/upsampler_manager.h
  78. 14 12
      src/audio_core/renderer/voice/voice_info.h
  79. 29 354
      src/audio_core/sink/cubeb_sink.cpp
  80. 3 14
      src/audio_core/sink/cubeb_sink.h
  81. 27 22
      src/audio_core/sink/null_sink.h
  82. 32 345
      src/audio_core/sink/sdl2_sink.cpp
  83. 3 14
      src/audio_core/sink/sdl2_sink.h
  84. 2 13
      src/audio_core/sink/sink.h
  85. 3 3
      src/audio_core/sink/sink_details.cpp
  86. 279 0
      src/audio_core/sink/sink_stream.cpp
  87. 100 75
      src/audio_core/sink/sink_stream.h
  88. 5 4
      src/common/CMakeLists.txt
  89. 1 0
      src/common/announce_multiplayer_room.h
  90. 5 0
      src/common/input.h
  91. 1 1
      src/common/parent_of_member.h
  92. 1 0
      src/common/settings.cpp
  93. 2 0
      src/common/settings.h
  94. 4 0
      src/common/thread.h
  95. 5 10
      src/core/CMakeLists.txt
  96. 9 4
      src/core/core.cpp
  97. 37 30
      src/core/core_timing.cpp
  98. 0 6
      src/core/core_timing.h
  99. 1 1
      src/core/file_sys/system_archive/shared_font.cpp
  100. 15 0
      src/core/hid/emulated_controller.cpp

+ 19 - 6
.ci/scripts/linux/upload.sh

@@ -5,21 +5,24 @@
 
 . .ci/scripts/common/pre-upload.sh
 
-APPIMAGE_NAME="yuzu-${GITDATE}-${GITREV}.AppImage"
-REV_NAME="yuzu-linux-${GITDATE}-${GITREV}"
+APPIMAGE_NAME="yuzu-${RELEASE_NAME}-${GITDATE}-${GITREV}.AppImage"
+BASE_NAME="yuzu-linux"
+REV_NAME="${BASE_NAME}-${GITDATE}-${GITREV}"
 ARCHIVE_NAME="${REV_NAME}.tar.xz"
 COMPRESSION_FLAGS="-cJvf"
 
-if [ "${RELEASE_NAME}" = "mainline" ]; then
-    DIR_NAME="${REV_NAME}"
+if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
+    DIR_NAME="${BASE_NAME}-${RELEASE_NAME}"
 else
-    DIR_NAME="${REV_NAME}_${RELEASE_NAME}"
+    DIR_NAME="${REV_NAME}-${RELEASE_NAME}"
 fi
 
 mkdir "$DIR_NAME"
 
 cp build/bin/yuzu-cmd "$DIR_NAME"
-cp build/bin/yuzu "$DIR_NAME"
+if [ "${RELEASE_NAME}" != "early-access" ] && [ "${RELEASE_NAME}" != "mainline" ]; then
+    cp build/bin/yuzu "$DIR_NAME"
+fi
 
 # Build an AppImage
 cd build
@@ -32,6 +35,11 @@ if ! ./appimagetool-x86_64.AppImage --version; then
     export APPIMAGE_EXTRACT_AND_RUN=1
 fi
 
+# Don't let AppImageLauncher ask to integrate EA
+if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
+    echo "X-AppImage-Integrate=false" >> AppDir/org.yuzu_emu.yuzu.desktop
+fi
+
 if [ "${RELEASE_NAME}" = "mainline" ]; then
     # Generate update information if releasing to mainline
     ./appimagetool-x86_64.AppImage -u "gh-releases-zsync|yuzu-emu|yuzu-${RELEASE_NAME}|latest|yuzu-*.AppImage.zsync" AppDir "${APPIMAGE_NAME}"
@@ -46,4 +54,9 @@ if [ -f "build/${APPIMAGE_NAME}.zsync" ]; then
     cp "build/${APPIMAGE_NAME}.zsync" "${ARTIFACTS_DIR}/"
 fi
 
+# Copy the AppImage to the general release directory and remove git revision info
+if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
+    cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage"
+fi
+
 . .ci/scripts/common/post-upload.sh

+ 1 - 0
.ci/scripts/windows/docker.sh

@@ -21,6 +21,7 @@ cmake .. \
     -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
     -DENABLE_QT_TRANSLATION=ON \
     -DUSE_CCACHE=ON \
+    -DYUZU_CRASH_DUMPS=ON \
     -DYUZU_USE_BUNDLED_SDL2=OFF \
     -DYUZU_USE_EXTERNAL_SDL2=OFF \
     -DYUZU_TESTS=OFF \

+ 2 - 2
.ci/scripts/windows/upload.ps1

@@ -65,8 +65,8 @@ if ("$env:GITHUB_ACTIONS" -eq "true") {
     # 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\
+    # Debugging symbols
+    cp .\build\bin\yuzu*.pdb .\artifacts\
 
     # Write out a tag BUILD_TAG to environment for the Upload step
     # We're getting ${{ github.event.number }} as $env:PR_NUMBER"

+ 1 - 1
.ci/templates/build-msvc.yml

@@ -9,7 +9,7 @@ parameters:
 steps:
 - script: choco install vulkan-sdk
   displayName: 'Install vulkan-sdk'
-- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 16 2019" -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_CRASH_DUMPS=ON .. && cd ..
   displayName: 'Configure CMake'
 - task: MSBuild@1
   displayName: 'Build'

+ 1 - 1
.ci/yuzu-mainline-step2.yml

@@ -50,7 +50,7 @@ stages:
     timeoutInMinutes: 120
     displayName: 'msvc'
     pool:
-      vmImage: windows-2019
+      vmImage: windows-2022
     steps:
     - template: ./templates/sync-source.yml
       parameters:

+ 24 - 3
.ci/yuzu-patreon-step2.yml

@@ -11,11 +11,32 @@ stages:
 - stage: build
   displayName: 'build'
   jobs:
-  - job: build
+  - job: linux
     timeoutInMinutes: 120
-    displayName: 'windows-msvc'
+    displayName: 'linux'
     pool:
-      vmImage: windows-2019
+      vmImage: ubuntu-latest
+    strategy:
+      maxParallel: 10
+      matrix:
+        linux:
+          BuildSuffix: 'linux'
+          ScriptFolder: 'linux'
+    steps:
+    - template: ./templates/sync-source.yml
+      parameters:
+        artifactSource: $(parameters.artifactSource)
+        needSubmodules: 'true'
+    - template: ./templates/build-single.yml
+      parameters:
+        artifactSource: 'false'
+        cache: $(parameters.cache)
+        version: $(DisplayVersion)
+  - job: msvc
+    timeoutInMinutes: 120
+    displayName: 'windows'
+    pool:
+      vmImage: windows-2022
     steps:
     - template: ./templates/sync-source.yml
       parameters:

+ 2 - 2
.github/workflows/verify.yml

@@ -71,7 +71,7 @@ jobs:
   build-msvc:
     name: 'test build (windows, msvc)'
     needs: format
-    runs-on: windows-2019
+    runs-on: windows-2022
     steps:
       - name: Set up cache
         uses: actions/cache@v3
@@ -104,7 +104,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 -DGIT_BRANCH=pr-verify
+          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 -DYUZU_CRASH_DUMPS=ON
       - name: Build
         run: cmake --build build
       - name: Cache Summary

+ 1 - 1
.gitignore

@@ -2,7 +2,7 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 # Build directory
-[Bb]uild/
+[Bb]uild*/
 doc-build/
 
 # Generated source files

+ 6 - 5
.reuse/dep5

@@ -6,6 +6,7 @@ Files: dist/english_plurals/*
        dist/icons/controller/*.png
        dist/icons/overlay/*.png
        dist/languages/*
+       dist/qt_themes/*/icons/48x48/sd_card.png
        dist/qt_themes/*/icons/index.theme
        dist/qt_themes/default/style.qss
 Copyright: yuzu Emulator Project
@@ -51,6 +52,8 @@ Files: dist/qt_themes/colorful/icons/16x16/lock.png
        dist/qt_themes/colorful/icons/48x48/chip.png
        dist/qt_themes/colorful/icons/48x48/folder.png
        dist/qt_themes/colorful_dark/icons/16x16/lock.png
+       dist/qt_themes/colorful/icons/16x16/info.png
+       dist/qt_themes/colorful/icons/16x16/sync.png
 Copyright: Icons8
 License: MIT
 Comment: https://github.com/icons8/flat-color-icons
@@ -66,11 +69,9 @@ Files: dist/qt_themes/*/icons/48x48/no_avatar.png
 Copyright: Ionic (http://ionic.io/)
 License: MIT
 
-
-Files: dist/qt_themes/*/icons/48x48/sd_card.png
-       dist/qt_themes/colorful/icons/48x48/star.png
-       dist/qt_themes/default/icons/16x16/checked.png
-       dist/qt_themes/default/icons/16x16/failed.png
+Files: dist/qt_themes/colorful/icons/48x48/star.png
+       dist/qt_themes/colorful/icons/16x16/checked.png
+       dist/qt_themes/colorful/icons/16x16/failed.png
 Copyright: SVG Repo
 License: CC0-1.0
 

+ 12 - 0
CMakeLists.txt

@@ -38,6 +38,8 @@ option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON)
 
 option(YUZU_TESTS "Compile tests" ON)
 
+CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
+
 option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
 
 option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON)
@@ -46,6 +48,9 @@ if (YUZU_USE_BUNDLED_VCPKG)
     if (YUZU_TESTS)
         list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
     endif()
+    if (YUZU_CRASH_DUMPS)
+        list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp")
+    endif()
 
     include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
 elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
@@ -447,6 +452,13 @@ elseif (WIN32)
         # PSAPI is the Process Status API
         set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
     endif()
+
+    if (YUZU_CRASH_DUMPS)
+        find_library(DBGHELP_LIBRARY dbghelp)
+        if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")
+            message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found")
+        endif()
+    endif()
 elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
     set(PLATFORM_LIBRARIES rt)
 endif()

+ 0 - 0
dist/qt_themes/default/icons/16x16/checked.png → dist/qt_themes/colorful/icons/16x16/checked.png


+ 0 - 0
dist/qt_themes/default/icons/16x16/failed.png → dist/qt_themes/colorful/icons/16x16/failed.png


BIN
dist/qt_themes/colorful/icons/16x16/info.png


BIN
dist/qt_themes/colorful/icons/16x16/sync.png


+ 0 - 0
dist/qt_themes/default/icons/16x16/view-refresh.png → dist/qt_themes/colorful/icons/16x16/view-refresh.png


+ 0 - 0
dist/qt_themes/default/icons/48x48/no_avatar.png → dist/qt_themes/colorful/icons/48x48/no_avatar.png


BIN
dist/qt_themes/colorful/icons/48x48/sd_card.png


+ 0 - 1
dist/qt_themes/colorful/icons/index.theme

@@ -1,7 +1,6 @@
 [Icon Theme]
 Name=colorful
 Comment=Colorful theme
-Inherits=default
 Directories=16x16,48x48,256x256
  
 [16x16]

+ 6 - 0
dist/qt_themes/colorful/style.qrc

@@ -6,14 +6,20 @@ SPDX-License-Identifier: GPL-2.0-or-later
 <RCC>
     <qresource prefix="icons/colorful">
         <file alias="index.theme">icons/index.theme</file>
+        <file alias="16x16/checked.png">icons/16x16/checked.png</file>
         <file alias="16x16/connected.png">icons/16x16/connected.png</file>
         <file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
         <file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
+        <file alias="16x16/failed.png">icons/16x16/failed.png</file>
+        <file alias="16x16/info.png">icons/16x16/info.png</file>
         <file alias="16x16/lock.png">icons/16x16/lock.png</file>
+        <file alias="16x16/sync.png">icons/16x16/sync.png</file>
+        <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
         <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
         <file alias="48x48/chip.png">icons/48x48/chip.png</file>
         <file alias="48x48/folder.png">icons/48x48/folder.png</file>
         <file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
+        <file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
         <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
         <file alias="48x48/star.png">icons/48x48/star.png</file>
         <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>

+ 0 - 10
dist/qt_themes/colorful_dark/style.qrc

@@ -5,19 +5,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 <RCC>
     <qresource prefix="icons/colorful_dark">
-        <file alias="16x16/connected.png">../colorful/icons/16x16/connected.png</file>
-        <file alias="16x16/connected_notification.png">../colorful/icons/16x16/connected_notification.png</file>
-        <file alias="16x16/disconnected.png">../colorful/icons/16x16/disconnected.png</file>
         <file alias="index.theme">icons/index.theme</file>
         <file alias="16x16/lock.png">icons/16x16/lock.png</file>
         <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
-        <file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
-        <file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
-        <file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
-        <file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
-        <file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
-        <file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
-        <file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
     </qresource>
 
     <qresource prefix="qss_icons">

+ 4 - 7
dist/qt_themes/default/default.qrc

@@ -5,23 +5,20 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 <RCC>
     <qresource prefix="icons/default">
+        <!-- "colorful" is now the default theme, add new icons there -->
         <file alias="index.theme">icons/index.theme</file>
-        <file alias="16x16/checked.png">icons/16x16/checked.png</file>
-        <file alias="16x16/failed.png">icons/16x16/failed.png</file>
-        <file alias="16x16/lock.png">icons/16x16/lock.png</file>
         <file alias="16x16/connected.png">icons/16x16/connected.png</file>
-        <file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
         <file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
-        <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
+        <file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
+        <file alias="16x16/lock.png">icons/16x16/lock.png</file>
         <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
         <file alias="48x48/chip.png">icons/48x48/chip.png</file>
         <file alias="48x48/folder.png">icons/48x48/folder.png</file>
-        <file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
         <file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
         <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
         <file alias="48x48/star.png">icons/48x48/star.png</file>
-        <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
         <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
+        <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
     </qresource>
     <qresource prefix="default">
         <file>style.qss</file>

BIN
dist/qt_themes/default/icons/48x48/sd_card.png


+ 2 - 1
dist/qt_themes/default/icons/index.theme

@@ -1,6 +1,7 @@
 [Icon Theme]
 Name=default
 Comment=default theme
+Inherits=colorful
 Directories=16x16,48x48,256x256
  
 [16x16]
@@ -10,4 +11,4 @@ Size=16
 Size=48
  
 [256x256]
-Size=256
+Size=256

BIN
dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png


+ 2 - 2
dist/qt_themes/qdarkstyle/icons/index.theme

@@ -1,7 +1,7 @@
 [Icon Theme]
 Name=qdarkstyle
 Comment=dark theme
-Inherits=default
+Inherits=colorful
 Directories=16x16,48x48,256x256
  
 [16x16]
@@ -11,4 +11,4 @@ Size=16
 Size=48
 
 [256x256]
-Size=256
+Size=256

+ 1 - 1
dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme

@@ -1,7 +1,7 @@
 [Icon Theme]
 Name=qdarkstyle_midnight_blue
 Comment=dark theme
-Inherits=default
+Inherits=colorful
 Directories=16x16,48x48,256x256
 
 [16x16]

+ 0 - 1
externals/CMakeLists.txt

@@ -16,7 +16,6 @@ endif()
 
 # Dynarmic
 if (ARCHITECTURE_x86_64)
-    set(DYNARMIC_TESTS OFF)
     set(DYNARMIC_NO_BUNDLED_FMT ON)
     set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE)
     add_subdirectory(dynarmic)

+ 1 - 0
src/audio_core/CMakeLists.txt

@@ -194,6 +194,7 @@ add_library(audio_core STATIC
     sink/sink.h
     sink/sink_details.cpp
     sink/sink_details.h
+    sink/sink_stream.cpp
     sink/sink_stream.h
 )
 

+ 4 - 14
src/audio_core/audio_core.cpp

@@ -47,22 +47,12 @@ 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();
-    }
+void AudioCore::SetNVDECActive(bool active) {
+    nvdec_active = active;
 }
 
-u32 AudioCore::GetStreamQueue() const {
-    return estimated_queue.load();
-}
-
-void AudioCore::SetStreamQueue(u32 size) {
-    estimated_queue.store(size);
+bool AudioCore::IsNVDECActive() const {
+    return nvdec_active;
 }
 
 } // namespace AudioCore

+ 8 - 18
src/audio_core/audio_core.h

@@ -17,7 +17,7 @@ namespace AudioCore {
 
 class AudioManager;
 /**
- * Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP.
+ * Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
  */
 class AudioCore {
 public:
@@ -58,26 +58,16 @@ public:
     AudioRenderer::ADSP::ADSP& GetADSP();
 
     /**
-     * Pause the sink. Called from the core.
+     * Toggle NVDEC state, used to avoid stall in playback.
      *
-     * @param pausing - Is this pause due to an actual pause, or shutdown?
-     *                  Unfortunately, shutdown also pauses streams, which can cause issues.
+     * @param active - Set true if nvdec is active, otherwise false.
      */
-    void PauseSinks(bool pausing) const;
+    void SetNVDECActive(bool active);
 
     /**
-     * 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.
+     * Get NVDEC state.
      */
-    void SetStreamQueue(u32 size);
+    bool IsNVDECActive() const;
 
 private:
     /**
@@ -93,8 +83,8 @@ private:
     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};
+    /// Is NVDec currently active?
+    bool nvdec_active{false};
 };
 
 } // namespace AudioCore

+ 2 - 2
src/audio_core/audio_event.h

@@ -14,7 +14,7 @@ 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.
+ * In a real Switch this is not a separate 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.
  */
@@ -81,7 +81,7 @@ public:
     void ClearEvents();
 
 private:
-    /// Lock, used bythe audio manager
+    /// Lock, used by the 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;

+ 1 - 1
src/audio_core/audio_in_manager.cpp

@@ -82,7 +82,7 @@ u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceN
 
     auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
     if (input_devices.size() > 1) {
-        names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac"));
+        names.emplace_back("Uac");
         return 1;
     }
     return 0;

+ 3 - 2
src/audio_core/audio_in_manager.h

@@ -59,9 +59,10 @@ public:
     /**
      * 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 names     - Output container to write names to.
+     * @param max_count - Maximum number of device 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,

+ 1 - 1
src/audio_core/audio_manager.h

@@ -76,7 +76,7 @@ public:
 
 private:
     /**
-     * Main thread, waiting on a manager signal and calling the registered fucntion.
+     * Main thread, waiting on a manager signal and calling the registered function.
      */
     void ThreadFunc();
 

+ 1 - 1
src/audio_core/audio_out_manager.cpp

@@ -74,7 +74,7 @@ void Manager::BufferReleaseAndRegister() {
 
 u32 Manager::GetAudioOutDeviceNames(
     std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
-    names.push_back({"DeviceOut"});
+    names.emplace_back("DeviceOut");
     return 1;
 }
 

+ 3 - 3
src/audio_core/audio_render_manager.cpp

@@ -25,8 +25,8 @@ SystemManager& Manager::GetSystemManager() {
     return *system_manager;
 }
 
-auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count)
-    -> Result {
+Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params,
+                                  u64& out_count) const {
     if (!CheckValidRevision(params.revision)) {
         return Service::Audio::ERR_INVALID_REVISION;
     }
@@ -54,7 +54,7 @@ void Manager::ReleaseSessionId(const s32 session_id) {
     session_ids[--session_count] = session_id;
 }
 
-u32 Manager::GetSessionCount() {
+u32 Manager::GetSessionCount() const {
     std::scoped_lock l{session_lock};
     return session_count;
 }

+ 6 - 6
src/audio_core/audio_render_manager.h

@@ -46,7 +46,7 @@ public:
      * @param out_count - Output size of the required workbuffer.
      * @return Result code.
      */
-    Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count);
+    Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) const;
 
     /**
      * Get a new session id.
@@ -60,14 +60,14 @@ public:
      *
      * @return The number of active sessions.
      */
-    u32 GetSessionCount();
+    u32 GetSessionCount() const;
 
     /**
      * Add a renderer system to the manager.
-     * The system will be reguarly called to generate commands for the AudioRenderer.
+     * The system will be regularly called to generate commands for the AudioRenderer.
      *
      * @param system - The system to add.
-     * @return True if the system was sucessfully added, otherwise false.
+     * @return True if the system was successfully added, otherwise false.
      */
     bool AddSystem(System& system);
 
@@ -75,7 +75,7 @@ public:
      * Remove a renderer system from the manager.
      *
      * @param system - The system to remove.
-     * @return True if the system was sucessfully removed, otherwise false.
+     * @return True if the system was successfully removed, otherwise false.
      */
     bool RemoveSystem(System& system);
 
@@ -94,7 +94,7 @@ private:
     /// Number of active renderers
     u32 session_count{};
     /// Lock for interacting with the sessions
-    std::mutex session_lock{};
+    mutable std::mutex session_lock{};
     /// Regularly generates commands from the registered systems for the AudioRenderer
     std::unique_ptr<SystemManager> system_manager{};
 };

+ 4 - 0
src/audio_core/device/audio_buffer.h

@@ -8,6 +8,10 @@
 namespace AudioCore {
 
 struct AudioBuffer {
+    /// Timestamp this buffer started playing.
+    u64 start_timestamp;
+    /// Timestamp this buffer should finish playing.
+    u64 end_timestamp;
     /// Timestamp this buffer completed playing.
     s64 played_timestamp;
     /// Game memory address for these samples.

+ 17 - 4
src/audio_core/device/audio_buffers.h

@@ -36,7 +36,7 @@ public:
      *
      * @param buffer - The new buffer.
      */
-    void AppendBuffer(AudioBuffer& buffer) {
+    void AppendBuffer(const AudioBuffer& buffer) {
         std::scoped_lock l{lock};
         buffers[appended_index] = buffer;
         appended_count++;
@@ -58,6 +58,7 @@ public:
             if (index < 0) {
                 index += N;
             }
+
             out_buffers.push_back(buffers[index]);
             registered_count++;
             registered_index = (registered_index + 1) % append_limit;
@@ -87,10 +88,12 @@ public:
     /**
      * Release all registered buffers.
      *
-     * @param timestamp - The released timestamp for this buffer.
+     * @param core_timing - The CoreTiming instance
+     * @param session     - The device session
+     *
      * @return Is the buffer was released.
      */
-    bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) {
+    bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session) {
         std::scoped_lock l{lock};
         bool buffer_released{false};
         while (registered_count > 0) {
@@ -100,7 +103,7 @@ public:
             }
 
             // Check with the backend if this buffer can be released yet.
-            if (!session.IsBufferConsumed(buffers[index].tag)) {
+            if (!session.IsBufferConsumed(buffers[index])) {
                 break;
             }
 
@@ -280,6 +283,16 @@ public:
         return true;
     }
 
+    u64 GetNextTimestamp() const {
+        // Iterate backwards through the buffer queue, and take the most recent buffer's end
+        std::scoped_lock l{lock};
+        auto index{appended_index - 1};
+        if (index < 0) {
+            index += append_limit;
+        }
+        return buffers[index].end_timestamp;
+    }
+
 private:
     /// Buffer lock
     mutable std::recursive_mutex lock{};

+ 41 - 23
src/audio_core/device/device_session.cpp

@@ -7,11 +7,20 @@
 #include "audio_core/device/device_session.h"
 #include "audio_core/sink/sink_stream.h"
 #include "core/core.h"
+#include "core/core_timing.h"
 #include "core/memory.h"
 
 namespace AudioCore {
 
-DeviceSession::DeviceSession(Core::System& system_) : system{system_} {}
+using namespace std::literals;
+constexpr auto INCREMENT_TIME{5ms};
+
+DeviceSession::DeviceSession(Core::System& system_)
+    : system{system_}, thread_event{Core::Timing::CreateEvent(
+                           "AudioOutSampleTick",
+                           [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
+                               return ThreadFunc();
+                           })} {}
 
 DeviceSession::~DeviceSession() {
     Finalize();
@@ -50,25 +59,26 @@ void DeviceSession::Finalize() {
 }
 
 void DeviceSession::Start() {
-    stream->SetPlayedSampleCount(played_sample_count);
-    stream->Start();
+    if (stream) {
+        stream->Start();
+        system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME,
+                                                 thread_event);
+    }
 }
 
 void DeviceSession::Stop() {
     if (stream) {
-        played_sample_count = stream->GetPlayedSampleCount();
         stream->Stop();
+        system.CoreTiming().UnscheduleEvent(thread_event, {});
     }
 }
 
-void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
-    auto& memory{system.Memory()};
-
-    for (size_t i = 0; i < buffers.size(); i++) {
+void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
+    for (const auto& buffer : buffers) {
         Sink::SinkBuffer new_buffer{
-            .frames = buffers[i].size / (channel_count * sizeof(s16)),
+            .frames = buffer.size / (channel_count * sizeof(s16)),
             .frames_played = 0,
-            .tag = buffers[i].tag,
+            .tag = buffer.tag,
             .consumed = false,
         };
 
@@ -76,26 +86,22 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
             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);
+            std::vector<s16> samples(buffer.size / sizeof(s16));
+            system.Memory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size);
             stream->AppendBuffer(new_buffer, samples);
         }
     }
 }
 
-void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const {
+void DeviceSession::ReleaseBuffer(const 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);
+        system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
     }
 }
 
-bool DeviceSession::IsBufferConsumed(u64 tag) const {
-    if (stream) {
-        return stream->IsBufferConsumed(tag);
-    }
-    return true;
+bool DeviceSession::IsBufferConsumed(const AudioBuffer& buffer) const {
+    return played_sample_count >= buffer.end_timestamp;
 }
 
 void DeviceSession::SetVolume(f32 volume) const {
@@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const {
 }
 
 u64 DeviceSession::GetPlayedSampleCount() const {
-    if (stream) {
-        return stream->GetPlayedSampleCount();
+    return played_sample_count;
+}
+
+std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
+    // Add 5ms of samples at a 48K sample rate.
+    played_sample_count += 48'000 * INCREMENT_TIME / 1s;
+    if (type == Sink::StreamType::Out) {
+        system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
+    } else {
+        system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true);
     }
-    return 0;
+    return std::nullopt;
+}
+
+void DeviceSession::SetRingSize(u32 ring_size) {
+    stream->SetRingSize(ring_size);
 }
 
 } // namespace AudioCore

+ 28 - 6
src/audio_core/device/device_session.h

@@ -3,6 +3,9 @@
 
 #pragma once
 
+#include <chrono>
+#include <memory>
+#include <optional>
 #include <span>
 
 #include "audio_core/common/common.h"
@@ -11,9 +14,13 @@
 
 namespace Core {
 class System;
-}
+namespace Timing {
+struct EventType;
+} // namespace Timing
+} // namespace Core
 
 namespace AudioCore {
+
 namespace Sink {
 class SinkStream;
 struct SinkBuffer;
@@ -55,22 +62,23 @@ public:
      *
      * @param buffers - The buffers to play.
      */
-    void AppendBuffers(std::span<AudioBuffer> buffers) const;
+    void AppendBuffers(std::span<const 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;
+    void ReleaseBuffer(const 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.
+     * @param buffer - the buffer to check.
+     *
      * @return true if the buffer has been consumed, otherwise false.
      */
-    bool IsBufferConsumed(u64 tag) const;
+    bool IsBufferConsumed(const AudioBuffer& buffer) const;
 
     /**
      * Start this device session, starting the backend stream.
@@ -96,6 +104,16 @@ public:
      */
     u64 GetPlayedSampleCount() const;
 
+    /*
+     * CoreTiming callback to increment played_sample_count over time.
+     */
+    std::optional<std::chrono::nanoseconds> ThreadFunc();
+
+    /*
+     * Set the size of the ring buffer.
+     */
+    void SetRingSize(u32 ring_size);
+
 private:
     /// System
     Core::System& system;
@@ -118,9 +136,13 @@ private:
     /// 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{};
+    std::atomic<u64> played_sample_count{};
+    /// Event increasing the played sample count every 5ms
+    std::shared_ptr<Core::Timing::EventType> thread_event;
     /// Is this session initialised?
     bool initialized{};
+    /// Buffer queue
+    std::vector<AudioBuffer> buffer_queue{};
 };
 
 } // namespace AudioCore

+ 4 - 4
src/audio_core/in/audio_in.cpp

@@ -72,7 +72,7 @@ Kernel::KReadableEvent& In::GetBufferEvent() {
     return event->GetReadableEvent();
 }
 
-f32 In::GetVolume() {
+f32 In::GetVolume() const {
     std::scoped_lock l{parent_mutex};
     return system.GetVolume();
 }
@@ -82,17 +82,17 @@ void In::SetVolume(f32 volume) {
     system.SetVolume(volume);
 }
 
-bool In::ContainsAudioBuffer(u64 tag) {
+bool In::ContainsAudioBuffer(u64 tag) const {
     std::scoped_lock l{parent_mutex};
     return system.ContainsAudioBuffer(tag);
 }
 
-u32 In::GetBufferCount() {
+u32 In::GetBufferCount() const {
     std::scoped_lock l{parent_mutex};
     return system.GetBufferCount();
 }
 
-u64 In::GetPlayedSampleCount() {
+u64 In::GetPlayedSampleCount() const {
     std::scoped_lock l{parent_mutex};
     return system.GetPlayedSampleCount();
 }

+ 4 - 4
src/audio_core/in/audio_in.h

@@ -102,7 +102,7 @@ public:
      *
      * @return The current volume.
      */
-    f32 GetVolume();
+    f32 GetVolume() const;
 
     /**
      * Set the system volume.
@@ -117,21 +117,21 @@ public:
      * @param tag - The tag to search for.
      * @return True if the buffer is in the system, otherwise false.
      */
-    bool ContainsAudioBuffer(u64 tag);
+    bool ContainsAudioBuffer(u64 tag) const;
 
     /**
      * Get the maximum number of buffers.
      *
      * @return The maximum number of buffers.
      */
-    u32 GetBufferCount();
+    u32 GetBufferCount() const;
 
     /**
      * Get the total played sample count for this audio in.
      *
      * @return The played sample count.
      */
-    u64 GetPlayedSampleCount();
+    u64 GetPlayedSampleCount() const;
 
 private:
     /// The AudioIn::Manager this audio in is registered with

+ 15 - 7
src/audio_core/in/audio_in_system.cpp

@@ -34,16 +34,16 @@ size_t System::GetSessionId() const {
     return session_id;
 }
 
-std::string_view System::GetDefaultDeviceName() {
+std::string_view System::GetDefaultDeviceName() const {
     return "BuiltInHeadset";
 }
 
-std::string_view System::GetDefaultUacDeviceName() {
+std::string_view System::GetDefaultUacDeviceName() const {
     return "Uac";
 }
 
 Result System::IsConfigValid(const std::string_view device_name,
-                             const AudioInParameter& in_params) {
+                             const AudioInParameter& in_params) const {
     if ((device_name.size() > 0) &&
         (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) {
         return Service::Audio::ERR_INVALID_DEVICE_NAME;
@@ -93,6 +93,7 @@ Result System::Start() {
     std::vector<AudioBuffer> buffers_to_flush{};
     buffers.RegisterBuffers(buffers_to_flush);
     session->AppendBuffers(buffers_to_flush);
+    session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
 
     return ResultSuccess;
 }
@@ -112,8 +113,15 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
         return false;
     }
 
-    AudioBuffer new_buffer{
-        .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size};
+    const auto timestamp{buffers.GetNextTimestamp()};
+    const AudioBuffer new_buffer{
+        .start_timestamp = timestamp,
+        .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
+        .played_timestamp = 0,
+        .samples = buffer.samples,
+        .tag = tag,
+        .size = buffer.size,
+    };
 
     buffers.AppendBuffer(new_buffer);
     RegisterBuffers();
@@ -194,11 +202,11 @@ void System::SetVolume(const f32 volume_) {
     session->SetVolume(volume_);
 }
 
-bool System::ContainsAudioBuffer(const u64 tag) {
+bool System::ContainsAudioBuffer(const u64 tag) const {
     return buffers.ContainsBuffer(tag);
 }
 
-u32 System::GetBufferCount() {
+u32 System::GetBufferCount() const {
     return buffers.GetAppendedRegisteredCount();
 }
 

+ 6 - 6
src/audio_core/in/audio_in_system.h

@@ -68,7 +68,7 @@ public:
      *
      * @return The default audio input device name.
      */
-    std::string_view GetDefaultDeviceName();
+    std::string_view GetDefaultDeviceName() const;
 
     /**
      * Get the default USB audio input device name.
@@ -77,7 +77,7 @@ public:
      *
      * @return The default USB audio input device name.
      */
-    std::string_view GetDefaultUacDeviceName();
+    std::string_view GetDefaultUacDeviceName() const;
 
     /**
      * Is the given initialize config valid?
@@ -86,7 +86,7 @@ public:
      * @param in_params   - Input parameters, see AudioInParameter.
      * @return Result code.
      */
-    Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params);
+    Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params) const;
 
     /**
      * Initialize this system.
@@ -208,7 +208,7 @@ public:
     /**
      * Set this system's current volume.
      *
-     * @param The new volume.
+     * @param volume The new volume.
      */
     void SetVolume(f32 volume);
 
@@ -218,14 +218,14 @@ public:
      * @param tag - Unique tag to search for.
      * @return True if the buffer is in the system, otherwise false.
      */
-    bool ContainsAudioBuffer(u64 tag);
+    bool ContainsAudioBuffer(u64 tag) const;
 
     /**
      * Get the maximum number of usable buffers (default 32).
      *
      * @return The number of buffers.
      */
-    u32 GetBufferCount();
+    u32 GetBufferCount() const;
 
     /**
      * Get the total number of samples played by this system.

+ 4 - 4
src/audio_core/out/audio_out.cpp

@@ -72,7 +72,7 @@ Kernel::KReadableEvent& Out::GetBufferEvent() {
     return event->GetReadableEvent();
 }
 
-f32 Out::GetVolume() {
+f32 Out::GetVolume() const {
     std::scoped_lock l{parent_mutex};
     return system.GetVolume();
 }
@@ -82,17 +82,17 @@ void Out::SetVolume(const f32 volume) {
     system.SetVolume(volume);
 }
 
-bool Out::ContainsAudioBuffer(const u64 tag) {
+bool Out::ContainsAudioBuffer(const u64 tag) const {
     std::scoped_lock l{parent_mutex};
     return system.ContainsAudioBuffer(tag);
 }
 
-u32 Out::GetBufferCount() {
+u32 Out::GetBufferCount() const {
     std::scoped_lock l{parent_mutex};
     return system.GetBufferCount();
 }
 
-u64 Out::GetPlayedSampleCount() {
+u64 Out::GetPlayedSampleCount() const {
     std::scoped_lock l{parent_mutex};
     return system.GetPlayedSampleCount();
 }

+ 4 - 4
src/audio_core/out/audio_out.h

@@ -102,7 +102,7 @@ public:
      *
      * @return The current volume.
      */
-    f32 GetVolume();
+    f32 GetVolume() const;
 
     /**
      * Set the system volume.
@@ -117,21 +117,21 @@ public:
      * @param tag - The tag to search for.
      * @return True if the buffer is in the system, otherwise false.
      */
-    bool ContainsAudioBuffer(u64 tag);
+    bool ContainsAudioBuffer(u64 tag) const;
 
     /**
      * Get the maximum number of buffers.
      *
      * @return The maximum number of buffers.
      */
-    u32 GetBufferCount();
+    u32 GetBufferCount() const;
 
     /**
      * Get the total played sample count for this audio out.
      *
      * @return The played sample count.
      */
-    u64 GetPlayedSampleCount();
+    u64 GetPlayedSampleCount() const;
 
 private:
     /// The AudioOut::Manager this audio out is registered with

+ 15 - 6
src/audio_core/out/audio_out_system.cpp

@@ -27,11 +27,12 @@ void System::Finalize() {
     buffer_event->GetWritableEvent().Signal();
 }
 
-std::string_view System::GetDefaultOutputDeviceName() {
+std::string_view System::GetDefaultOutputDeviceName() const {
     return "DeviceOut";
 }
 
-Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) {
+Result System::IsConfigValid(std::string_view device_name,
+                             const AudioOutParameter& in_params) const {
     if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) {
         return Service::Audio::ERR_INVALID_DEVICE_NAME;
     }
@@ -92,6 +93,7 @@ Result System::Start() {
     std::vector<AudioBuffer> buffers_to_flush{};
     buffers.RegisterBuffers(buffers_to_flush);
     session->AppendBuffers(buffers_to_flush);
+    session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
 
     return ResultSuccess;
 }
@@ -111,8 +113,15 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
         return false;
     }
 
-    AudioBuffer new_buffer{
-        .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size};
+    const auto timestamp{buffers.GetNextTimestamp()};
+    const AudioBuffer new_buffer{
+        .start_timestamp = timestamp,
+        .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
+        .played_timestamp = 0,
+        .samples = buffer.samples,
+        .tag = tag,
+        .size = buffer.size,
+    };
 
     buffers.AppendBuffer(new_buffer);
     RegisterBuffers();
@@ -192,11 +201,11 @@ void System::SetVolume(const f32 volume_) {
     session->SetVolume(volume_);
 }
 
-bool System::ContainsAudioBuffer(const u64 tag) {
+bool System::ContainsAudioBuffer(const u64 tag) const {
     return buffers.ContainsBuffer(tag);
 }
 
-u32 System::GetBufferCount() {
+u32 System::GetBufferCount() const {
     return buffers.GetAppendedRegisteredCount();
 }
 

+ 5 - 5
src/audio_core/out/audio_out_system.h

@@ -68,7 +68,7 @@ public:
      *
      * @return The default audio output device name.
      */
-    std::string_view GetDefaultOutputDeviceName();
+    std::string_view GetDefaultOutputDeviceName() const;
 
     /**
      * Is the given initialize config valid?
@@ -77,7 +77,7 @@ public:
      * @param in_params   - Input parameters, see AudioOutParameter.
      * @return Result code.
      */
-    Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params);
+    Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) const;
 
     /**
      * Initialize this system.
@@ -199,7 +199,7 @@ public:
     /**
      * Set this system's current volume.
      *
-     * @param The new volume.
+     * @param volume The new volume.
      */
     void SetVolume(f32 volume);
 
@@ -209,14 +209,14 @@ public:
      * @param tag - Unique tag to search for.
      * @return True if the buffer is in the system, otherwise false.
      */
-    bool ContainsAudioBuffer(u64 tag);
+    bool ContainsAudioBuffer(u64 tag) const;
 
     /**
      * Get the maximum number of usable buffers (default 32).
      *
      * @return The number of buffers.
      */
-    u32 GetBufferCount();
+    u32 GetBufferCount() const;
 
     /**
      * Get the total number of samples played by this system.

+ 1 - 1
src/audio_core/renderer/adsp/adsp.cpp

@@ -50,7 +50,7 @@ u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
     return render_mailbox.GetRemainCommandCount(session_id);
 }
 
-void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) {
+void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
     render_mailbox.SetCommandBuffer(session_id, command_buffer);
 }
 

+ 1 - 3
src/audio_core/renderer/adsp/adsp.h

@@ -63,8 +63,6 @@ public:
 
     /**
      * Stop the ADSP.
-     *
-     * @return True if started or already running, otherwise false.
      */
     void Stop();
 
@@ -133,7 +131,7 @@ public:
      * @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);
+    void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
 
     /**
      * Clear the command buffers (does not clear the time taken or the remaining command count)

+ 2 - 9
src/audio_core/renderer/adsp/audio_renderer.cpp

@@ -51,7 +51,7 @@ CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) {
     return command_buffers[session_id];
 }
 
-void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) {
+void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
     command_buffers[session_id] = buffer;
 }
 
@@ -106,9 +106,6 @@ void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
 
     mailbox = mailbox_;
     thread = std::thread(&AudioRenderer::ThreadFunc, this);
-    for (auto& stream : streams) {
-        stream->Start();
-    }
     running = true;
 }
 
@@ -130,6 +127,7 @@ void AudioRenderer::CreateSinkStreams() {
         std::string name{fmt::format("ADSP_RenderStream-{}", i)};
         streams[i] =
             sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
+        streams[i]->SetRingSize(4);
     }
 }
 
@@ -198,11 +196,6 @@ void AudioRenderer::ThreadFunc() {
                             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 =

+ 4 - 4
src/audio_core/renderer/adsp/audio_renderer.h

@@ -52,7 +52,7 @@ public:
     /**
      * Send a message from the host to the AudioRenderer.
      *
-     * @param message_ - The message to send to the AudioRenderer.
+     * @param message - The message to send to the AudioRenderer.
      */
     void HostSendMessage(RenderMessage message);
 
@@ -66,7 +66,7 @@ public:
     /**
      * Send a message from the AudioRenderer to the host.
      *
-     * @param message_ - The message to send to the host.
+     * @param message - The message to send to the host.
      */
     void ADSPSendMessage(RenderMessage message);
 
@@ -91,7 +91,7 @@ public:
      * @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);
+    void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
 
     /**
      * Get the total render time taken for the last command lists sent.
@@ -163,7 +163,7 @@ public:
     /**
      * Start the AudioRenderer.
      *
-     * @param The mailbox to use for this session.
+     * @param mailbox The mailbox to use for this session.
      */
     void Start(AudioRenderer_Mailbox* mailbox);
 

+ 7 - 6
src/audio_core/renderer/adsp/command_list_processor.h

@@ -33,10 +33,10 @@ 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.
+     * @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);
 
@@ -72,7 +72,8 @@ public:
     /**
      * Process the command list.
      *
-     * @param index - Index of the current command list.
+     * @param session_id - Session ID for the commands being processed.
+     *
      * @return The time taken to process.
      */
     u64 Process(u32 session_id);
@@ -89,7 +90,7 @@ public:
     u8* commands{};
     /// The command buffer size
     u64 commands_buffer_size{};
-    /// The maximum processing time alloted
+    /// The maximum processing time allotted
     u64 max_process_time{};
     /// The number of commands in the buffer
     u32 command_count{};

+ 28 - 6
src/audio_core/renderer/audio_device.cpp

@@ -1,6 +1,9 @@
 // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include <array>
+#include <span>
+
 #include "audio_core/audio_core.h"
 #include "audio_core/common/feature_support.h"
 #include "audio_core/renderer/audio_device.h"
@@ -9,14 +12,33 @@
 
 namespace AudioCore::AudioRenderer {
 
+constexpr std::array usb_device_names{
+    AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
+    AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
+    AudioDevice::AudioDeviceName{"AudioTvOutput"},
+    AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"},
+};
+
+constexpr std::array device_names{
+    AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
+    AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
+    AudioDevice::AudioDeviceName{"AudioTvOutput"},
+};
+
+constexpr std::array output_device_names{
+    AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
+    AudioDevice::AudioDeviceName{"AudioTvOutput"},
+    AudioDevice::AudioDeviceName{"AudioExternalOutput"},
+};
+
 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{};
+                                     const size_t max_count) const {
+    std::span<const AudioDeviceName> names{};
 
     if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
         names = usb_device_names;
@@ -24,7 +46,7 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
         names = device_names;
     }
 
-    u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
+    const 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]);
     }
@@ -32,8 +54,8 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
 }
 
 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()))};
+                                           const size_t max_count) const {
+    const 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]);
@@ -45,7 +67,7 @@ void AudioDevice::SetDeviceVolumes(const f32 volume) {
     output_sink.SetDeviceVolume(volume);
 }
 
-f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) {
+f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
     return output_sink.GetDeviceVolume();
 }
 

+ 7 - 15
src/audio_core/renderer/audio_device.h

@@ -3,7 +3,7 @@
 
 #pragma once
 
-#include <span>
+#include <string_view>
 
 #include "audio_core/audio_render_manager.h"
 
@@ -23,21 +23,13 @@ namespace AudioRenderer {
 class AudioDevice {
 public:
     struct AudioDeviceName {
-        std::array<char, 0x100> name;
+        std::array<char, 0x100> name{};
 
-        AudioDeviceName(const char* name_) {
-            std::strncpy(name.data(), name_, name.size());
+        constexpr AudioDeviceName(std::string_view name_) {
+            name_.copy(name.data(), name.size() - 1);
         }
     };
 
-    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);
 
     /**
@@ -47,7 +39,7 @@ public:
      * @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);
+    u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
 
     /**
      * Get a list of the available output devices.
@@ -57,7 +49,7 @@ public:
      * @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);
+    u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
 
     /**
      * Set the volume of all streams in the backend sink.
@@ -73,7 +65,7 @@ public:
      * @param name - Name of the device to check. Unused.
      * @return Volume of the device.
      */
-    f32 GetDeviceVolume(std::string_view name);
+    f32 GetDeviceVolume(std::string_view name) const;
 
 private:
     /// Backend output sink for the device

+ 9 - 7
src/audio_core/renderer/behavior/behavior_info.cpp

@@ -34,7 +34,7 @@ void BehaviorInfo::ClearError() {
     error_count = 0;
 }
 
-void BehaviorInfo::AppendError(ErrorInfo& error) {
+void BehaviorInfo::AppendError(const ErrorInfo& error) {
     LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
               error.error_code.raw, error.address);
     if (error_count < MaxErrors) {
@@ -42,14 +42,16 @@ void BehaviorInfo::AppendError(ErrorInfo& 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));
+void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const {
+    out_count = std::min(error_count, MaxErrors);
 
-    for (size_t i = 0; i < error_count_; i++) {
-        out_errors[i] = errors[i];
+    for (size_t i = 0; i < MaxErrors; i++) {
+        if (i < out_count) {
+            out_errors[i] = errors[i];
+        } else {
+            out_errors[i] = {};
+        }
     }
-    out_count = error_count_;
 }
 
 void BehaviorInfo::UpdateFlags(const Flags flags_) {

+ 2 - 2
src/audio_core/renderer/behavior/behavior_info.h

@@ -94,7 +94,7 @@ public:
      *
      * @param error - The new error.
      */
-    void AppendError(ErrorInfo& error);
+    void AppendError(const ErrorInfo& error);
 
     /**
      * Copy errors to the given output container.
@@ -102,7 +102,7 @@ public:
      * @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);
+    void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const;
 
     /**
      * Update the behaviour flags.

+ 1 - 1
src/audio_core/renderer/behavior/info_updater.cpp

@@ -485,7 +485,7 @@ Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
     return ResultSuccess;
 }
 
-Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) {
+Result InfoUpdater::UpdateErrorInfo(const BehaviorInfo& behaviour_) {
     auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
     behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);
 

+ 1 - 1
src/audio_core/renderer/behavior/info_updater.h

@@ -130,7 +130,7 @@ public:
      * @param behaviour - Behaviour to update.
      * @return Result code.
      */
-    Result UpdateErrorInfo(BehaviorInfo& behaviour);
+    Result UpdateErrorInfo(const BehaviorInfo& behaviour);
 
     /**
      * Update splitter.

+ 7 - 5
src/audio_core/renderer/command/command_buffer.h

@@ -191,6 +191,7 @@ public:
      * @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 prev_samples - Previous sample buffer. Used for depopping.
      * @param precision    - Number of decimal bits for fixed point operations.
      */
     void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
@@ -208,6 +209,7 @@ public:
      * @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 prev_samples - Previous sample buffer. Used for depopping.
      * @param precision    - Number of decimal bits for fixed point operations.
      */
     void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
@@ -297,11 +299,11 @@ public:
     /**
      * 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.
+     * @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.
+     * @param session_id     - System session id this command is generated from.
+     * @param 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);

+ 23 - 23
src/audio_core/renderer/command/command_generator.h

@@ -197,9 +197,9 @@ public:
     /**
      * 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.
+     * @param buffer_offset - Base mix buffer offset to use.
+     * @param effect_info   - 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);
@@ -207,18 +207,18 @@ public:
     /**
      * 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.
+     * @param buffer_offset - Base mix buffer offset to use.
+     * @param effect_info   - 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.
+     * @param buffer_offset - Base mix buffer offset to use.
+     * @param effect_info   - 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);
@@ -226,10 +226,10 @@ public:
     /**
      * 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.
+     * @param buffer_offset - Base mix buffer offset to use.
+     * @param effect_info   - 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);
@@ -238,21 +238,20 @@ public:
      * 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.
+     * @param buffer_offset - Base mix buffer offset to use.
+     * @param effect_info   - 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.
+     * @param buffer_offset - Base mix buffer offset to use.
+     * @param effect_info   - 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);
+    void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
 
     /**
      * Generate all effect commands for a mix.
@@ -318,8 +317,9 @@ public:
      * 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.
+     * @param node_id         - Node ID of the mix this command is generated for
+     * @param state           - Output state of the generated performance command
+     * @param entry_addresses - Addresses to be written
      */
     void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
                                     const PerformanceEntryAddresses& entry_addresses);

+ 5 - 6
src/audio_core/renderer/command/effect/compressor.cpp

@@ -11,7 +11,7 @@
 
 namespace AudioCore::AudioRenderer {
 
-static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params,
+static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params,
                                          CompressorInfo::State& state) {
     const auto ratio{1.0f / params.compressor_ratio};
     auto makeup_gain{0.0f};
@@ -31,9 +31,9 @@ static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& para
     state.unk_20 = c;
 }
 
-static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params,
+static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params,
                                        CompressorInfo::State& state) {
-    std::memset(&state, 0, sizeof(CompressorInfo::State));
+    state = {};
 
     state.unk_00 = 0;
     state.unk_04 = 1.0f;
@@ -42,7 +42,7 @@ static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params
     SetCompressorEffectParameter(params, state);
 }
 
-static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
+static void ApplyCompressorEffect(const 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) {
@@ -103,8 +103,7 @@ static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
     } 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(),
+                std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(),
                             output_buffers[channel].size_bytes());
             }
         }

+ 3 - 15
src/audio_core/renderer/command/mix/mix_ramp.cpp

@@ -7,17 +7,7 @@
 #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) {
@@ -40,10 +30,8 @@ s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 vo
     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);
+template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
+template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
 
 void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
     const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};

+ 4 - 4
src/audio_core/renderer/command/mix/mix_ramp.h

@@ -61,13 +61,13 @@ struct MixRampCommand : ICommand {
  * @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 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);
+s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
+                 u32 sample_count);
 
 } // namespace AudioCore::AudioRenderer

+ 2 - 2
src/audio_core/renderer/command/mix/mix_ramp_grouped.h

@@ -50,9 +50,9 @@ struct MixRampGroupedCommand : ICommand {
     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
+    /// Previous mix volumes for each mix buffer
     std::array<f32, MaxMixBuffers> prev_volumes;
-    /// Current mix vloumes for each mix buffer
+    /// Current mix volumes for each mix buffer
     std::array<f32, MaxMixBuffers> volumes;
     /// Pointer to the previous sample buffer, used for depop
     CpuAddr previous_samples;

+ 4 - 0
src/audio_core/renderer/command/sink/device.cpp

@@ -46,6 +46,10 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
 
     out_buffer.tag = reinterpret_cast<u64>(samples.data());
     stream->AppendBuffer(out_buffer, samples);
+
+    if (stream->IsPaused()) {
+        stream->Start();
+    }
 }
 
 bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {

+ 7 - 7
src/audio_core/renderer/effect/effect_context.h

@@ -15,15 +15,15 @@ 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
+     * @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 dsp_state_count    - The number of result states
      */
-    void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
+    void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_,
                     std::span<EffectResultState> result_states_cpu_,
-                    std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count);
+                    std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count);
 
     /**
      * Get the EffectInfo for a given index

+ 2 - 2
src/audio_core/renderer/effect/effect_info_base.h

@@ -291,7 +291,7 @@ 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 params      - New parameters to update the info with.
      * @param pool_mapper - Pool for mapping buffers.
      */
     virtual void Update(BehaviorInfo::ErrorInfo& error_info,
@@ -305,7 +305,7 @@ public:
      * 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 params      - New parameters to update the info with.
      * @param pool_mapper - Pool for mapping buffers.
      */
     virtual void Update(BehaviorInfo::ErrorInfo& error_info,

+ 2 - 2
src/audio_core/renderer/effect/i3dl2.h

@@ -99,7 +99,7 @@ public:
             return out_sample;
         }
 
-        Common::FixedPoint<50, 14> Read() {
+        Common::FixedPoint<50, 14> Read() const {
             return *output;
         }
 
@@ -110,7 +110,7 @@ public:
             }
         }
 
-        Common::FixedPoint<50, 14> TapOut(const s32 index) {
+        Common::FixedPoint<50, 14> TapOut(const s32 index) const {
             auto out{input - (index + 1)};
             if (out < buffer.data()) {
                 out += max_delay + 1;

+ 2 - 2
src/audio_core/renderer/effect/reverb.h

@@ -95,7 +95,7 @@ public:
             return out_sample;
         }
 
-        Common::FixedPoint<50, 14> Read() {
+        Common::FixedPoint<50, 14> Read() const {
             return *output;
         }
 
@@ -106,7 +106,7 @@ public:
             }
         }
 
-        Common::FixedPoint<50, 14> TapOut(const s32 index) {
+        Common::FixedPoint<50, 14> TapOut(const s32 index) const {
             auto out{input - (index + 1)};
             if (out < buffer.data()) {
                 out += sample_count;

+ 2 - 3
src/audio_core/renderer/memory/address_info.h

@@ -19,8 +19,8 @@ public:
     /**
      * Setup a new AddressInfo.
      *
-     * @param cpu_address - The CPU address of this region.
-     * @param size        - The size of this region.
+     * @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_;
@@ -42,7 +42,6 @@ public:
      * 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_;

+ 4 - 4
src/audio_core/renderer/nodes/node_states.h

@@ -56,7 +56,7 @@ class NodeStates {
          *
          * @return The current stack position.
          */
-        u32 Count() {
+        u32 Count() const {
             return pos;
         }
 
@@ -83,7 +83,7 @@ class NodeStates {
          *
          * @return The node on the top of the stack.
          */
-        u32 top() {
+        u32 top() const {
             return stack[pos - 1];
         }
 
@@ -112,11 +112,11 @@ public:
     /**
      * Initialize the node states.
      *
-     * @param buffer           - The workbuffer to use. Unused.
+     * @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);
+    void Initialize(std::span<u8> buffer_, u64 node_buffer_size, u32 count);
 
     /**
      * Sort the graph. Only calls DepthFirstSearch.

+ 5 - 3
src/audio_core/renderer/performance/performance_manager.h

@@ -73,7 +73,8 @@ public:
      * Calculate the required size for the performance workbuffer.
      *
      * @param behavior - Check which version is supported.
-     * @param params    - Input parameters.
+     * @param params   - Input parameters.
+     *
      * @return Required workbuffer size.
      */
     static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
@@ -104,7 +105,7 @@ public:
      * @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 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,
@@ -160,7 +161,8 @@ public:
      * 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.
+     *                          to the AudioRenderer with Performance commands to be written.
+     * @param detail_type     - Performance detail type.
      * @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.

+ 5 - 41
src/audio_core/renderer/system_manager.cpp

@@ -15,17 +15,14 @@ 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};
+constexpr std::chrono::nanoseconds RENDER_TIME{5'000'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();
@@ -36,8 +33,8 @@ bool SystemManager::InitializeUnsafe() {
         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);
+            core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME,
+                                                   thread_event);
         }
     }
 
@@ -121,42 +118,9 @@ void SystemManager::ThreadFunc() {
 }
 
 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();
-    }
+    return std::nullopt;
 }
 
 } // namespace AudioCore::AudioRenderer

+ 0 - 9
src/audio_core/renderer/system_manager.h

@@ -73,13 +73,6 @@ private:
      */
     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,
@@ -106,8 +99,6 @@ private:
     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

+ 1 - 1
src/audio_core/renderer/upsampler/upsampler_manager.h

@@ -27,7 +27,7 @@ public:
     /**
      * Free the given upsampler.
      *
-     * @param The upsampler to be freed.
+     * @param info The upsampler to be freed.
      */
     void Free(UpsamplerInfo* info);
 

+ 14 - 12
src/audio_core/renderer/voice/voice_info.h

@@ -185,7 +185,8 @@ public:
     /**
      * Does this voice ned an update?
      *
-     * @param params - Input parametetrs to check matching.
+     * @param params - Input parameters to check matching.
+     *
      * @return True if this voice needs an update, otherwise false.
      */
     bool ShouldUpdateParameters(const InParameter& params) const;
@@ -194,9 +195,9 @@ public:
      * Update the parameters of this voice.
      *
      * @param error_info  - Output error code.
-     * @param params      - Input parametters to udpate from.
+     * @param params      - Input parameters to update from.
      * @param pool_mapper - Used to map buffers.
-     * @param behavior   - behavior to check supported features.
+     * @param behavior    - behavior to check supported features.
      */
     void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
                           const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
@@ -218,12 +219,12 @@ public:
     /**
      * 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 error_infos  - Output 2D array of errors, 2 per wavebuffer.
+     * @param error_count  - Number of errors provided. Unused.
+     * @param params       - Input parameters 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.
+     * @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,
@@ -233,13 +234,13 @@ public:
     /**
      * Update a wavebuffer.
      *
-     * @param error_infos          - Output array of errors.
+     * @param error_info           - 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.
+     * @param behavior             - Used to check for supported features.
      */
     void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
                           const WaveBufferInternal& wave_buffer_internal,
@@ -276,7 +277,7 @@ public:
     /**
      * Check if this voice has any mixing connections.
      *
-     * @return True if this voice participes in mixing, otherwise false.
+     * @return True if this voice participates in mixing, otherwise false.
      */
     bool HasAnyConnection() const;
 
@@ -301,7 +302,8 @@ public:
     /**
      * Update this voice on command generation.
      *
-     * @param voice_states  - Voice states for these wavebuffers.
+     * @param voice_context - Voice context for these wavebuffers.
+     *
      * @return True if this voice should be generated, otherwise false.
      */
     bool UpdateForCommandGeneration(VoiceContext& voice_context);

+ 29 - 354
src/audio_core/sink/cubeb_sink.cpp

@@ -1,21 +1,13 @@
 // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
-#include <algorithm>
-#include <atomic>
 #include <span>
+#include <vector>
 
-#include "audio_core/audio_core.h"
-#include "audio_core/audio_event.h"
-#include "audio_core/audio_manager.h"
+#include "audio_core/common/common.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
@@ -42,10 +34,10 @@ public:
      * @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_,
+    CubebSinkStream(cubeb* ctx_, u32 device_channels_, 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_} {
+                    StreamType type_, Core::System& system_)
+        : SinkStream(system_, type_), ctx{ctx_} {
 #ifdef _WIN32
         CoInitializeEx(nullptr, COINIT_MULTITHREADED);
 #endif
@@ -79,12 +71,10 @@ public:
 
         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);
+        LOG_INFO(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) {
@@ -111,6 +101,8 @@ public:
     ~CubebSinkStream() override {
         LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
 
+        Unstall();
+
         if (!ctx) {
             return;
         }
@@ -136,21 +128,14 @@ public:
      * @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) {
+    void Start(bool resume = false) override {
+        if (!ctx || !paused) {
             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;
+        paused = false;
+        if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
+            LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
         }
     }
 
@@ -158,206 +143,19 @@ public:
      * Stop the sink stream.
      */
     void Stop() override {
-        if (!ctx) {
+        Unstall();
+
+        if (!ctx || paused) {
             return;
         }
 
+        paused = true;
         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()};
-            if (yuzu_volume > 1.0f) {
-                yuzu_volume = 0.6f + 20 * std::log10(yuzu_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).
@@ -378,106 +176,15 @@ private:
 
         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);
+            impl->ProcessAudioIn(input_buffer, num_frames);
         } 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);
+            impl->ProcessAudioOutAndRender(output_buffer, num_frames);
         }
 
         return num_frames_;
@@ -490,32 +197,12 @@ private:
      * @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) {}
+    static void StateCallback(cubeb_stream*, void*, cubeb_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) {
@@ -569,15 +256,15 @@ CubebSink::~CubebSink() {
 #endif
 }
 
-SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels,
-                                         const std::string& name, const StreamType type) {
+SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
+                                         const std::string& name, 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) {
+void CubebSink::CloseStream(SinkStream* stream) {
     for (size_t i = 0; i < sink_streams.size(); i++) {
         if (sink_streams[i].get() == stream) {
             sink_streams[i].reset();
@@ -591,18 +278,6 @@ 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;
@@ -611,19 +286,19 @@ f32 CubebSink::GetDeviceVolume() const {
     return sink_streams[0]->GetDeviceVolume();
 }
 
-void CubebSink::SetDeviceVolume(const f32 volume) {
+void CubebSink::SetDeviceVolume(f32 volume) {
     for (auto& stream : sink_streams) {
         stream->SetDeviceVolume(volume);
     }
 }
 
-void CubebSink::SetSystemVolume(const f32 volume) {
+void CubebSink::SetSystemVolume(f32 volume) {
     for (auto& stream : sink_streams) {
         stream->SetSystemVolume(volume);
     }
 }
 
-std::vector<std::string> ListCubebSinkDevices(const bool capture) {
+std::vector<std::string> ListCubebSinkDevices(bool capture) {
     std::vector<std::string> device_list;
     cubeb* ctx;
 

+ 3 - 14
src/audio_core/sink/cubeb_sink.h

@@ -34,8 +34,7 @@ public:
      *                          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,
@@ -46,23 +45,13 @@ public:
      *
      * @param stream - The stream to close.
      */
-    void CloseStream(const SinkStream* stream) override;
+    void CloseStream(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.
      *
@@ -101,7 +90,7 @@ private:
 };
 
 /**
- * Get a list of conencted devices from Cubeb.
+ * Get a list of connected devices from Cubeb.
  *
  * @param capture - Return input (capture) devices if true, otherwise output devices.
  */

+ 27 - 22
src/audio_core/sink/null_sink.h

@@ -3,10 +3,29 @@
 
 #pragma once
 
+#include <string>
+#include <string_view>
+#include <vector>
+
 #include "audio_core/sink/sink.h"
 #include "audio_core/sink/sink_stream.h"
 
+namespace Core {
+class System;
+} // namespace Core
+
 namespace AudioCore::Sink {
+class NullSinkStreamImpl final : public SinkStream {
+public:
+    explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
+        : SinkStream{system_, type_} {}
+    ~NullSinkStreamImpl() override {}
+    void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {}
+    std::vector<s16> ReleaseBuffer(u64) override {
+        return {};
+    }
+};
+
 /**
  * A no-op sink for when no audio out is wanted.
  */
@@ -15,17 +34,16 @@ 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;
+    SinkStream* AcquireSinkStream(Core::System& system, u32, const std::string&,
+                                  StreamType type) override {
+        if (null_sink == nullptr) {
+            null_sink = std::make_unique<NullSinkStreamImpl>(system, type);
+        }
+        return null_sink.get();
     }
 
-    void CloseStream([[maybe_unused]] const SinkStream* stream) override {}
+    void CloseStream(SinkStream*) override {}
     void CloseStreams() override {}
-    void PauseStreams() override {}
-    void UnpauseStreams() override {}
     f32 GetDeviceVolume() const override {
         return 1.0f;
     }
@@ -33,20 +51,7 @@ public:
     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;
+    SinkStreamPtr null_sink{};
 };
 
 } // namespace AudioCore::Sink

+ 32 - 345
src/audio_core/sink/sdl2_sink.cpp

@@ -1,20 +1,13 @@
 // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
-#include <algorithm>
-#include <atomic>
+#include <span>
+#include <vector>
 
-#include "audio_core/audio_core.h"
-#include "audio_core/audio_event.h"
-#include "audio_core/audio_manager.h"
+#include "audio_core/common/common.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
@@ -44,10 +37,9 @@ public:
      * @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_} {
+    SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device,
+                  const std::string& input_device, StreamType type_, Core::System& system_)
+        : SinkStream{system_, type_} {
         system_channels = system_channels_;
         device_channels = device_channels_;
 
@@ -63,8 +55,6 @@ public:
         spec.callback = &SDLSinkStream::DataCallback;
         spec.userdata = this;
 
-        playing_buffer.consumed = true;
-
         std::string device_name{output_device};
         bool capture{false};
         if (type == StreamType::In) {
@@ -84,31 +74,30 @@ public:
             return;
         }
 
-        LOG_DEBUG(Service_Audio,
-                  "Opening sdl stream {} with: rate {} channels {} (system channels {}) "
-                  " samples {}",
-                  device, obtained.freq, obtained.channels, system_channels, obtained.samples);
+        LOG_INFO(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);
+        LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name);
+        Finalize();
     }
 
     /**
      * Finalize the sink stream.
      */
     void Finalize() override {
+        Unstall();
         if (device == 0) {
             return;
         }
 
+        Stop();
         SDL_CloseAudioDevice(device);
     }
 
@@ -118,216 +107,28 @@ public:
      * @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) {
+    void Start(bool resume = false) override {
+        if (device == 0 || !paused) {
             return;
         }
 
-        if (resume && was_playing) {
-            SDL_PauseAudioDevice(device, 0);
-            paused = false;
-        } else if (!resume) {
-            SDL_PauseAudioDevice(device, 0);
-            paused = false;
-        }
+        paused = false;
+        SDL_PauseAudioDevice(device, 0);
     }
 
     /**
      * Stop the sink stream.
      */
-    void Stop() {
-        if (device == 0) {
+    void Stop() override {
+        Unstall();
+        if (device == 0 || paused) {
             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;
+        SDL_PauseAudioDevice(device, 1);
     }
 
 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).
@@ -345,122 +146,20 @@ private:
 
         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);
+            std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
+                                              num_frames * frame_size};
+            impl->ProcessAudioIn(input_buffer, num_frames);
         } 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);
+            impl->ProcessAudioOutAndRender(output_buffer, num_frames);
         }
     }
 
     /// 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) {
@@ -482,14 +181,14 @@ SDLSink::SDLSink(std::string_view target_device_name) {
 
 SDLSink::~SDLSink() = default;
 
-SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels,
-                                       const std::string&, const StreamType type) {
+SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels,
+                                       const std::string&, 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) {
+void SDLSink::CloseStream(SinkStream* stream) {
     for (size_t i = 0; i < sink_streams.size(); i++) {
         if (sink_streams[i].get() == stream) {
             sink_streams[i].reset();
@@ -503,18 +202,6 @@ 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;
@@ -523,19 +210,19 @@ f32 SDLSink::GetDeviceVolume() const {
     return sink_streams[0]->GetDeviceVolume();
 }
 
-void SDLSink::SetDeviceVolume(const f32 volume) {
+void SDLSink::SetDeviceVolume(f32 volume) {
     for (auto& stream : sink_streams) {
         stream->SetDeviceVolume(volume);
     }
 }
 
-void SDLSink::SetSystemVolume(const f32 volume) {
+void SDLSink::SetSystemVolume(f32 volume) {
     for (auto& stream : sink_streams) {
         stream->SetSystemVolume(volume);
     }
 }
 
-std::vector<std::string> ListSDLSinkDevices(const bool capture) {
+std::vector<std::string> ListSDLSinkDevices(bool capture) {
     std::vector<std::string> device_list;
 
     if (!SDL_WasInit(SDL_INIT_AUDIO)) {

+ 3 - 14
src/audio_core/sink/sdl2_sink.h

@@ -32,8 +32,7 @@ public:
      *                          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,
@@ -44,23 +43,13 @@ public:
      *
      * @param stream - The stream to close.
      */
-    void CloseStream(const SinkStream* stream) override;
+    void CloseStream(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.
      *
@@ -92,7 +81,7 @@ private:
 };
 
 /**
- * Get a list of conencted devices from Cubeb.
+ * Get a list of connected devices from SDL.
  *
  * @param capture - Return input (capture) devices if true, otherwise output devices.
  */

+ 2 - 13
src/audio_core/sink/sink.h

@@ -32,23 +32,13 @@ public:
      *
      * @param stream - The stream to close.
      */
-    virtual void CloseStream(const SinkStream* stream) = 0;
+    virtual void CloseStream(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.
@@ -58,8 +48,7 @@ public:
      *                          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,

+ 3 - 3
src/audio_core/sink/sink_details.cpp

@@ -5,7 +5,7 @@
 #include <memory>
 #include <string>
 #include <vector>
-#include "audio_core/sink/null_sink.h"
+
 #include "audio_core/sink/sink_details.h"
 #ifdef HAVE_CUBEB
 #include "audio_core/sink/cubeb_sink.h"
@@ -13,6 +13,7 @@
 #ifdef HAVE_SDL2
 #include "audio_core/sink/sdl2_sink.h"
 #endif
+#include "audio_core/sink/null_sink.h"
 #include "common/logging/log.h"
 
 namespace AudioCore::Sink {
@@ -59,8 +60,7 @@ const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
 
     if (sink_id == "auto" || iter == std::end(sink_details)) {
         if (sink_id != "auto") {
-            LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}",
-                      sink_id);
+            LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
         }
         // Auto-select.
         // sink_details is ordered in terms of desirability, with the best choice at the front.

+ 279 - 0
src/audio_core/sink/sink_stream.cpp

@@ -0,0 +1,279 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <atomic>
+#include <memory>
+#include <span>
+#include <vector>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/common/common.h"
+#include "audio_core/sink/sink_stream.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+#include "common/settings.h"
+#include "core/core.h"
+
+namespace AudioCore::Sink {
+
+void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
+    if (type == StreamType::In) {
+        queue.enqueue(buffer);
+        queued_buffers++;
+        return;
+    }
+
+    constexpr s32 min{std::numeric_limits<s16>::min()};
+    constexpr s32 max{std::numeric_limits<s16>::max()};
+
+    auto yuzu_volume{Settings::Volume()};
+    if (yuzu_volume > 1.0f) {
+        yuzu_volume = 0.6f + 20 * std::log10(yuzu_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++;
+}
+
+std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) {
+    constexpr s32 min = std::numeric_limits<s16>::min();
+    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;
+}
+
+void SinkStream::ClearQueue() {
+    samples_buffer.Pop();
+    while (queue.pop()) {
+    }
+    queued_buffers = 0;
+    playing_buffer = {};
+    playing_buffer.consumed = true;
+}
+
+void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames) {
+    const std::size_t num_channels = GetDeviceChannels();
+    const std::size_t frame_size = num_channels;
+    const std::size_t frame_size_bytes = frame_size * sizeof(s16);
+    size_t frames_written{0};
+
+    // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
+    // paused and we'll desync, so just return.
+    if (system.IsPaused() || system.IsShuttingDown()) {
+        return;
+    }
+
+    if (queued_buffers > max_queue_size) {
+        Stall();
+    }
+
+    while (frames_written < num_frames) {
+        // 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 (!queue.try_dequeue(playing_buffer)) {
+                // If no buffer was available we've underrun, just push the samples and
+                // continue.
+                samples_buffer.Push(&input_buffer[frames_written * frame_size],
+                                    (num_frames - frames_written) * frame_size);
+                frames_written = num_frames;
+                continue;
+            }
+            // Successfully dequeued a new buffer.
+            queued_buffers--;
+        }
+
+        // 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)};
+
+        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) {
+            playing_buffer.consumed = true;
+        }
+    }
+
+    std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes);
+
+    if (queued_buffers <= max_queue_size) {
+        Unstall();
+    }
+}
+
+void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) {
+    const std::size_t num_channels = GetDeviceChannels();
+    const std::size_t frame_size = num_channels;
+    const std::size_t frame_size_bytes = frame_size * sizeof(s16);
+    size_t frames_written{0};
+
+    // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
+    // paused and we'll desync, so just play silence.
+    if (system.IsPaused() || system.IsShuttingDown()) {
+        constexpr std::array<s16, 6> silence{};
+        for (size_t i = frames_written; i < num_frames; i++) {
+            std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes);
+        }
+        return;
+    }
+
+    // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get
+    // queued up (30+) but not all at once, which causes constant stalling here, so just let the
+    // video play out without attempting to stall.
+    // Can hopefully remove this later with a more complete NVDEC implementation.
+    const auto nvdec_active{system.AudioCore().IsNVDECActive()};
+    if (!nvdec_active && queued_buffers > max_queue_size) {
+        Stall();
+    }
+
+    while (frames_written < num_frames) {
+        // 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 (!queue.try_dequeue(playing_buffer)) {
+                // If no buffer was available we've underrun, fill the remaining buffer with
+                // the last written frame and continue.
+                for (size_t i = frames_written; i < num_frames; i++) {
+                    std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes);
+                }
+                frames_written = num_frames;
+                continue;
+            }
+            // Successfully dequeued a new buffer.
+            queued_buffers--;
+        }
+
+        // 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)};
+
+        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) {
+            playing_buffer.consumed = true;
+        }
+    }
+
+    std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
+                frame_size_bytes);
+
+    if (stalled && queued_buffers <= max_queue_size) {
+        Unstall();
+    }
+}
+
+void SinkStream::Stall() {
+    if (stalled) {
+        return;
+    }
+    stalled = true;
+    system.StallProcesses();
+}
+
+void SinkStream::Unstall() {
+    if (!stalled) {
+        return;
+    }
+    system.UnstallProcesses();
+    stalled = false;
+}
+
+} // namespace AudioCore::Sink

+ 100 - 75
src/audio_core/sink/sink_stream.h

@@ -3,12 +3,20 @@
 
 #pragma once
 
+#include <array>
 #include <atomic>
 #include <memory>
+#include <span>
 #include <vector>
 
 #include "audio_core/common/common.h"
 #include "common/common_types.h"
+#include "common/reader_writer_queue.h"
+#include "common/ring_buffer.h"
+
+namespace Core {
+class System;
+} // namespace Core
 
 namespace AudioCore::Sink {
 
@@ -34,20 +42,24 @@ struct SinkBuffer {
  * 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.
+ * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were
+ * appended, skipping a buffer will result in the queue getting stuck, and 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;
+    explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {}
+    virtual ~SinkStream() {
+        Unstall();
+    }
 
     /**
      * Finalize the sink stream.
      */
-    virtual void Finalize() = 0;
+    virtual void Finalize() {}
 
     /**
      * Start the sink stream.
@@ -55,48 +67,19 @@ public:
      * @param resume - Set to true if this is resuming the stream a previously-active stream.
      *                 Default false.
      */
-    virtual void Start(bool resume = false) = 0;
+    virtual void Start(bool resume = false) {}
 
     /**
      * 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;
+    virtual void Stop() {}
 
     /**
      * Check if the stream is paused.
      *
      * @return True if paused, otherwise false.
      */
-    bool IsPaused() {
+    bool IsPaused() const {
         return paused;
     }
 
@@ -127,34 +110,6 @@ public:
         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.
      *
@@ -196,27 +151,97 @@ public:
      *
      * @return The number of queued buffers.
      */
-    u32 GetQueueSize() {
+    u32 GetQueueSize() const {
         return queued_buffers.load();
     }
 
+    /**
+     * Set the maximum buffer queue size.
+     */
+    void SetRingSize(u32 ring_size) {
+        max_queue_size = ring_size;
+    }
+
+    /**
+     * 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);
+
+    /**
+     * 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);
+
+    /**
+     * Empty out the buffer queue.
+     */
+    void ClearQueue();
+
+    /**
+     * Callback for AudioIn.
+     *
+     * @param input_buffer - Input buffer to be filled with samples.
+     * @param num_frames - Number of frames to be filled.
+     */
+    void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames);
+
+    /**
+     * Callback for AudioOut and AudioRenderer.
+     *
+     * @param output_buffer - Output buffer to be filled with samples.
+     * @param num_frames - Number of frames to be filled.
+     */
+    void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames);
+
+    /**
+     * Stall core processes if the audio thread falls too far behind.
+     */
+    void Stall();
+
+    /**
+     * Unstall core processes.
+     */
+    void Unstall();
+
 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{};
+    /// Core system
+    Core::System& system;
+    /// Type of this stream
+    StreamType type;
     /// 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};
+    /// Name of this stream
+    std::string name{};
+
+private:
+    /// 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<SinkBuffer> queue;
+    /// The currently-playing audio buffer
+    SinkBuffer playing_buffer{};
+    /// The last played (or received) frame of audio, used when the callback underruns
+    std::array<s16, MaxChannels> last_frame{};
+    /// Number of buffers waiting to be played
+    std::atomic<u32> queued_buffers{};
+    /// The ring size for audio out buffers (usually 4, rarely 2 or 8)
+    u32 max_queue_size{};
+    /// 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};
+    /// True if coretiming has been stalled
+    bool stalled{false};
 };
 
 using SinkStreamPtr = std::unique_ptr<SinkStream>;

+ 5 - 4
src/common/CMakeLists.txt

@@ -19,7 +19,7 @@ find_package(Git QUIET)
 
 add_custom_command(OUTPUT scm_rev.cpp
     COMMAND ${CMAKE_COMMAND}
-      -DSRC_DIR=${CMAKE_SOURCE_DIR}
+      -DSRC_DIR=${PROJECT_SOURCE_DIR}
       -DBUILD_REPOSITORY=${BUILD_REPOSITORY}
       -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE}
       -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING}
@@ -31,13 +31,13 @@ add_custom_command(OUTPUT scm_rev.cpp
       -DGIT_BRANCH=${GIT_BRANCH}
       -DBUILD_FULLNAME=${BUILD_FULLNAME}
       -DGIT_EXECUTABLE=${GIT_EXECUTABLE}
-      -P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
+      -P ${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
     DEPENDS
       # Check that the scm_rev files haven't changed
       "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in"
       "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h"
       # technically we should regenerate if the git version changed, but its not worth the effort imo
-      "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
+      "${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
     VERBATIM
 )
 
@@ -166,6 +166,7 @@ if(ARCHITECTURE_x86_64)
             x64/xbyak_abi.h
             x64/xbyak_util.h
     )
+    target_link_libraries(common PRIVATE xbyak)
 endif()
 
 if (MSVC)
@@ -189,7 +190,7 @@ endif()
 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)
+target_link_libraries(common PRIVATE lz4::lz4)
 if (TARGET zstd::zstd)
   target_link_libraries(common PRIVATE zstd::zstd)
 else()

+ 1 - 0
src/common/announce_multiplayer_room.h

@@ -16,6 +16,7 @@ namespace AnnounceMultiplayerRoom {
 struct GameInfo {
     std::string name{""};
     u64 id{0};
+    std::string version{""};
 };
 
 struct Member {

+ 5 - 0
src/common/input.h

@@ -102,6 +102,8 @@ struct AnalogProperties {
     float offset{};
     // Invert direction of the sensor data
     bool inverted{};
+    // Press once to activate, press again to release
+    bool toggle{};
 };
 
 // Single analog sensor data
@@ -115,8 +117,11 @@ struct AnalogStatus {
 struct ButtonStatus {
     Common::UUID uuid{};
     bool value{};
+    // Invert value of the button
     bool inverted{};
+    // Press once to activate, press again to release
     bool toggle{};
+    // Internal lock for the toggle status
     bool locked{};
 };
 

+ 1 - 1
src/common/parent_of_member.h

@@ -11,7 +11,7 @@ namespace Common {
 namespace detail {
 template <typename T, size_t Size, size_t Align>
 struct TypedStorageImpl {
-    std::aligned_storage_t<Size, Align> storage_;
+    alignas(Align) u8 storage_[Size];
 };
 } // namespace detail
 

+ 1 - 0
src/common/settings.cpp

@@ -195,6 +195,7 @@ void RestoreGlobalState(bool is_powered_on) {
     values.shader_backend.SetGlobal(true);
     values.use_asynchronous_shaders.SetGlobal(true);
     values.use_fast_gpu_time.SetGlobal(true);
+    values.use_pessimistic_flushes.SetGlobal(true);
     values.bg_red.SetGlobal(true);
     values.bg_green.SetGlobal(true);
     values.bg_blue.SetGlobal(true);

+ 2 - 0
src/common/settings.h

@@ -446,6 +446,7 @@ struct Values {
                                                           ShaderBackend::SPIRV, "shader_backend"};
     SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
     SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
+    SwitchableSetting<bool> use_pessimistic_flushes{false, "use_pessimistic_flushes"};
 
     SwitchableSetting<u8> bg_red{0, "bg_red"};
     SwitchableSetting<u8> bg_green{0, "bg_green"};
@@ -529,6 +530,7 @@ struct Values {
     Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
     Setting<bool> use_auto_stub{false, "use_auto_stub"};
     Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
+    Setting<bool> create_crash_dumps{false, "create_crash_dumps"};
 
     // Miscellaneous
     Setting<std::string> log_filter{"*:Info", "log_filter"};

+ 4 - 0
src/common/thread.h

@@ -54,6 +54,10 @@ public:
         is_set = false;
     }
 
+    [[nodiscard]] bool IsSet() {
+        return is_set;
+    }
+
 private:
     std::condition_variable condvar;
     std::mutex mutex;

+ 5 - 10
src/core/CMakeLists.txt

@@ -2,16 +2,8 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 add_library(core STATIC
-    announce_multiplayer_session.cpp
-    announce_multiplayer_session.h
     arm/arm_interface.h
     arm/arm_interface.cpp
-    arm/dynarmic/arm_dynarmic_32.cpp
-    arm/dynarmic/arm_dynarmic_32.h
-    arm/dynarmic/arm_dynarmic_64.cpp
-    arm/dynarmic/arm_dynarmic_64.h
-    arm/dynarmic/arm_dynarmic_cp15.cpp
-    arm/dynarmic/arm_dynarmic_cp15.h
     arm/dynarmic/arm_exclusive_monitor.cpp
     arm/dynarmic/arm_exclusive_monitor.h
     arm/exclusive_monitor.cpp
@@ -527,6 +519,9 @@ add_library(core STATIC
     hle/service/ncm/ncm.h
     hle/service/nfc/nfc.cpp
     hle/service/nfc/nfc.h
+    hle/service/nfp/amiibo_crypto.cpp
+    hle/service/nfp/amiibo_crypto.h
+    hle/service/nfp/amiibo_types.h
     hle/service/nfp/nfp.cpp
     hle/service/nfp/nfp.h
     hle/service/nfp/nfp_user.cpp
@@ -540,14 +535,14 @@ add_library(core STATIC
     hle/service/npns/npns.cpp
     hle/service/npns/npns.h
     hle/service/ns/errors.h
+    hle/service/ns/iplatform_service_manager.cpp
+    hle/service/ns/iplatform_service_manager.h
     hle/service/ns/language.cpp
     hle/service/ns/language.h
     hle/service/ns/ns.cpp
     hle/service/ns/ns.h
     hle/service/ns/pdm_qry.cpp
     hle/service/ns/pdm_qry.h
-    hle/service/ns/pl_u.cpp
-    hle/service/ns/pl_u.h
     hle/service/nvdrv/devices/nvdevice.h
     hle/service/nvdrv/devices/nvdisp_disp0.cpp
     hle/service/nvdrv/devices/nvdisp_disp0.h

+ 9 - 4
src/core/core.cpp

@@ -141,8 +141,6 @@ struct System::Impl {
         core_timing.SyncPause(false);
         is_paused = false;
 
-        audio_core->PauseSinks(false);
-
         return status;
     }
 
@@ -150,8 +148,6 @@ 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;
@@ -319,10 +315,19 @@ struct System::Impl {
         if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) {
             LOG_ERROR(Core, "Failed to read title for ROM (Error {})", load_result);
         }
+
+        std::string title_version;
+        const FileSys::PatchManager pm(program_id, system.GetFileSystemController(),
+                                       system.GetContentProvider());
+        const auto metadata = pm.GetControlMetadata();
+        if (metadata.first != nullptr) {
+            title_version = metadata.first->GetVersionString();
+        }
         if (auto room_member = room_network.GetRoomMember().lock()) {
             Network::GameInfo game_info;
             game_info.name = name;
             game_info.id = program_id;
+            game_info.version = title_version;
             room_member->SendGameInfo(game_info);
         }
 

+ 37 - 30
src/core/core_timing.cpp

@@ -73,7 +73,6 @@ void CoreTiming::Shutdown() {
     if (timer_thread) {
         timer_thread->join();
     }
-    pause_callbacks.clear();
     ClearPendingEvents();
     timer_thread.reset();
     has_started = false;
@@ -86,10 +85,6 @@ void CoreTiming::Pause(bool is_paused) {
     if (!is_paused) {
         pause_end_time = GetGlobalTimeNs().count();
     }
-
-    for (auto& cb : pause_callbacks) {
-        cb(is_paused);
-    }
 }
 
 void CoreTiming::SyncPause(bool is_paused) {
@@ -110,10 +105,6 @@ void CoreTiming::SyncPause(bool is_paused) {
     if (!is_paused) {
         pause_end_time = GetGlobalTimeNs().count();
     }
-
-    for (auto& cb : pause_callbacks) {
-        cb(is_paused);
-    }
 }
 
 bool CoreTiming::IsRunning() const {
@@ -143,13 +134,17 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
                                       std::chrono::nanoseconds resched_time,
                                       const std::shared_ptr<EventType>& event_type,
                                       std::uintptr_t user_data, bool absolute_time) {
-    std::scoped_lock scope{basic_lock};
-    const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
+    {
+        std::scoped_lock scope{basic_lock};
+        const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
+
+        event_queue.emplace_back(
+            Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
 
-    event_queue.emplace_back(
-        Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
+        std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+    }
 
-    std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+    event.Set();
 }
 
 void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
@@ -219,11 +214,6 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
     }
 }
 
-void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) {
-    std::scoped_lock lock{basic_lock};
-    pause_callbacks.emplace_back(std::move(callback));
-}
-
 std::optional<s64> CoreTiming::Advance() {
     std::scoped_lock lock{advance_lock, basic_lock};
     global_timer = GetGlobalTimeNs().count();
@@ -243,17 +233,17 @@ std::optional<s64> CoreTiming::Advance() {
             basic_lock.lock();
 
             if (evt.reschedule_time != 0) {
+                const auto next_schedule_time{new_schedule_time.has_value()
+                                                  ? new_schedule_time.value().count()
+                                                  : evt.reschedule_time};
+
                 // If this event was scheduled into a pause, its time now is going to be way behind.
                 // Re-set this event to continue from the end of the pause.
-                auto next_time{evt.time + evt.reschedule_time};
+                auto next_time{evt.time + next_schedule_time};
                 if (evt.time < pause_end_time) {
-                    next_time = pause_end_time + evt.reschedule_time;
+                    next_time = pause_end_time + next_schedule_time;
                 }
 
-                const auto next_schedule_time{new_schedule_time.has_value()
-                                                  ? new_schedule_time.value().count()
-                                                  : evt.reschedule_time};
-
                 event_queue.emplace_back(
                     Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
                 std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
@@ -264,8 +254,7 @@ std::optional<s64> CoreTiming::Advance() {
     }
 
     if (!event_queue.empty()) {
-        const s64 next_time = event_queue.front().time - global_timer;
-        return next_time;
+        return event_queue.front().time;
     } else {
         return std::nullopt;
     }
@@ -278,11 +267,29 @@ void CoreTiming::ThreadLoop() {
             paused_set = false;
             const auto next_time = Advance();
             if (next_time) {
-                if (*next_time > 0) {
-                    std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time);
-                    event.WaitFor(next_time_ns);
+                // There are more events left in the queue, wait until the next event.
+                const auto wait_time = *next_time - GetGlobalTimeNs().count();
+                if (wait_time > 0) {
+                    // Assume a timer resolution of 1ms.
+                    static constexpr s64 TimerResolutionNS = 1000000;
+
+                    // Sleep in discrete intervals of the timer resolution, and spin the rest.
+                    const auto sleep_time = wait_time - (wait_time % TimerResolutionNS);
+                    if (sleep_time > 0) {
+                        event.WaitFor(std::chrono::nanoseconds(sleep_time));
+                    }
+
+                    while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) {
+                        // Yield to reduce thread starvation.
+                        std::this_thread::yield();
+                    }
+
+                    if (event.IsSet()) {
+                        event.Reset();
+                    }
                 }
             } else {
+                // Queue is empty, wait until another event is scheduled and signals us to continue.
                 wait_set = true;
                 event.Wait();
             }

+ 0 - 6
src/core/core_timing.h

@@ -22,7 +22,6 @@ namespace Core::Timing {
 /// A callback that may be scheduled for a particular core timing event.
 using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
     std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>;
-using PauseCallback = std::function<void(bool paused)>;
 
 /// Contains the characteristics of a particular event.
 struct EventType {
@@ -134,9 +133,6 @@ public:
     /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
     std::optional<s64> Advance();
 
-    /// Register a callback function to be called when coretiming pauses.
-    void RegisterPauseCallback(PauseCallback&& callback);
-
 private:
     struct Event;
 
@@ -176,8 +172,6 @@ private:
     /// Cycle timing
     u64 ticks{};
     s64 downcount{};
-
-    std::vector<PauseCallback> pause_callbacks{};
 };
 
 /// Creates a core timing event with the given name and callback.

+ 1 - 1
src/core/file_sys/system_archive/shared_font.cpp

@@ -9,7 +9,7 @@
 #include "core/file_sys/system_archive/data/font_standard.h"
 #include "core/file_sys/system_archive/shared_font.h"
 #include "core/file_sys/vfs_vector.h"
-#include "core/hle/service/ns/pl_u.h"
+#include "core/hle/service/ns/iplatform_service_manager.h"
 
 namespace FileSys::SystemArchive {
 

+ 15 - 0
src/core/hid/emulated_controller.cpp

@@ -562,6 +562,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
         return;
     }
 
+    // GC controllers have triggers not buttons
+    if (npad_type == NpadStyleIndex::GameCube) {
+        if (index == Settings::NativeButton::ZR) {
+            return;
+        }
+        if (index == Settings::NativeButton::ZL) {
+            return;
+        }
+    }
+
     switch (index) {
     case Settings::NativeButton::A:
         controller.npad_button_state.a.Assign(current_status.value);
@@ -738,6 +748,11 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
         return;
     }
 
+    // Only GC controllers have analog triggers
+    if (npad_type != NpadStyleIndex::GameCube) {
+        return;
+    }
+
     const auto& trigger = controller.trigger_values[index];
 
     switch (index) {

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor