update openal-soft to 1.24.3

keeping the alt 87514151c4 (diff-73a8dc1ce58605f6c5ea53548454c3bae516ec5132a29c9d7ff7edf9730c75be)
This commit is contained in:
AzaezelX 2025-09-03 11:09:27 -05:00
parent 12db0500e8
commit ba32094b7b
276 changed files with 49304 additions and 8712 deletions

View file

@ -0,0 +1,180 @@
---
Checks: '-*,
bugprone-argument-comment,
bugprone-assert-side-effect,
bugprone-assignment-in-if-condition,
bugprone-bad-signal-to-kill-thread,
bugprone-bool-pointer-implicit-conversion,
bugprone-casting-through-void,
bugprone-chained-comparison,
bugprone-compare-pointer-to-member-virtual-function,
bugprone-copy-constructor-init,
bugprone-crtp-constructor-accessibility,
bugprone-dangling-handle,
bugprone-dynamic-static-initializers,
bugprone-fold-init-type,
bugprone-forward-declaration-namespace,
bugprone-forwarding-reference-overload,
bugprone-implicit-widening-of-multiplication-result,
bugprone-inaccurate-erase,
bugprone-incorrect-*,
bugprone-infinite-loop,
bugprone-integer-division,
bugprone-lambda-function-name,
bugprone-macro-repeated-side-effects,
bugprone-misplaced-*,
bugprone-move-forwarding-reference,
bugprone-multi-level-implicit-pointer-conversion,
bugprone-multiple-*,
bugprone-narrowing-conversions,
bugprone-no-escape,
bugprone-non-zero-enum-to-bool-conversion,
bugprone-not-null-terminated-result,
bugprone-optional-value-conversion,
bugprone-parent-virtual-call,
bugprone-pointer-arithmetic-on-polymorphic-object,
bugprone-posix-return,
bugprone-redundant-branch-condition,
bugprone-reserved-identifier,
bugprone-return-const-ref-from-parameter,
bugprone-shared-ptr-array-mismatch,
bugprone-signal-handler,
bugprone-signed-char-misuse,
bugprone-sizeof-*,
bugprone-spuriously-wake-up-functions,
bugprone-standalone-empty,
bugprone-string-*,
bugprone-stringview-nullptr,
bugprone-suspicious-*,
bugprone-swapped-arguments,
bugprone-terminating-continue,
bugprone-throw-keyword-missing,
bugprone-too-small-loop-variable,
bugprone-undefined-memory-manipulation,
bugprone-undelegated-constructor,
bugprone-unhandled-*,
bugprone-unique-ptr-array-mismatch,
bugprone-unsafe-functions,
bugprone-unused-*,
bugprone-use-after-move,
bugprone-virtual-near-miss,
cert-dcl50-cpp,
cert-dcl58-cpp,
cert-env33-c,
cert-err34-c,
cert-err52-cpp,
cert-err60-cpp,
cert-flp30-c,
cert-mem57-cpp,
clang-analyzer-apiModeling.*,
clang-analyzer-core.*,
clang-analyzer-cplusplus.*,
clang-analyzer-deadcode.DeadStores,
clang-analyzer-fuchsia.HandleChecker,
clang-analyzer-nullability.*,
clang-analyzer-optin.*,
clang-analyzer-osx.*,
clang-analyzer-security.FloatLoopCounter,
clang-analyzer-security.PutenvStackArray,
clang-analyzer-security.SetgidSetuidOrder,
clang-analyzer-security.cert.env.InvalidPtr,
clang-analyzer-security.insecureAPI.SecuritySyntaxChecker,
clang-analyzer-security.insecureAPI.UncheckedReturn,
clang-analyzer-security.insecureAPI.bcmp,
clang-analyzer-security.insecureAPI.bcopy,
clang-analyzer-security.insecureAPI.bzero,
clang-analyzer-security.insecureAPI.decodeValueOfObjCType,
clang-analyzer-security.insecureAPI.getpw,
clang-analyzer-security.insecureAPI.gets,
clang-analyzer-security.insecureAPI.mkstemp,
clang-analyzer-security.insecureAPI.mktemp,
clang-analyzer-security.insecureAPI.rand,
clang-analyzer-security.insecureAPI.strcpy,
clang-analyzer-security.insecureAPI.vfork,
clang-analyzer-unix.*,
clang-analyzer-valist.*,
clang-analyzer-webkit.*,
concurrency-thread-canceltype-asynchronous,
cppcoreguidelines-avoid-capturing-lambda-coroutines,
cppcoreguidelines-avoid-c-arrays,
cppcoreguidelines-avoid-goto,
cppcoreguidelines-avoid-reference-coroutine-parameters,
cppcoreguidelines-c-copy-assignment-signature,
cppcoreguidelines-explicit-virtual-functions,
cppcoreguidelines-interfaces-global-init,
cppcoreguidelines-narrowing-conversions,
cppcoreguidelines-no-malloc,
cppcoreguidelines-no-suspend-with-lock,
cppcoreguidelines-owning-memory,
cppcoreguidelines-prefer-member-initializer,
cppcoreguidelines-pro-bounds-array-to-pointer-decay,
cppcoreguidelines-pro-bounds-pointer-arithmetic,
cppcoreguidelines-pro-type-const-cast,
cppcoreguidelines-pro-type-cstyle-cast,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-pro-type-static-cast-downcast,
cppcoreguidelines-pro-type-union-access,
cppcoreguidelines-pro-type-vararg,
cppcoreguidelines-slicing,
cppcoreguidelines-virtual-class-destructor,
google-build-explicit-make-pair,
google-default-arguments,
google-explicit-constructor,
hicpp-exception-baseclass,
misc-confusable-identifiers,
misc-coroutine-hostile-raii,
misc-misleading-*,
misc-non-copyable-objects,
misc-throw-by-value-catch-by-reference,
misc-uniqueptr-reset-release,
modernize-avoid-*,
modernize-concat-nested-namespaces,
modernize-deprecated-*,
modernize-loop-convert,
modernize-macro-to-enum,
modernize-make-*,
modernize-pass-by-value,
modernize-raw-string-literal,
modernize-redundant-void-arg,
modernize-replace-*,
modernize-return-braced-init-list,
modernize-shrink-to-fit,
modernize-unary-static-assert,
modernize-use-auto,
modernize-use-bool-literals,
modernize-use-default-member-init,
modernize-use-emplace,
modernize-use-equals-*,
modernize-use-nodiscard,
modernize-use-noexcept,
modernize-use-nullptr,
modernize-use-override,
modernize-use-transparent-functors,
modernize-use-uncaught-exceptions,
modernize-use-using,
performance-faster-string-find,
performance-for-range-copy,
performance-inefficient-*,
performance-move-constructor-init,
performance-noexcept-destructor,
performance-noexcept-swap,
performance-unnecessary-copy-initialization,
portability-restrict-system-includes,
portability-std-allocator-const,
readability-const-return-type,
readability-container-contains,
readability-container-size-empty,
readability-convert-member-functions-to-static,
readability-delete-null-pointer,
readability-duplicate-include,
readability-else-after-return,
readability-inconsistent-declaration-parameter-name,
readability-make-member-function-const,
readability-misleading-indentation,
readability-misplaced-array-index,
readability-redundant-*,
readability-simplify-subscript-expr,
readability-static-definition-in-anonymous-namespace,
readability-string-compare,
readability-uniqueptr-delete-release,
readability-use-*'

View file

@ -106,9 +106,35 @@ jobs:
libdbus-1-dev", libdbus-1-dev",
build_type: "Release" build_type: "Release"
} }
- {
name: "Android_armeabi-v7a-Release",
os: ubuntu-latest,
cmake_opts: "-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \
-DALSOFT_EMBED_HRTF_DATA=TRUE \
-DALSOFT_REQUIRE_OPENSL=ON",
build_type: "Release"
}
- {
name: "Android_arm64-v8a-Release",
os: ubuntu-latest,
cmake_opts: "-DANDRIOD_ABI=arm64-v8a \
-DANDROID_PLATFORM=25 \
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \
-DALSOFT_EMBED_HRTF_DATA=TRUE \
-DALSOFT_REQUIRE_OPENSL=ON",
build_type: "Release"
}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with:
fetch-depth: '0'
- name: Get current commit tag, short hash, count and date
run: |
echo "CommitTag=$(git describe --tags --abbrev=0 --match *.*.*)" >> $env:GITHUB_ENV
echo "CommitHashShort=$(git rev-parse --short=8 HEAD)" >> $env:GITHUB_ENV
echo "CommitCount=$(git rev-list --count $env:GITHUB_REF_NAME)" >> $env:GITHUB_ENV
echo "CommitDate=$(git show -s --date=iso-local --format=%cd)" >> $env:GITHUB_ENV
- name: Install Dependencies - name: Install Dependencies
shell: bash shell: bash
@ -133,8 +159,8 @@ jobs:
cd build cd build
ctest ctest
- name: Create Archive - name: Set up Windows artifacts
if: ${{ matrix.config.os == 'windows-latest' }} if: ${{ contains(matrix.config.name, 'Win') }}
shell: bash shell: bash
run: | run: |
cd build cd build
@ -143,10 +169,74 @@ jobs:
cp ${{matrix.config.build_type}}/soft_oal.dll archive cp ${{matrix.config.build_type}}/soft_oal.dll archive
cp ${{matrix.config.build_type}}/OpenAL32.dll archive/router cp ${{matrix.config.build_type}}/OpenAL32.dll archive/router
- name: Upload Archive - name: Set up Android artifacts
# Upload package as an artifact of this workflow. if: ${{ contains(matrix.config.name, 'Android') }}
uses: actions/upload-artifact@v3.1.2 shell: bash
if: ${{ matrix.config.os == 'windows-latest' }} run: |
cd build
mkdir archive
cp ${{github.workspace}}/build/libopenal.so archive/
- name: Upload build as a workflow artifact
uses: actions/upload-artifact@v4
if: ${{ contains(matrix.config.name, 'Win') || contains(matrix.config.name, 'Android') }}
with: with:
name: soft_oal-${{matrix.config.name}} name: soft_oal-${{matrix.config.name}}
path: build/archive path: build/archive
outputs:
CommitTag: ${{env.CommitTag}}
CommitHashShort: ${{env.CommitHashShort}}
CommitCount: ${{env.CommitCount}}
CommitDate: ${{env.CommitDate}}
release:
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4.1.7
with:
path: "build"
pattern: "*-Win??-Release"
github-token: "${{secrets.GITHUB_TOKEN}}"
- name: Set up build folders
run: |
mkdir -p build/release/OpenALSoft/Documentation
mkdir -p build/release/OpenALSoft/Win32
mkdir -p build/release/OpenALSoft/Win64
echo "${{github.repository}}" >> "build/release/OpenALSoft/Documentation/Version.txt"
echo "v${{needs.build.outputs.CommitTag}}-${{needs.build.outputs.CommitHashShort}} ${{github.ref_name}}" >> "build/release/OpenALSoft/Documentation/Version.txt"
echo "Commit #${{needs.build.outputs.CommitCount}}" >> "build/release/OpenALSoft/Documentation/Version.txt"
echo "${{needs.build.outputs.CommitDate}}" >> "build/release/OpenALSoft/Documentation/Version.txt"
curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/README.md -o "build/release/OpenALSoft/Documentation/ReadMe.txt"
curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/ChangeLog -o "build/release/OpenALSoft/Documentation/ChangeLog.txt"
curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/COPYING -o "build/release/OpenALSoft/Documentation/License.txt"
curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/BSD-3Clause -o "build/release/OpenALSoft/Documentation/License_BSD-3Clause.txt"
curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/LICENSE-pffft -o "build/release/OpenALSoft/Documentation/License_PFFFT.txt"
curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/alsoftrc.sample -o "build/release/OpenALSoft/Win32/alsoft.ini"
curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/alsoftrc.sample -o "build/release/OpenALSoft/Win64/alsoft.ini"
cp "build/soft_oal-Win32-Release/soft_oal.dll" "build/release/OpenALSoft/Win32/OpenAL32.dll"
cp "build/soft_oal-Win64-Release/soft_oal.dll" "build/release/OpenALSoft/Win64/OpenAL32.dll"
cp -r "build/release/OpenALSoft" "build/release/OpenALSoft+HRTF"
cp "build/release/OpenALSoft+HRTF/Win32/alsoft.ini" "build/release/OpenALSoft+HRTF/Documentation/alsoft.ini"
curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/configs/HRTF/alsoft.ini -o "build/release/OpenALSoft+HRTF/Win32/alsoft.ini"
cp "build/release/OpenALSoft+HRTF/Win32/alsoft.ini" "build/release/OpenALSoft+HRTF/Win64/alsoft.ini"
- name: Compress artifacts
run: |
cd build/release
7z a OpenALSoft.zip ./OpenALSoft/*
7z a OpenALSoft+HRTF.zip ./OpenALSoft+HRTF/*
- name: GitHub pre-release
uses: "Sweeistaken/sweelease@v1.1"
with:
repo_token: "${{secrets.GITHUB_TOKEN}}"
automatic_release_tag: "latest"
prerelease: true
title: "OpenAL Soft v${{needs.build.outputs.CommitTag}}-${{needs.build.outputs.CommitHashShort}}"
files: "build/release/*"

View file

@ -1,31 +1,35 @@
name: makemhr name: utils
on: on:
push: push:
paths: paths:
- 'utils/makemhr/**' - 'utils/**'
- '.github/workflows/makemhr.yml' - 'examples/**'
- '.github/workflows/utils.yml'
workflow_dispatch: workflow_dispatch:
env: env:
BUILD_TYPE: Release BUILD_TYPE: Release
Branch: ${{github.ref_name}}
jobs: jobs:
Win64: Win64:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v3 - name: Clone repo and submodules
run: git clone https://github.com/${{github.repository}}.git . --branch ${{env.Branch}}
- name: Get current date - name: Get current date, commit hash and count
run: echo "CurrentDate=$(date +'%Y-%m-%d')" >> $env:GITHUB_ENV run: |
echo "CommitDate=$(git show -s --date=format:'%Y-%m-%d' --format=%cd)" >> $env:GITHUB_ENV
- name: Get commit hash echo "CommitHashShort=$(git rev-parse --short=7 HEAD)" >> $env:GITHUB_ENV
run: echo "CommitHash=$(git rev-parse --short=7 HEAD)" >> $env:GITHUB_ENV echo "CommitCount=$(git rev-list --count ${{env.Branch}} --)" >> $env:GITHUB_ENV
- name: Clone libmysofa - name: Clone libmysofa
run: git clone --depth 1 --branch v1.3.1 https://github.com/hoene/libmysofa.git libmysofa run: |
git clone https://github.com/hoene/libmysofa.git --branch v1.3.3
- name: Add MSBuild to PATH - name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@v1.1.3 uses: microsoft/setup-msbuild@v1.1.3
@ -52,25 +56,30 @@ jobs:
- name: Collect artifacts - name: Collect artifacts
run: | run: |
copy "build/Release/makemhr.exe" "Artifacts/makemhr.exe" copy "build/Release/makemhr.exe" "Artifacts/makemhr.exe"
copy "build/Release/sofa-info.exe" "Artifacts/sofa-info.exe"
copy "build/Release/alrecord.exe" "Artifacts/alrecord.exe"
copy "build/Release/altonegen.exe" "Artifacts/altonegen.exe"
copy "build/Release/openal-info.exe" "Artifacts/openal-info.exe"
copy "build/Release/allafplay.exe" "Artifacts/allafplay.exe"
copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll" copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll"
- name: Upload makemhr artifact - name: Upload artifacts
uses: actions/upload-artifact@v3.1.2 uses: actions/upload-artifact@v4
with: with:
name: makemhr name: ${{env.CommitDate}}_utils-r${{env.CommitCount}}@${{env.CommitHashShort}}
path: "Artifacts/" path: "Artifacts/"
- name: Compress artifacts - name: Compress artifacts
uses: papeloto/action-zip@v1 uses: papeloto/action-zip@v1
with: with:
files: Artifacts/ files: Artifacts/
dest: "Release/makemhr.zip" dest: "Release/utils.zip"
- name: GitHub pre-release - name: GitHub pre-release
uses: "marvinpinto/action-automatic-releases@latest" uses: "marvinpinto/action-automatic-releases@latest"
with: with:
repo_token: "${{secrets.GITHUB_TOKEN}}" repo_token: "${{secrets.GITHUB_TOKEN}}"
automatic_release_tag: "makemhr" automatic_release_tag: "utils"
prerelease: true prerelease: true
title: "[${{env.CurrentDate}}] makemhr-${{env.CommitHash}}" title: "[${{env.CommitDate}}] utils-r${{env.CommitCount}}@${{env.CommitHashShort}}"
files: "Release/makemhr.zip" files: "Release/utils.zip"

View file

@ -75,12 +75,12 @@ if(NOT CMAKE_DEBUG_POSTFIX)
FORCE) FORCE)
endif() endif()
set(DEFAULT_TARGET_PROPS set(ALSOFT_STD_VERSION_PROPS
# Require C++17. # Require C++17.
CXX_STANDARD 17 CXX_STANDARD 17
CXX_STANDARD_REQUIRED TRUE CXX_STANDARD_REQUIRED TRUE
# Prefer C11, but support C99 and earlier when possible. # Prefer C17, but support earlier when necessary.
C_STANDARD 11) C_STANDARD 17)
list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
@ -93,13 +93,13 @@ include(CheckCCompilerFlag)
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
include(CheckCSourceCompiles) include(CheckCSourceCompiles)
include(CheckCXXSourceCompiles) include(CheckCXXSourceCompiles)
include(CheckLinkerFlag)
include(CheckStructHasMember) include(CheckStructHasMember)
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
include(GNUInstallDirs) include(GNUInstallDirs)
find_package(PkgConfig) find_package(PkgConfig)
find_package(SDL2 QUIET) find_package(SDL3 QUIET)
option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON)
@ -144,6 +144,23 @@ if(DEFINED ALSOFT_AMBDEC_PRESETS)
message(WARNING "ALSOFT_AMBDEC_PRESETS is deprecated. Use ALSOFT_INSTALL_AMBDEC_PRESETS instead") message(WARNING "ALSOFT_AMBDEC_PRESETS is deprecated. Use ALSOFT_INSTALL_AMBDEC_PRESETS instead")
endif() endif()
if(MSVC)
option(FORCE_STATIC_VCRT "Force /MT for static VC runtimes" OFF)
if(FORCE_STATIC_VCRT)
foreach(flag_var
CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
if(${flag_var} MATCHES "/MD")
string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
endif()
endforeach(flag_var)
endif()
endif()
add_subdirectory(fmt-11.1.1 EXCLUDE_FROM_ALL)
set(CPP_DEFS ) # C pre-processor, not C++ set(CPP_DEFS ) # C pre-processor, not C++
set(INC_PATHS ) set(INC_PATHS )
@ -163,6 +180,21 @@ if(WIN32)
if(MINGW) if(MINGW)
option(ALSOFT_BUILD_IMPORT_LIB "Build an import .lib using dlltool (requires sed)" ON) option(ALSOFT_BUILD_IMPORT_LIB "Build an import .lib using dlltool (requires sed)" ON)
endif() endif()
if(NOT ALSOFT_UWP)
# Some systems may need NTDDI_VERSION defined to NTDDI_VISTA or later
check_c_source_compiles("#define INITGUID
#include <shlobj.h>
#include <knownfolders.h>
int main()
{
SHGetKnownFolderPath(&FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, NULL, NULL);
return 0;
}" HAVE_SHGETKNOWNFOLDERPATH_NO_NTDDI)
if(NOT HAVE_SHGETKNOWNFOLDERPATH_NO_NTDDI)
set(CPP_DEFS ${CPP_DEFS} NTDDI_VERSION=NTDDI_VISTA)
endif()
endif()
elseif(APPLE) elseif(APPLE)
option(ALSOFT_OSX_FRAMEWORK "Build as macOS framework" OFF) option(ALSOFT_OSX_FRAMEWORK "Build as macOS framework" OFF)
endif() endif()
@ -181,8 +213,8 @@ if(NOT LIBTYPE)
endif() endif()
set(LIB_MAJOR_VERSION "1") set(LIB_MAJOR_VERSION "1")
set(LIB_MINOR_VERSION "23") set(LIB_MINOR_VERSION "24")
set(LIB_REVISION "1") set(LIB_REVISION "3")
set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}")
set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0)
@ -227,12 +259,18 @@ if(ANDROID)
endif() endif()
if(MSVC) if(MSVC)
set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS) # NOTE: _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR is temporary. When building on
# VS 2022 17.10 or newer, but using an older runtime, mutexes can crash
# when locked. Ideally the runtime should be updated on the system, but
# until the update becomes more widespread, this helps avoid some pain
# points.
set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
check_cxx_compiler_flag(/permissive- HAVE_PERMISSIVE_SWITCH) check_cxx_compiler_flag(/permissive- HAVE_PERMISSIVE_SWITCH)
if(HAVE_PERMISSIVE_SWITCH) if(HAVE_PERMISSIVE_SWITCH)
set(C_FLAGS ${C_FLAGS} $<$<COMPILE_LANGUAGE:CXX>:/permissive->) set(C_FLAGS ${C_FLAGS} $<$<COMPILE_LANGUAGE:CXX>:/permissive->)
endif() endif()
set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030 /wd5051) set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030 /wd5051
$<$<COMPILE_LANGUAGE:CXX>:/EHsc> /utf-8)
if(NOT DXSDK_DIR) if(NOT DXSDK_DIR)
string(REGEX REPLACE "\\\\" "/" DXSDK_DIR "$ENV{DXSDK_DIR}") string(REGEX REPLACE "\\\\" "/" DXSDK_DIR "$ENV{DXSDK_DIR}")
@ -242,19 +280,6 @@ if(MSVC)
if(DXSDK_DIR) if(DXSDK_DIR)
message(STATUS "Using DirectX SDK directory: ${DXSDK_DIR}") message(STATUS "Using DirectX SDK directory: ${DXSDK_DIR}")
endif() endif()
option(FORCE_STATIC_VCRT "Force /MT for static VC runtimes" OFF)
if(FORCE_STATIC_VCRT)
foreach(flag_var
CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
if(${flag_var} MATCHES "/MD")
string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
endif()
endforeach(flag_var)
endif()
else() else()
set(C_FLAGS ${C_FLAGS} -Winline -Wunused -Wall -Wextra -Wshadow -Wconversion -Wcast-align set(C_FLAGS ${C_FLAGS} -Winline -Wunused -Wall -Wextra -Wshadow -Wconversion -Wcast-align
-Wpedantic -Wpedantic
@ -277,15 +302,18 @@ else()
if(ALSOFT_WERROR) if(ALSOFT_WERROR)
set(C_FLAGS ${C_FLAGS} -Werror) set(C_FLAGS ${C_FLAGS} -Werror)
else()
set(C_FLAGS ${C_FLAGS} -Werror=undef)
endif() endif()
# We want RelWithDebInfo to actually include debug stuff (define _DEBUG # NOTE: This essentially provides the equivalent of the C++26 feature to
# instead of NDEBUG) # initialize all local variables with a non-0 bit pattern. Until C++26 is
foreach(flag_var CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS_RELWITHDEBINFO) # adopted, the [[gnu::uninitialized]] attribute will avoid the auto-
if(${flag_var} MATCHES "-DNDEBUG") # initialization where necessary.
string(REGEX REPLACE "-DNDEBUG" "-D_DEBUG" ${flag_var} "${${flag_var}}") check_c_compiler_flag(-ftrivial-auto-var-init=pattern HAVE_FTRIVIAL_AUTO_VAR_INIT)
endif() if(HAVE_FTRIVIAL_AUTO_VAR_INIT)
endforeach() set(C_FLAGS ${C_FLAGS} -ftrivial-auto-var-init=pattern)
endif()
check_c_compiler_flag(-fno-math-errno HAVE_FNO_MATH_ERRNO) check_c_compiler_flag(-fno-math-errno HAVE_FNO_MATH_ERRNO)
if(HAVE_FNO_MATH_ERRNO) if(HAVE_FNO_MATH_ERRNO)
@ -600,6 +628,8 @@ set(COMMON_OBJS
common/comptr.h common/comptr.h
common/dynload.cpp common/dynload.cpp
common/dynload.h common/dynload.h
common/filesystem.cpp
common/filesystem.h
common/flexarray.h common/flexarray.h
common/intrusive_ptr.h common/intrusive_ptr.h
common/opthelpers.h common/opthelpers.h
@ -657,7 +687,6 @@ set(CORE_OBJS
core/filters/nfc.h core/filters/nfc.h
core/filters/splitter.cpp core/filters/splitter.cpp
core/filters/splitter.h core/filters/splitter.h
core/fmt_traits.cpp
core/fmt_traits.h core/fmt_traits.h
core/fpu_ctrl.cpp core/fpu_ctrl.cpp
core/fpu_ctrl.h core/fpu_ctrl.h
@ -707,17 +736,17 @@ if(NOT WIN32)
else() else()
set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES}) set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES})
endif() endif()
else()
set(MISSING_VARS "")
if(NOT DBus1_INCLUDE_DIRS)
set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS")
endif()
if(NOT DBus1_LIBRARIES)
set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES")
endif()
message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})")
unset(MISSING_VARS)
endif() endif()
else()
set(MISSING_VARS "")
if(NOT DBus1_INCLUDE_DIRS)
set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS")
endif()
if(NOT DBus1_LIBRARIES)
set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES")
endif()
message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})")
unset(MISSING_VARS)
endif() endif()
endif() endif()
if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT) if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT)
@ -759,7 +788,6 @@ set(OPENAL_OBJS
al/effects/reverb.cpp al/effects/reverb.cpp
al/effects/vmorpher.cpp al/effects/vmorpher.cpp
al/error.cpp al/error.cpp
al/error.h
al/event.cpp al/event.cpp
al/event.h al/event.h
al/extension.cpp al/extension.cpp
@ -861,8 +889,10 @@ set(HAVE_PULSEAUDIO 0)
set(HAVE_COREAUDIO 0) set(HAVE_COREAUDIO 0)
set(HAVE_OPENSL 0) set(HAVE_OPENSL 0)
set(HAVE_OBOE 0) set(HAVE_OBOE 0)
set(HAVE_OTHERIO 0)
set(HAVE_WAVE 0) set(HAVE_WAVE 0)
set(HAVE_SDL2 0) set(HAVE_SDL2 0)
set(HAVE_SDL3 0)
if(WIN32 OR HAVE_DLFCN_H) if(WIN32 OR HAVE_DLFCN_H)
set(IS_LINKED "") set(IS_LINKED "")
@ -976,7 +1006,7 @@ if(NOT WIN32)
if(SNDIO_FOUND) if(SNDIO_FOUND)
set(HAVE_SNDIO 1) set(HAVE_SNDIO 1)
set(BACKENDS "${BACKENDS} SndIO (linked),") set(BACKENDS "${BACKENDS} SndIO (linked),")
set(ALC_OBJS ${ALC_OBJS} alc/backends/sndio.cpp alc/backends/sndio.h) set(ALC_OBJS ${ALC_OBJS} alc/backends/sndio.cpp alc/backends/sndio.hpp)
set(EXTRA_LIBS ${SNDIO_LIBRARIES} ${EXTRA_LIBS}) set(EXTRA_LIBS ${SNDIO_LIBRARIES} ${EXTRA_LIBS})
set(INC_PATHS ${INC_PATHS} ${SNDIO_INCLUDE_DIRS}) set(INC_PATHS ${INC_PATHS} ${SNDIO_INCLUDE_DIRS})
endif() endif()
@ -1043,8 +1073,20 @@ if(WIN32)
set(HAVE_WASAPI 1) set(HAVE_WASAPI 1)
set(BACKENDS "${BACKENDS} WASAPI,") set(BACKENDS "${BACKENDS} WASAPI,")
set(ALC_OBJS ${ALC_OBJS} alc/backends/wasapi.cpp alc/backends/wasapi.h) set(ALC_OBJS ${ALC_OBJS} alc/backends/wasapi.cpp alc/backends/wasapi.h)
if(NOT ALSOFT_UWP)
set(EXTRA_LIBS avrt ${EXTRA_LIBS})
endif()
endif() endif()
endif() endif()
option(ALSOFT_BACKEND_OTHERIO "Enable OtherIO backend" OFF)
option(ALSOFT_REQUIRE_OTHERIO "Require OtherIO backend" OFF)
if(ALSOFT_BACKEND_OTHERIO)
set(HAVE_OTHERIO 1)
set(BACKENDS "${BACKENDS} OtherIO,")
set(ALC_OBJS ${ALC_OBJS} alc/backends/otherio.cpp alc/backends/otherio.h)
endif()
endif() endif()
if(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM) if(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM)
message(FATAL_ERROR "Failed to enable required WinMM backend") message(FATAL_ERROR "Failed to enable required WinMM backend")
@ -1055,6 +1097,9 @@ endif()
if(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI) if(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI)
message(FATAL_ERROR "Failed to enable required WASAPI backend") message(FATAL_ERROR "Failed to enable required WASAPI backend")
endif() endif()
if(ALSOFT_REQUIRE_OTHERIO AND NOT HAVE_OTHERIO)
message(FATAL_ERROR "Failed to enable required OtherIO backend")
endif()
# Check JACK backend # Check JACK backend
option(ALSOFT_BACKEND_JACK "Enable JACK backend" ON) option(ALSOFT_BACKEND_JACK "Enable JACK backend" ON)
@ -1116,7 +1161,7 @@ if(ALSOFT_BACKEND_OBOE)
if(ANDROID) if(ANDROID)
set(OBOE_SOURCE "" CACHE STRING "Source directory for Oboe.") set(OBOE_SOURCE "" CACHE STRING "Source directory for Oboe.")
if(OBOE_SOURCE) if(OBOE_SOURCE)
add_subdirectory(${OBOE_SOURCE} ./oboe) add_subdirectory(${OBOE_SOURCE} ./oboe EXCLUDE_FROM_ALL)
set(OBOE_TARGET oboe) set(OBOE_TARGET oboe)
else() else()
find_package(oboe CONFIG) find_package(oboe CONFIG)
@ -1148,8 +1193,8 @@ if(ALSOFT_BACKEND_OPENSL)
if(OPENSL_FOUND) if(OPENSL_FOUND)
set(HAVE_OPENSL 1) set(HAVE_OPENSL 1)
set(ALC_OBJS ${ALC_OBJS} alc/backends/opensl.cpp alc/backends/opensl.h) set(ALC_OBJS ${ALC_OBJS} alc/backends/opensl.cpp alc/backends/opensl.h)
set(BACKENDS "${BACKENDS} OpenSL,") set(BACKENDS "${BACKENDS} OpenSL${IS_LINKED},")
set(EXTRA_LIBS ${OPENSL_LIBRARIES} ${EXTRA_LIBS}) add_backend_libs(${OPENSL_LIBRARIES})
set(INC_PATHS ${INC_PATHS} ${OPENSL_INCLUDE_DIRS}) set(INC_PATHS ${INC_PATHS} ${OPENSL_INCLUDE_DIRS})
endif() endif()
endif() endif()
@ -1165,7 +1210,7 @@ if(ALSOFT_BACKEND_PORTAUDIO)
if(PORTAUDIO_FOUND) if(PORTAUDIO_FOUND)
set(HAVE_PORTAUDIO 1) set(HAVE_PORTAUDIO 1)
set(BACKENDS "${BACKENDS} PortAudio${IS_LINKED},") set(BACKENDS "${BACKENDS} PortAudio${IS_LINKED},")
set(ALC_OBJS ${ALC_OBJS} alc/backends/portaudio.cpp alc/backends/portaudio.h) set(ALC_OBJS ${ALC_OBJS} alc/backends/portaudio.cpp alc/backends/portaudio.hpp)
add_backend_libs(${PORTAUDIO_LIBRARIES}) add_backend_libs(${PORTAUDIO_LIBRARIES})
set(INC_PATHS ${INC_PATHS} ${PORTAUDIO_INCLUDE_DIRS}) set(INC_PATHS ${INC_PATHS} ${PORTAUDIO_INCLUDE_DIRS})
endif() endif()
@ -1174,11 +1219,29 @@ if(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO)
message(FATAL_ERROR "Failed to enable required PortAudio backend") message(FATAL_ERROR "Failed to enable required PortAudio backend")
endif() endif()
# Check for SDL2 backend # Check for SDL2 or SDL3 backend
# Off by default, since it adds a runtime dependency # Off by default, since it adds a runtime dependency. Additionally, both SDL2
# and SDL3 can't be enabled simultaneously.
option(ALSOFT_BACKEND_SDL3 "Enable SDL3 backend" OFF)
option(ALSOFT_REQUIRE_SDL3 "Require SDL3 backend" OFF)
if(ALSOFT_BACKEND_SDL3)
if(SDL3_FOUND)
set(HAVE_SDL3 1)
set(ALC_OBJS ${ALC_OBJS} alc/backends/sdl3.cpp alc/backends/sdl3.h)
set(BACKENDS "${BACKENDS} SDL3,")
set(EXTRA_LIBS ${EXTRA_LIBS} SDL3::SDL3)
else()
message(STATUS "Could NOT find SDL3")
endif()
endif()
if(ALSOFT_REQUIRE_SDL3 AND NOT HAVE_SDL3)
message(FATAL_ERROR "Failed to enable required SDL3 backend")
endif()
option(ALSOFT_BACKEND_SDL2 "Enable SDL2 backend" OFF) option(ALSOFT_BACKEND_SDL2 "Enable SDL2 backend" OFF)
option(ALSOFT_REQUIRE_SDL2 "Require SDL2 backend" OFF) option(ALSOFT_REQUIRE_SDL2 "Require SDL2 backend" OFF)
if(ALSOFT_BACKEND_SDL2) if(ALSOFT_BACKEND_SDL2 AND NOT HAVE_SDL3)
find_package(SDL2)
if(SDL2_FOUND) if(SDL2_FOUND)
set(HAVE_SDL2 1) set(HAVE_SDL2 1)
set(ALC_OBJS ${ALC_OBJS} alc/backends/sdl2.cpp alc/backends/sdl2.h) set(ALC_OBJS ${ALC_OBJS} alc/backends/sdl2.cpp alc/backends/sdl2.h)
@ -1188,7 +1251,7 @@ if(ALSOFT_BACKEND_SDL2)
message(STATUS "Could NOT find SDL2") message(STATUS "Could NOT find SDL2")
endif() endif()
endif() endif()
if(ALSOFT_REQUIRE_SDL2 AND NOT SDL2_FOUND) if(ALSOFT_REQUIRE_SDL2 AND NOT HAVE_SDL2)
message(FATAL_ERROR "Failed to enable required SDL2 backend") message(FATAL_ERROR "Failed to enable required SDL2 backend")
endif() endif()
@ -1230,7 +1293,7 @@ if(ALSOFT_UPDATE_BUILD_VERSION AND GIT_FOUND AND EXISTS "${OpenAL_SOURCE_DIR}/.g
VERBATIM VERBATIM
) )
add_custom_target(build_version DEPENDS "${OpenAL_BINARY_DIR}/version_witness.txt") add_custom_target(alsoft.build_version DEPENDS "${OpenAL_BINARY_DIR}/version_witness.txt")
else() else()
set(GIT_BRANCH "UNKNOWN") set(GIT_BRANCH "UNKNOWN")
set(GIT_COMMIT_HASH "unknown") set(GIT_COMMIT_HASH "unknown")
@ -1259,6 +1322,15 @@ if(ALSOFT_EMBED_HRTF_DATA)
make_hrtf_header("Default HRTF.mhr" "default_hrtf") make_hrtf_header("Default HRTF.mhr" "default_hrtf")
endif() endif()
# Set a 16KB page size for Android
if(ANDROID)
set(CPP_DEFS ${CPP_DEFS} __BIONIC_NO_PAGE_SIZE_MACRO)
check_linker_flag(C "-Wl,-z,max-page-size=16384" HAS_MAX_PAGE_SIZE_16384)
if(HAS_MAX_PAGE_SIZE_16384)
set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,-z,max-page-size=16384")
endif()
endif()
if(ALSOFT_UTILS) if(ALSOFT_UTILS)
find_package(MySOFA) find_package(MySOFA)
@ -1271,9 +1343,9 @@ if(ALSOFT_UTILS)
endif() endif()
if(ALSOFT_UTILS OR ALSOFT_EXAMPLES) if(ALSOFT_UTILS OR ALSOFT_EXAMPLES)
find_package(SndFile) find_package(SndFile)
if(SDL2_FOUND) endif()
find_package(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE) if(ALSOFT_EXAMPLES AND SDL3_FOUND)
endif() find_package(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE)
endif() endif()
if(NOT WIN32) if(NOT WIN32)
@ -1295,12 +1367,14 @@ if(LIBTYPE STREQUAL "STATIC")
set(PKG_CONFIG_CFLAGS -DAL_LIBTYPE_STATIC) set(PKG_CONFIG_CFLAGS -DAL_LIBTYPE_STATIC)
foreach(FLAG ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) foreach(FLAG ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB})
# If this is already a linker flag, or is a full path+file, add it # If this is already a linker flag, or is a full path+file, add it
# as-is. If it's an SDL2 target, add the link flag for it. Otherwise, # as-is. If it's an SDL2 or SDL3 target, add the link flag for it.
# it's a name intended to be dressed as -lname. # Otherwise, it's a name intended to be dressed as -lname.
if(FLAG MATCHES "^-.*" OR EXISTS "${FLAG}") if(FLAG MATCHES "^-.*" OR EXISTS "${FLAG}")
set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} ${FLAG}") set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} ${FLAG}")
elseif(FLAG MATCHES "^SDL2::SDL2") elseif(FLAG MATCHES "^SDL2::SDL2")
set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -lSDL2") set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -lSDL2")
elseif(FLAG MATCHES "^SDL3::SDL3")
set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -lSDL3")
else() else()
set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -l${FLAG}") set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -l${FLAG}")
endif() endif()
@ -1311,28 +1385,53 @@ endif()
configure_file( configure_file(
"${OpenAL_SOURCE_DIR}/config.h.in" "${OpenAL_SOURCE_DIR}/config.h.in"
"${OpenAL_BINARY_DIR}/config.h") "${OpenAL_BINARY_DIR}/config.h")
configure_file(
"${OpenAL_SOURCE_DIR}/config_backends.h.in"
"${OpenAL_BINARY_DIR}/config_backends.h")
configure_file(
"${OpenAL_SOURCE_DIR}/config_simd.h.in"
"${OpenAL_BINARY_DIR}/config_simd.h")
configure_file( configure_file(
"${OpenAL_SOURCE_DIR}/openal.pc.in" "${OpenAL_SOURCE_DIR}/openal.pc.in"
"${OpenAL_BINARY_DIR}/openal.pc" "${OpenAL_BINARY_DIR}/openal.pc"
@ONLY) @ONLY)
add_library(alcommon STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS}) add_library(alsoft.common STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS})
target_include_directories(alcommon PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/include target_include_directories(alsoft.common PRIVATE ${OpenAL_SOURCE_DIR}/include
PUBLIC ${OpenAL_SOURCE_DIR}/common) PUBLIC ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common)
target_compile_definitions(alcommon PRIVATE ${CPP_DEFS}) target_compile_definitions(alsoft.common PRIVATE ${CPP_DEFS})
target_compile_options(alcommon PRIVATE ${C_FLAGS}) target_compile_options(alsoft.common PRIVATE ${C_FLAGS})
set_target_properties(alcommon PROPERTIES ${DEFAULT_TARGET_PROPS} POSITION_INDEPENDENT_CODE TRUE) target_link_libraries(alsoft.common PRIVATE alsoft::fmt)
set_target_properties(alsoft.common PROPERTIES ${ALSOFT_STD_VERSION_PROPS}
POSITION_INDEPENDENT_CODE TRUE)
unset(HAS_ROUTER) unset(HAS_ROUTER)
set(IMPL_TARGET OpenAL) # Either OpenAL or soft_oal. set(IMPL_TARGET OpenAL) # Either OpenAL or soft_oal.
set(NEED_ANALYZE_SOURCE_FILES "")
foreach(obj ${CORE_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${COMMON_OBJS})
IF (NOT ${obj} MATCHES "${CMAKE_BINARY_DIR}/default_hrtf.txt")
list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${obj}")
endif()
endforeach()
IF (ALSOFT_UTILS)
list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/utils/openal-info.c")
endif()
SET(CLANG_TIDY_EXECUTABLE "clang-tidy")
if(DEFINED ENV{CLANG_TIDY_EXECUTABLE})
SET(CLANG_TIDY_EXECUTABLE $ENV{CLANG_TIDY_EXECUTABLE})
endif()
add_custom_target(clang-tidy-check ${CLANG_TIDY_EXECUTABLE} -format-style=file -p ${CMAKE_BINARY_DIR}/compile_commands.json ${NEED_ANALYZE_SOURCE_FILES} DEPENDS ${NEED_ANALYZE_SOURCE_FILES})
# Build main library # Build main library
if(LIBTYPE STREQUAL "STATIC") if(LIBTYPE STREQUAL "STATIC")
add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS}) add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS})
target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC) target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC)
target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}
$<BUILD_LOCAL_INTERFACE:alsoft::fmt>)
if(WIN32) if(WIN32)
# This option is for static linking OpenAL Soft into another project # This option is for static linking OpenAL Soft into another project
@ -1357,7 +1456,7 @@ else()
PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}"
"AL_API=${EXPORT_DECL}" ${CPP_DEFS}) "AL_API=${EXPORT_DECL}" ${CPP_DEFS})
target_compile_options(OpenAL PRIVATE ${C_FLAGS}) target_compile_options(OpenAL PRIVATE ${C_FLAGS})
target_link_libraries(OpenAL PRIVATE alcommon ${LINKER_FLAGS}) target_link_libraries(OpenAL PRIVATE alsoft.common ${LINKER_FLAGS} alsoft::fmt)
target_include_directories(OpenAL target_include_directories(OpenAL
PUBLIC PUBLIC
$<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include> $<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
@ -1366,10 +1465,10 @@ else()
${OpenAL_SOURCE_DIR}/common ${OpenAL_SOURCE_DIR}/common
${OpenAL_BINARY_DIR} ${OpenAL_BINARY_DIR}
) )
set_target_properties(OpenAL PROPERTIES ${DEFAULT_TARGET_PROPS} PREFIX "" set_target_properties(OpenAL PROPERTIES ${ALSOFT_STD_VERSION_PROPS} PREFIX ""
OUTPUT_NAME ${LIBNAME}) OUTPUT_NAME ${LIBNAME})
if(TARGET build_version) if(TARGET alsoft.build_version)
add_dependencies(OpenAL build_version) add_dependencies(OpenAL alsoft.build_version)
endif() endif()
set(HAS_ROUTER 1) set(HAS_ROUTER 1)
@ -1388,24 +1487,30 @@ else()
if(WIN32) if(WIN32)
set_target_properties(${IMPL_TARGET} PROPERTIES PREFIX "") set_target_properties(${IMPL_TARGET} PROPERTIES PREFIX "")
endif() endif()
target_link_libraries(${IMPL_TARGET} PRIVATE alcommon ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) target_link_libraries(${IMPL_TARGET} PRIVATE alsoft.common ${LINKER_FLAGS} ${EXTRA_LIBS}
${MATH_LIB} alsoft::fmt)
if(ALSOFT_UWP) if(ALSOFT_UWP)
set(ALSOFT_CPPWINRT_VERSION "2.0.230706.1" CACHE STRING "The soft-oal default cppwinrt version") find_package(cppwinrt CONFIG)
if (TARGET Microsoft::CppWinRT)
target_link_libraries(${IMPL_TARGET} PRIVATE Microsoft::CppWinRT)
else()
set(ALSOFT_CPPWINRT_VERSION "2.0.230706.1" CACHE STRING "The soft-oal default cppwinrt version")
find_program(NUGET_EXE NAMES nuget) find_program(NUGET_EXE NAMES nuget)
if(NOT NUGET_EXE) if(NOT NUGET_EXE)
message("NUGET.EXE not found.") message("NUGET.EXE not found.")
message(FATAL_ERROR "Please install this executable, and run CMake again.") message(FATAL_ERROR "Please install this executable, and run CMake again.")
endif()
exec_program(${NUGET_EXE}
ARGS install "Microsoft.Windows.CppWinRT" -Version ${ALSOFT_CPPWINRT_VERSION} -ExcludeVersion -OutputDirectory "\"${CMAKE_BINARY_DIR}/packages\"")
set_target_properties(${IMPL_TARGET} PROPERTIES
VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props
)
target_link_libraries(${IMPL_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets)
endif() endif()
exec_program(${NUGET_EXE}
ARGS install "Microsoft.Windows.CppWinRT" -Version ${ALSOFT_CPPWINRT_VERSION} -ExcludeVersion -OutputDirectory "\"${CMAKE_BINARY_DIR}/packages\"")
set_target_properties(${IMPL_TARGET} PROPERTIES
VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props
)
target_link_libraries(${IMPL_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets)
endif() endif()
if(NOT WIN32 AND NOT APPLE) if(NOT WIN32 AND NOT APPLE)
@ -1464,18 +1569,18 @@ target_include_directories(${IMPL_TARGET}
${OpenAL_SOURCE_DIR}/common ${OpenAL_SOURCE_DIR}/common
) )
set_target_properties(${IMPL_TARGET} PROPERTIES ${DEFAULT_TARGET_PROPS} set_target_properties(${IMPL_TARGET} PROPERTIES ${ALSOFT_STD_VERSION_PROPS}
OUTPUT_NAME ${LIBNAME} OUTPUT_NAME ${LIBNAME}
VERSION ${LIB_VERSION} VERSION ${LIB_VERSION}
SOVERSION ${LIB_MAJOR_VERSION} SOVERSION ${LIB_MAJOR_VERSION}
) )
target_compile_definitions(${IMPL_TARGET} target_compile_definitions(${IMPL_TARGET}
PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES $<$<BOOL:${ALSOFT_EAX}>:ALSOFT_EAX> PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}"
"ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) ${CPP_DEFS})
target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS}) target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS})
if(TARGET build_version) if(TARGET alsoft.build_version)
add_dependencies(${IMPL_TARGET} build_version) add_dependencies(${IMPL_TARGET} alsoft.build_version)
endif() endif()
if(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC") if(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC")
@ -1598,7 +1703,7 @@ if(ALSOFT_UTILS)
target_include_directories(openal-info PRIVATE ${OpenAL_SOURCE_DIR}/common) target_include_directories(openal-info PRIVATE ${OpenAL_SOURCE_DIR}/common)
target_compile_options(openal-info PRIVATE ${C_FLAGS}) target_compile_options(openal-info PRIVATE ${C_FLAGS})
target_link_libraries(openal-info PRIVATE ${LINKER_FLAGS} OpenAL ${UNICODE_FLAG}) target_link_libraries(openal-info PRIVATE ${LINKER_FLAGS} OpenAL ${UNICODE_FLAG})
set_target_properties(openal-info PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(openal-info PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
if(ALSOFT_INSTALL_EXAMPLES) if(ALSOFT_INSTALL_EXAMPLES)
set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info)
endif() endif()
@ -1609,30 +1714,31 @@ if(ALSOFT_UTILS)
target_include_directories(uhjdecoder target_include_directories(uhjdecoder
PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common)
target_compile_options(uhjdecoder PRIVATE ${C_FLAGS}) target_compile_options(uhjdecoder PRIVATE ${C_FLAGS})
target_link_libraries(uhjdecoder PUBLIC alcommon target_link_libraries(uhjdecoder PUBLIC alsoft.common
PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG} alsoft::fmt)
set_target_properties(uhjdecoder PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(uhjdecoder PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(uhjencoder utils/uhjencoder.cpp) add_executable(uhjencoder utils/uhjencoder.cpp)
target_compile_definitions(uhjencoder PRIVATE ${CPP_DEFS}) target_compile_definitions(uhjencoder PRIVATE ${CPP_DEFS})
target_include_directories(uhjencoder target_include_directories(uhjencoder
PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common)
target_compile_options(uhjencoder PRIVATE ${C_FLAGS}) target_compile_options(uhjencoder PRIVATE ${C_FLAGS})
target_link_libraries(uhjencoder PUBLIC alcommon target_link_libraries(uhjencoder PUBLIC alsoft.common
PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG} alsoft::fmt)
set_target_properties(uhjencoder PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(uhjencoder PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
endif() endif()
if(MYSOFA_FOUND) if(MYSOFA_FOUND)
set(SOFA_SUPPORT_SRCS set(SOFA_SUPPORT_SRCS
utils/sofa-support.cpp utils/sofa-support.cpp
utils/sofa-support.h) utils/sofa-support.h)
add_library(sofa-support STATIC EXCLUDE_FROM_ALL ${SOFA_SUPPORT_SRCS}) add_library(alsoft.sofa-support STATIC EXCLUDE_FROM_ALL ${SOFA_SUPPORT_SRCS})
target_compile_definitions(sofa-support PRIVATE ${CPP_DEFS}) target_compile_definitions(alsoft.sofa-support PRIVATE ${CPP_DEFS})
target_include_directories(sofa-support PUBLIC ${OpenAL_SOURCE_DIR}/common) target_include_directories(alsoft.sofa-support PUBLIC ${OpenAL_SOURCE_DIR}/common)
target_compile_options(sofa-support PRIVATE ${C_FLAGS}) target_compile_options(alsoft.sofa-support PRIVATE ${C_FLAGS})
target_link_libraries(sofa-support PUBLIC alcommon MySOFA::MySOFA PRIVATE ${LINKER_FLAGS}) target_link_libraries(alsoft.sofa-support PUBLIC alsoft.common MySOFA::MySOFA
set_target_properties(sofa-support PROPERTIES ${DEFAULT_TARGET_PROPS}) PRIVATE ${LINKER_FLAGS} alsoft::fmt)
set_target_properties(alsoft.sofa-support PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
set(MAKEMHR_SRCS set(MAKEMHR_SRCS
utils/makemhr/loaddef.cpp utils/makemhr/loaddef.cpp
@ -1646,8 +1752,9 @@ if(ALSOFT_UTILS)
target_include_directories(makemhr target_include_directories(makemhr
PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/utils) PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/utils)
target_compile_options(makemhr PRIVATE ${C_FLAGS}) target_compile_options(makemhr PRIVATE ${C_FLAGS})
target_link_libraries(makemhr PRIVATE ${LINKER_FLAGS} sofa-support ${UNICODE_FLAG}) target_link_libraries(makemhr PRIVATE ${LINKER_FLAGS} alsoft.sofa-support ${UNICODE_FLAG}
set_target_properties(makemhr PROPERTIES ${DEFAULT_TARGET_PROPS}) alsoft::fmt)
set_target_properties(makemhr PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
if(ALSOFT_INSTALL_EXAMPLES) if(ALSOFT_INSTALL_EXAMPLES)
set(EXTRA_INSTALLS ${EXTRA_INSTALLS} makemhr) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} makemhr)
endif() endif()
@ -1657,8 +1764,9 @@ if(ALSOFT_UTILS)
target_compile_definitions(sofa-info PRIVATE ${CPP_DEFS}) target_compile_definitions(sofa-info PRIVATE ${CPP_DEFS})
target_include_directories(sofa-info PRIVATE ${OpenAL_SOURCE_DIR}/utils) target_include_directories(sofa-info PRIVATE ${OpenAL_SOURCE_DIR}/utils)
target_compile_options(sofa-info PRIVATE ${C_FLAGS}) target_compile_options(sofa-info PRIVATE ${C_FLAGS})
target_link_libraries(sofa-info PRIVATE ${LINKER_FLAGS} sofa-support ${UNICODE_FLAG}) target_link_libraries(sofa-info PRIVATE ${LINKER_FLAGS} alsoft.sofa-support
set_target_properties(sofa-info PROPERTIES ${DEFAULT_TARGET_PROPS}) ${UNICODE_FLAG} alsoft::fmt)
set_target_properties(sofa-info PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
endif() endif()
message(STATUS "Building utility programs") message(STATUS "Building utility programs")
@ -1670,75 +1778,86 @@ endif()
# Add a static library with common functions used by multiple example targets # Add a static library with common functions used by multiple example targets
add_library(al-excommon STATIC EXCLUDE_FROM_ALL add_library(alsoft.excommon STATIC EXCLUDE_FROM_ALL
examples/common/alhelpers.c examples/common/alhelpers.c
examples/common/alhelpers.h) examples/common/alhelpers.h)
target_compile_definitions(al-excommon PUBLIC ${CPP_DEFS}) target_compile_definitions(alsoft.excommon PUBLIC ${CPP_DEFS})
target_include_directories(al-excommon PUBLIC ${OpenAL_SOURCE_DIR}/common) target_include_directories(alsoft.excommon PUBLIC ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common)
target_compile_options(al-excommon PUBLIC ${C_FLAGS}) target_compile_options(alsoft.excommon PUBLIC ${C_FLAGS})
target_link_libraries(al-excommon PUBLIC OpenAL PRIVATE ${RT_LIB}) target_link_libraries(alsoft.excommon PUBLIC OpenAL PRIVATE ${RT_LIB})
set_target_properties(al-excommon PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(alsoft.excommon PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
if(ALSOFT_EXAMPLES) if(ALSOFT_EXAMPLES)
add_executable(altonegen examples/altonegen.c) add_executable(altonegen examples/altonegen.c)
target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} al-excommon ${UNICODE_FLAG}) target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} alsoft.excommon
set_target_properties(altonegen PROPERTIES ${DEFAULT_TARGET_PROPS}) ${UNICODE_FLAG})
set_target_properties(altonegen PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(alrecord examples/alrecord.c) add_executable(alrecord examples/alrecord.c)
target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} al-excommon ${UNICODE_FLAG}) target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} alsoft.excommon ${UNICODE_FLAG})
set_target_properties(alrecord PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(alrecord PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(aldebug examples/aldebug.cpp)
target_link_libraries(aldebug PRIVATE ${LINKER_FLAGS} alsoft.excommon ${UNICODE_FLAG}
alsoft::fmt)
set_target_properties(aldebug PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(allafplay examples/allafplay.cpp)
target_link_libraries(allafplay PRIVATE ${LINKER_FLAGS} alsoft.common alsoft.excommon
${UNICODE_FLAG} alsoft::fmt)
set_target_properties(allafplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
if(ALSOFT_INSTALL_EXAMPLES) if(ALSOFT_INSTALL_EXAMPLES)
set(EXTRA_INSTALLS ${EXTRA_INSTALLS} altonegen alrecord) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} altonegen alrecord aldebug allafplay)
endif() endif()
message(STATUS "Building example programs") message(STATUS "Building example programs")
if(SNDFILE_FOUND) if(SNDFILE_FOUND)
add_executable(alplay examples/alplay.c) add_executable(alplay examples/alplay.c)
target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon
${UNICODE_FLAG}) ${UNICODE_FLAG})
set_target_properties(alplay PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(alplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(alstream examples/alstream.c) add_executable(alstream examples/alstream.c)
target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon
${UNICODE_FLAG}) ${UNICODE_FLAG})
set_target_properties(alstream PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(alstream PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(alreverb examples/alreverb.c) add_executable(alreverb examples/alreverb.c)
target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon
${UNICODE_FLAG}) ${UNICODE_FLAG})
set_target_properties(alreverb PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(alreverb PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(almultireverb examples/almultireverb.c) add_executable(almultireverb examples/almultireverb.c)
target_link_libraries(almultireverb target_link_libraries(almultireverb
PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG}) PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${MATH_LIB} ${UNICODE_FLAG})
set_target_properties(almultireverb PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(almultireverb PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(allatency examples/allatency.c) add_executable(allatency examples/allatency.c)
target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon
${UNICODE_FLAG}) ${UNICODE_FLAG})
set_target_properties(allatency PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(allatency PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(alhrtf examples/alhrtf.c) add_executable(alhrtf examples/alhrtf.c)
target_link_libraries(alhrtf target_link_libraries(alhrtf
PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG}) PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${MATH_LIB} ${UNICODE_FLAG})
set_target_properties(alhrtf PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(alhrtf PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(alstreamcb examples/alstreamcb.cpp) add_executable(alstreamcb examples/alstreamcb.cpp)
target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon
${UNICODE_FLAG}) ${UNICODE_FLAG} alsoft::fmt)
set_target_properties(alstreamcb PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(alstreamcb PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(aldirect examples/aldirect.cpp) add_executable(aldirect examples/aldirect.cpp)
target_link_libraries(aldirect PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon target_link_libraries(aldirect PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon
${UNICODE_FLAG}) ${UNICODE_FLAG} alsoft::fmt)
set_target_properties(aldirect PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(aldirect PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
add_executable(alconvolve examples/alconvolve.c) add_executable(alconvolve examples/alconvolve.c)
target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} alcommon SndFile::SndFile target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} alsoft.common SndFile::SndFile
al-excommon ${UNICODE_FLAG}) alsoft.excommon ${UNICODE_FLAG})
set_target_properties(alconvolve PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(alconvolve PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
if(ALSOFT_INSTALL_EXAMPLES) if(ALSOFT_INSTALL_EXAMPLES)
set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alplay alstream alreverb almultireverb allatency set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alplay alstream alreverb almultireverb allatency
@ -1748,18 +1867,19 @@ if(ALSOFT_EXAMPLES)
message(STATUS "Building SndFile example programs") message(STATUS "Building SndFile example programs")
endif() endif()
if(SDL2_FOUND) # Can't safely use SDL3 and SDL2 together
if(SDL3_FOUND AND NOT HAVE_SDL2)
message(STATUS "Building SDL3 example programs")
add_executable(alloopback examples/alloopback.c) add_executable(alloopback examples/alloopback.c)
target_link_libraries(alloopback target_link_libraries(alloopback
PRIVATE ${LINKER_FLAGS} SDL2::SDL2 al-excommon ${MATH_LIB}) PRIVATE ${LINKER_FLAGS} SDL3::SDL3 alsoft.excommon ${MATH_LIB})
set_target_properties(alloopback PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(alloopback PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
if(ALSOFT_INSTALL_EXAMPLES) if(ALSOFT_INSTALL_EXAMPLES)
set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alloopback) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alloopback)
endif() endif()
message(STATUS "Building SDL example programs")
set(FFVER_OK FALSE) set(FFVER_OK FALSE)
if(FFMPEG_FOUND) if(FFMPEG_FOUND)
set(FFVER_OK TRUE) set(FFVER_OK TRUE)
@ -1788,19 +1908,19 @@ if(ALSOFT_EXAMPLES)
add_executable(alffplay examples/alffplay.cpp) add_executable(alffplay examples/alffplay.cpp)
target_include_directories(alffplay PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_include_directories(alffplay PRIVATE ${FFMPEG_INCLUDE_DIRS})
target_link_libraries(alffplay target_link_libraries(alffplay
PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ${FFMPEG_LIBRARIES} al-excommon) PRIVATE ${LINKER_FLAGS} SDL3::SDL3 ${FFMPEG_LIBRARIES} alsoft.excommon alsoft::fmt)
set_target_properties(alffplay PROPERTIES ${DEFAULT_TARGET_PROPS}) set_target_properties(alffplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS})
if(ALSOFT_INSTALL_EXAMPLES) if(ALSOFT_INSTALL_EXAMPLES)
set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alffplay) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alffplay)
endif() endif()
message(STATUS "Building SDL+FFmpeg example programs") message(STATUS "Building SDL3+FFmpeg example programs")
endif() endif()
endif() endif()
message(STATUS "") message(STATUS "")
endif() endif()
if (ALSOFT_TESTS) if(ALSOFT_TESTS)
add_subdirectory(tests) add_subdirectory(tests)
endif() endif()

View file

@ -1,3 +1,137 @@
openal-soft-1.24.3:
Fixed using as a static library when linked into another project that uses
fmtlib.
Fixed building with static VC runtimes.
Fixed building with Windows headers that default to older targets.
Fixed building on 32-bit targets that use 32-bit file offsets.
Fixed handling WASAPI enumerated device changes.
Fixed a crash with UWP builds when __wargv is null.
Fixed using AL_FORMAT_BFORMAT3D_I32.
Improved the bsinc resamplers' cutoff frequencies.
Slightly reduced the aliasing noise in the cubic spline resampler.
Added new bsinc48 and fast_bsinc48 resampler options.
Added support for 16KB page sizes on Android.
Added support for using NFC filters with UHJ output.
openal-soft-1.24.2:
Implemented the AL_SOFT_bformat_hoa extension.
Implemented default device change events for the PulseAudio backend.
Implemented an option for WASAPI exclusive mode playback.
Fixed reverb being too quiet for sounds from different directions.
Fixed compiling with certain versions of Clang.
Fixed compiling for some older macOS versions.
Fixed building alffplay on systems without pkg-config.
Improved output format detection for CoreAudio.
Changed the default resampler back to Cubic Spline.
Added an SDL3 playback backend. Disabled by default to avoid a runtime
dependency and for compatibility; a single process can't safely use SDL2
and SDL3 together on some OSs, so enable with care.
Converted examples from SDL2 to SDL3.
Integrated fmtlib into the main library and router for logging and string
formatting.
openal-soft-1.24.1:
Fixed compilation on PowerPC.
Fixed compilation on some targets that lack lock-free 64-bit atomics.
Fixed a crash when parsing certain option values.
Fixed applying noexcept in the public headers with MSVC.
Fixed building for UWP with vcpkg.
Improved compatibility when compiling as C++20 or later.
Integrated fmtlib for some examples and utilities.
openal-soft-1.24.0:
Updated library codebase to C++17.
Implemented the ALC_SOFT_system_events extension.
Implemented the AL_EXT_debug extension.
Implemented the AL_EXT_direct_context extension.
Implemented speaker configuration and headphones detection on CoreAudio.
Fixed a potential crash with some extension functions on 32-bit Windows.
Fixed a crash that can occur when stopping playback with the Oboe backend.
Fixed calculating the reverb room rolloff.
Fixed EAX occlusion, obstruction, and exclusion low-pass filter strength.
Fixed EAX distance factor calculations.
Fixed querying AL_EFFECTSLOT_EFFECT on auxiliary effect slots.
Fixed compilation on some macOS systems that lack libdispatch.
Fixed compilation as a subproject with MinGW.
Changed the context error state to be thread-local. This is technically out
of spec, but necessary to avoid race conditions with multi-threaded use.
Split the cubic resampler into 4-point spline and gaussian variants. The
latter prioritizing the suppression of aliasing distortion and harmonics,
the former not reducing high frequencies as much.
Improved timing precision of starting delayed sources.
Improved ring modulator quality.
Improved performance of convolution reverb.
Improved WASAPI device enumeration performance.
Added UWP support.
Added 'noexcept' to functions and function types when compiled as C++. As a
C API, OpenAL can't be expected to throw C++ exceptions, nor can it handle
them if they leave a callback.
Added an experimental config option for using WASAPI spatial audio output.
Added enumeration support to the PortAudio backend.
Added compatibility options to override the AL_VENDOR, AL_VERSION, and
AL_RENDERER strings.
Added an example to play LAF files.
Disabled real-time mixing by default for PipeWire playback.
Disabled the SndIO backend by default on non-BSD targets.
openal-soft-1.23.1: openal-soft-1.23.1:
Implemented the AL_SOFT_UHJ_ex extension. Implemented the AL_SOFT_UHJ_ex extension.

View file

@ -78,15 +78,26 @@ API, including some extensions. It also includes utility libraries for math and
linear algebra, which can be useful for 3D calculations. linear algebra, which can be useful for 3D calculations.
Java Bindings: Java Bindings:
* [LWJGL](https://github.com/LWJGL/lwjgl3), the Lightweight Java Game Library,
includes Java bindings for the OpenAL API, usable with OpenAL Soft.
* [JOAL](https://jogamp.org/joal/www/), part of the JogAmp project, includes * [JOAL](https://jogamp.org/joal/www/), part of the JogAmp project, includes
Java bindings for the OpenAL API, usable with OpenAL Soft. It also includes a Java bindings for the OpenAL API, usable with OpenAL Soft. It also includes a
higher level Sound3D Toolkit API and utility functions to make easier use of higher level Sound3D Toolkit API and utility functions to make easier use of
OpenAL features and capabilities. OpenAL features and capabilities.
Kotlin Bindings:
* [Multiplatform OpenAL](https://git.karmakrafts.dev/kk/multiplatform-openal), developed for the Kleaver project,
includes Kotlin/Native bindings for the OpenAL API, based on OpenAL Soft with support
for Windows, Linux, macOS, iOS and Android.
Python Bindings: Python Bindings:
* [PyOpenAL](https://pypi.org/project/PyOpenAL/). Also includes methods to play * [PyOpenAL](https://pypi.org/project/PyOpenAL/). Also includes methods to play
wave files and, with PyOgg, also Vorbis, Opus, and FLAC. wave files and, with PyOgg, also Vorbis, Opus, and FLAC.
FreePascal/Lazarus Bindings:
* [ALSound](https://github.com/Lulu04/ALSound). Also includes a higher level
API and libsndfile support to simplify loading and playing sounds.
Other bindings for these and other languages also exist. This list will grow as Other bindings for these and other languages also exist. This list will grow as
more bindings are found. more bindings are found.

View file

@ -31,7 +31,6 @@
#include <mutex> #include <mutex>
#include <numeric> #include <numeric>
#include <stdexcept> #include <stdexcept>
#include <string>
#include <tuple> #include <tuple>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -54,27 +53,27 @@
#include "buffer.h" #include "buffer.h"
#include "core/buffer_storage.h" #include "core/buffer_storage.h"
#include "core/device.h" #include "core/device.h"
#include "core/except.h"
#include "core/fpu_ctrl.h" #include "core/fpu_ctrl.h"
#include "core/logging.h" #include "core/logging.h"
#include "direct_defs.h" #include "direct_defs.h"
#include "effect.h" #include "effect.h"
#include "error.h"
#include "flexarray.h" #include "flexarray.h"
#include "opthelpers.h" #include "opthelpers.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "eax/api.h" #include "eax/api.h"
#include "eax/call.h" #include "eax/call.h"
#include "eax/effect.h" #include "eax/effect.h"
#include "eax/fx_slot_index.h" #include "eax/fx_slot_index.h"
#include "eax/utils.h"
#endif #endif
namespace { namespace {
using SubListAllocator = al::allocator<std::array<ALeffectslot,64>>; using SubListAllocator = al::allocator<std::array<ALeffectslot,64>>;
EffectStateFactory *getFactoryByType(EffectSlotType type) [[nodiscard]]
auto getFactoryByType(EffectSlotType type) -> EffectStateFactory*
{ {
switch(type) switch(type)
{ {
@ -98,6 +97,7 @@ EffectStateFactory *getFactoryByType(EffectSlotType type)
} }
[[nodiscard]]
auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot* auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot*
{ {
const size_t lidx{(id-1) >> 6}; const size_t lidx{(id-1) >> 6};
@ -111,7 +111,8 @@ auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot*
return al::to_address(sublist.EffectSlots->begin() + slidx); return al::to_address(sublist.EffectSlots->begin() + slidx);
} }
inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* [[nodiscard]]
inline auto LookupEffect(al::Device *device, ALuint id) noexcept -> ALeffect*
{ {
const size_t lidx{(id-1) >> 6}; const size_t lidx{(id-1) >> 6};
const ALuint slidx{(id-1) & 0x3f}; const ALuint slidx{(id-1) & 0x3f};
@ -124,7 +125,8 @@ inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect*
return al::to_address(sublist.Effects->begin() + slidx); return al::to_address(sublist.Effects->begin() + slidx);
} }
inline auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* [[nodiscard]]
inline auto LookupBuffer(al::Device *device, ALuint id) noexcept -> ALbuffer*
{ {
const size_t lidx{(id-1) >> 6}; const size_t lidx{(id-1) >> 6};
const ALuint slidx{(id-1) & 0x3f}; const ALuint slidx{(id-1) & 0x3f};
@ -217,6 +219,7 @@ void RemoveActiveEffectSlots(const al::span<ALeffectslot*> auxslots, ALCcontext
} }
[[nodiscard]]
constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType
{ {
switch(type) switch(type)
@ -239,10 +242,11 @@ constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType
case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::Dedicated; case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::Dedicated;
case AL_EFFECT_CONVOLUTION_SOFT: return EffectSlotType::Convolution; case AL_EFFECT_CONVOLUTION_SOFT: return EffectSlotType::Convolution;
} }
ERR("Unhandled effect enum: 0x%04x\n", type); ERR("Unhandled effect enum: {:#04x}", as_unsigned(type));
return EffectSlotType::None; return EffectSlotType::None;
} }
[[nodiscard]]
auto EnsureEffectSlots(ALCcontext *context, size_t needed) noexcept -> bool auto EnsureEffectSlots(ALCcontext *context, size_t needed) noexcept -> bool
try { try {
size_t count{std::accumulate(context->mEffectSlotList.cbegin(), size_t count{std::accumulate(context->mEffectSlotList.cbegin(),
@ -267,7 +271,8 @@ catch(...) {
return false; return false;
} }
ALeffectslot *AllocEffectSlot(ALCcontext *context) [[nodiscard]]
auto AllocEffectSlot(ALCcontext *context) -> ALeffectslot*
{ {
auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(), auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(),
[](const EffectSlotSubList &entry) noexcept -> bool [](const EffectSlotSubList &entry) noexcept -> bool
@ -322,20 +327,21 @@ FORCE_ALIGN void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context
ALuint *effectslots) noexcept ALuint *effectslots) noexcept
try { try {
if(n < 0) if(n < 0)
throw al::context_error{AL_INVALID_VALUE, "Generating %d effect slots", n}; context->throw_error(AL_INVALID_VALUE, "Generating {} effect slots", n);
if(n <= 0) UNLIKELY return; if(n <= 0) UNLIKELY return;
std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock}; auto slotlock = std::lock_guard{context->mEffectSlotLock};
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
const al::span eids{effectslots, static_cast<ALuint>(n)}; const al::span eids{effectslots, static_cast<ALuint>(n)};
if(eids.size() > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots) if(context->mNumEffectSlots > device->AuxiliaryEffectSlotMax
throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)", || eids.size() > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots)
device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n}; context->throw_error(AL_OUT_OF_MEMORY, "Exceeding {} effect slot limit ({} + {})",
device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n);
if(!EnsureEffectSlots(context, eids.size())) if(!EnsureEffectSlots(context, eids.size()))
throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n, context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} effectslot{}", n,
(n == 1) ? "" : "s"}; (n==1) ? "" : "s");
std::vector<ALeffectslot*> slots; std::vector<ALeffectslot*> slots;
try { try {
@ -355,16 +361,18 @@ try {
} }
} }
catch(std::exception& e) { catch(std::exception& e) {
ERR("Exception allocating effectslot %zu of %d: %s\n", slots.size()+1, n, e.what()); ERR("Exception allocating effectslot {} of {}: {}", slots.size()+1, n, e.what());
auto delete_effectslot = [context](ALeffectslot *slot) -> void auto delete_effectslot = [context](ALeffectslot *slot) -> void
{ FreeEffectSlot(context, slot); }; { FreeEffectSlot(context, slot); };
std::for_each(slots.begin(), slots.end(), delete_effectslot); std::for_each(slots.begin(), slots.end(), delete_effectslot);
throw al::context_error{AL_INVALID_OPERATION, "Exception allocating %d effectslots: %s", n, context->throw_error(AL_INVALID_OPERATION, "Exception allocating {} effectslots: {}", n,
e.what()}; e.what());
} }
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC2(void, alDeleteAuxiliaryEffectSlots, ALsizei,n, const ALuint*,effectslots) AL_API DECL_FUNC2(void, alDeleteAuxiliaryEffectSlots, ALsizei,n, const ALuint*,effectslots)
@ -372,7 +380,7 @@ FORCE_ALIGN void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *cont
const ALuint *effectslots) noexcept const ALuint *effectslots) noexcept
try { try {
if(n < 0) UNLIKELY if(n < 0) UNLIKELY
throw al::context_error{AL_INVALID_VALUE, "Deleting %d effect slots", n}; context->throw_error(AL_INVALID_VALUE, "Deleting {} effect slots", n);
if(n <= 0) UNLIKELY return; if(n <= 0) UNLIKELY return;
std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock}; std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
@ -380,10 +388,10 @@ try {
{ {
ALeffectslot *slot{LookupEffectSlot(context, *effectslots)}; ALeffectslot *slot{LookupEffectSlot(context, *effectslots)};
if(!slot) if(!slot)
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", *effectslots}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", *effectslots);
if(slot->ref.load(std::memory_order_relaxed) != 0) if(slot->ref.load(std::memory_order_relaxed) != 0)
throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u", context->throw_error(AL_INVALID_OPERATION, "Deleting in-use effect slot {}",
*effectslots}; *effectslots);
RemoveActiveEffectSlots({&slot, 1u}, context); RemoveActiveEffectSlots({&slot, 1u}, context);
FreeEffectSlot(context, slot); FreeEffectSlot(context, slot);
@ -398,10 +406,9 @@ try {
{ {
ALeffectslot *slot{LookupEffectSlot(context, eid)}; ALeffectslot *slot{LookupEffectSlot(context, eid)};
if(!slot) if(!slot)
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", eid}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", eid);
if(slot->ref.load(std::memory_order_relaxed) != 0) if(slot->ref.load(std::memory_order_relaxed) != 0)
throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u", context->throw_error(AL_INVALID_OPERATION, "Deleting in-use effect slot {}", eid);
eid};
return slot; return slot;
}; };
std::transform(eids.cbegin(), eids.cend(), std::back_inserter(slots), lookupslot); std::transform(eids.cbegin(), eids.cend(), std::back_inserter(slots), lookupslot);
@ -417,8 +424,10 @@ try {
std::for_each(eids.begin(), eids.end(), delete_effectslot); std::for_each(eids.begin(), eids.end(), delete_effectslot);
} }
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC1(ALboolean, alIsAuxiliaryEffectSlot, ALuint,effectslot) AL_API DECL_FUNC1(ALboolean, alIsAuxiliaryEffectSlot, ALuint,effectslot)
@ -436,7 +445,6 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint) noexcept
{ {
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if(!context) UNLIKELY return; if(!context) UNLIKELY return;
context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlaySOFT not supported"); context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlaySOFT not supported");
} }
@ -444,7 +452,6 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei, const ALuint*) n
{ {
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if(!context) UNLIKELY return; if(!context) UNLIKELY return;
context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlayvSOFT not supported"); context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlayvSOFT not supported");
} }
@ -452,7 +459,6 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint) noexcept
{ {
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if(!context) UNLIKELY return; if(!context) UNLIKELY return;
context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopSOFT not supported"); context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopSOFT not supported");
} }
@ -460,7 +466,6 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei, const ALuint*) n
{ {
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if(!context) UNLIKELY return; if(!context) UNLIKELY return;
context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopvSOFT not supported"); context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopvSOFT not supported");
} }
@ -474,7 +479,7 @@ try {
ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
if(!slot) UNLIKELY if(!slot) UNLIKELY
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot);
ALeffectslot *target{}; ALeffectslot *target{};
ALenum err{}; ALenum err{};
@ -482,20 +487,20 @@ try {
{ {
case AL_EFFECTSLOT_EFFECT: case AL_EFFECTSLOT_EFFECT:
{ {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
ALeffect *effect{value ? LookupEffect(device, static_cast<ALuint>(value)) : nullptr}; auto *effect = value ? LookupEffect(device, static_cast<ALuint>(value)) : nullptr;
if(effect) if(effect)
err = slot->initEffect(effect->id, effect->type, effect->Props, context); err = slot->initEffect(effect->id, effect->type, effect->Props, context);
else else
{ {
if(value != 0) if(value != 0)
throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %u", value}; context->throw_error(AL_INVALID_VALUE, "Invalid effect ID {}", value);
err = slot->initEffect(0, AL_EFFECT_NULL, EffectProps{}, context); err = slot->initEffect(0, AL_EFFECT_NULL, EffectProps{}, context);
} }
} }
if(err != AL_NO_ERROR) if(err != AL_NO_ERROR)
throw al::context_error{err, "Effect initialization failed"}; context->throw_error(err, "Effect initialization failed");
if(slot->mState == SlotState::Initial) UNLIKELY if(slot->mState == SlotState::Initial) UNLIKELY
{ {
@ -511,8 +516,7 @@ try {
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
if(!(value == AL_TRUE || value == AL_FALSE)) if(!(value == AL_TRUE || value == AL_FALSE))
throw al::context_error{AL_INVALID_VALUE, context->throw_error(AL_INVALID_VALUE, "Effect slot auxiliary send auto out of range");
"Effect slot auxiliary send auto out of range"};
if(!(slot->AuxSendAuto == !!value)) LIKELY if(!(slot->AuxSendAuto == !!value)) LIKELY
{ {
slot->AuxSendAuto = !!value; slot->AuxSendAuto = !!value;
@ -523,7 +527,7 @@ try {
case AL_EFFECTSLOT_TARGET_SOFT: case AL_EFFECTSLOT_TARGET_SOFT:
target = LookupEffectSlot(context, static_cast<ALuint>(value)); target = LookupEffectSlot(context, static_cast<ALuint>(value));
if(value && !target) if(value && !target)
throw al::context_error{AL_INVALID_VALUE, "Invalid effect slot target ID"}; context->throw_error(AL_INVALID_VALUE, "Invalid effect slot target ID {}", value);
if(slot->Target == target) UNLIKELY if(slot->Target == target) UNLIKELY
return; return;
if(target) if(target)
@ -532,9 +536,9 @@ try {
while(checker && checker != slot) while(checker && checker != slot)
checker = checker->Target; checker = checker->Target;
if(checker) if(checker)
throw al::context_error{AL_INVALID_OPERATION, context->throw_error(AL_INVALID_OPERATION,
"Setting target of effect slot ID %u to %u creates circular chain", slot->id, "Setting target of effect slot ID {} to {} creates circular chain", slot->id,
target->id}; target->id);
} }
if(ALeffectslot *oldtarget{slot->Target}) if(ALeffectslot *oldtarget{slot->Target})
@ -569,17 +573,17 @@ try {
assert(factory); assert(factory);
al::intrusive_ptr<EffectState> state{factory->create()}; al::intrusive_ptr<EffectState> state{factory->create()};
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
auto bufferlock = std::unique_lock{device->BufferLock}; auto bufferlock = std::unique_lock{device->BufferLock};
ALbuffer *buffer{}; ALbuffer *buffer{};
if(value) if(value)
{ {
buffer = LookupBuffer(device, static_cast<ALuint>(value)); buffer = LookupBuffer(device, static_cast<ALuint>(value));
if(!buffer) if(!buffer)
throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value}; context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", value);
if(buffer->mCallback) if(buffer->mCallback)
throw al::context_error{AL_INVALID_OPERATION, context->throw_error(AL_INVALID_OPERATION,
"Callback buffer not valid for effects"}; "Callback buffer not valid for effects");
IncrementRef(buffer->ref); IncrementRef(buffer->ref);
} }
@ -605,17 +609,17 @@ try {
} }
else else
{ {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
auto bufferlock = std::unique_lock{device->BufferLock}; auto bufferlock = std::unique_lock{device->BufferLock};
ALbuffer *buffer{}; ALbuffer *buffer{};
if(value) if(value)
{ {
buffer = LookupBuffer(device, static_cast<ALuint>(value)); buffer = LookupBuffer(device, static_cast<ALuint>(value));
if(!buffer) if(!buffer)
throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value}; context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", value);
if(buffer->mCallback) if(buffer->mCallback)
throw al::context_error{AL_INVALID_OPERATION, context->throw_error(AL_INVALID_OPERATION,
"Callback buffer not valid for effects"}; "Callback buffer not valid for effects");
IncrementRef(buffer->ref); IncrementRef(buffer->ref);
} }
@ -633,13 +637,16 @@ try {
return; return;
case AL_EFFECTSLOT_STATE_SOFT: case AL_EFFECTSLOT_STATE_SOFT:
throw al::context_error{AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"}; context->throw_error(AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only");
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, const ALint*,values) AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, const ALint*,values)
@ -660,16 +667,15 @@ try {
std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock}; std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
if(!slot) if(!slot)
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot);
switch(param) context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer-vector property {:#04x}",
{ as_unsigned(param));
}
throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
param};
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat,value) AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat,value)
@ -681,13 +687,13 @@ try {
ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
if(!slot) if(!slot)
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot);
switch(param) switch(param)
{ {
case AL_EFFECTSLOT_GAIN: case AL_EFFECTSLOT_GAIN:
if(!(value >= 0.0f && value <= 1.0f)) if(!(value >= 0.0f && value <= 1.0f))
throw al::context_error{AL_INVALID_VALUE, "Effect slot gain out of range"}; context->throw_error(AL_INVALID_VALUE, "Effect slot gain {} out of range", value);
if(!(slot->Gain == value)) LIKELY if(!(slot->Gain == value)) LIKELY
{ {
slot->Gain = value; slot->Gain = value;
@ -696,10 +702,13 @@ try {
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, const ALfloat*,values) AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, const ALfloat*,values)
@ -716,16 +725,15 @@ try {
std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock}; std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
if(!slot) if(!slot)
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot);
switch(param) context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float-vector property {:#04x}",
{ as_unsigned(param));
}
throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
param};
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
@ -736,7 +744,7 @@ try {
std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock}; std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
if(!slot) if(!slot)
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot);
switch(param) switch(param)
{ {
@ -767,10 +775,13 @@ try {
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, ALint*,values) AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, ALint*,values)
@ -791,16 +802,15 @@ try {
std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock}; std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
ALeffectslot *slot = LookupEffectSlot(context, effectslot); ALeffectslot *slot = LookupEffectSlot(context, effectslot);
if(!slot) if(!slot)
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot);
switch(param) context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer-vector property {:#04x}",
{ as_unsigned(param));
}
throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
param};
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat*,value) AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat*,value)
@ -810,19 +820,20 @@ try {
std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock}; std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
if(!slot) if(!slot)
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot);
switch(param) switch(param)
{ {
case AL_EFFECTSLOT_GAIN: case AL_EFFECTSLOT_GAIN: *value = slot->Gain; return;
*value = slot->Gain;
return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, ALfloat*,values) AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, ALfloat*,values)
@ -839,16 +850,15 @@ try {
std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock}; std::lock_guard<std::mutex> slotlock{context->mEffectSlotLock};
ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)};
if(!slot) if(!slot)
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot);
switch(param) context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float-vector property {:#04x}",
{ as_unsigned(param));
}
throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
param};
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
@ -890,12 +900,13 @@ ALenum ALeffectslot::initEffect(ALuint effectId, ALenum effectType, const Effect
EffectStateFactory *factory{getFactoryByType(newtype)}; EffectStateFactory *factory{getFactoryByType(newtype)};
if(!factory) if(!factory)
{ {
ERR("Failed to find factory for effect slot type %d\n", static_cast<int>(newtype)); ERR("Failed to find factory for effect slot type {}",
int{al::to_underlying(newtype)});
return AL_INVALID_ENUM; return AL_INVALID_ENUM;
} }
al::intrusive_ptr<EffectState> state{factory->create()}; al::intrusive_ptr<EffectState> state{factory->create()};
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
state->mOutTarget = device->Dry.Buffer; state->mOutTarget = device->Dry.Buffer;
{ {
FPUCtl mixer_mode{}; FPUCtl mixer_mode{};
@ -964,7 +975,7 @@ void ALeffectslot::SetName(ALCcontext* context, ALuint id, std::string_view name
auto slot = LookupEffectSlot(context, id); auto slot = LookupEffectSlot(context, id);
if(!slot) if(!slot)
throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id}; context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", id);
context->mEffectSlotNames.insert_or_assign(id, name); context->mEffectSlotNames.insert_or_assign(id, name);
} }
@ -1004,51 +1015,51 @@ EffectSlotSubList::~EffectSlotSubList()
EffectSlots = nullptr; EffectSlots = nullptr;
} }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
void ALeffectslot::eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index) void ALeffectslot::eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index)
{ {
if(index >= EAX_MAX_FXSLOTS) if(index >= EAX_MAX_FXSLOTS)
eax_fail("Index out of range."); eax_fail("Index out of range.");
eax_al_context_ = &al_context; mEaxALContext = &al_context;
eax_fx_slot_index_ = index; mEaxFXSlotIndex = index;
eax_fx_slot_set_defaults(); eax_fx_slot_set_defaults();
eax_effect_ = std::make_unique<EaxEffect>(); mEaxEffect = std::make_unique<EaxEffect>();
if(index == 0) eax_effect_->init<EaxReverbCommitter>(); if(index == 0) mEaxEffect->init<EaxReverbCommitter>();
else if(index == 1) eax_effect_->init<EaxChorusCommitter>(); else if(index == 1) mEaxEffect->init<EaxChorusCommitter>();
else eax_effect_->init<EaxNullCommitter>(); else mEaxEffect->init<EaxNullCommitter>();
} }
void ALeffectslot::eax_commit() void ALeffectslot::eax_commit()
{ {
if(eax_df_ != EaxDirtyFlags{}) if(mEaxDf.any())
{ {
auto df = EaxDirtyFlags{}; auto df = std::bitset<eax_dirty_bit_count>{};
switch(eax_version_) switch(mEaxVersion)
{ {
case 1: case 1:
case 2: case 2:
case 3: case 3:
eax5_fx_slot_commit(eax123_, df); eax5_fx_slot_commit(mEax123, df);
break; break;
case 4: case 4:
eax4_fx_slot_commit(df); eax4_fx_slot_commit(df);
break; break;
case 5: case 5:
eax5_fx_slot_commit(eax5_, df); eax5_fx_slot_commit(mEax5, df);
break; break;
} }
eax_df_ = EaxDirtyFlags{}; mEaxDf.reset();
if((df & eax_volume_dirty_bit) != EaxDirtyFlags{}) if(df.test(eax_volume_dirty_bit))
eax_fx_slot_set_volume(); eax_fx_slot_set_volume();
if((df & eax_flags_dirty_bit) != EaxDirtyFlags{}) if(df.test(eax_flags_dirty_bit))
eax_fx_slot_set_flags(); eax_fx_slot_set_flags();
} }
if(eax_effect_->commit(eax_version_)) if(mEaxEffect->commit(mEaxVersion))
eax_set_efx_slot_effect(*eax_effect_); eax_set_efx_slot_effect(*mEaxEffect);
} }
[[noreturn]] void ALeffectslot::eax_fail(const char* message) [[noreturn]] void ALeffectslot::eax_fail(const char* message)
@ -1111,7 +1122,7 @@ ALenum ALeffectslot::eax_get_efx_effect_type(const GUID& guid)
const GUID& ALeffectslot::eax_get_eax_default_effect_guid() const noexcept const GUID& ALeffectslot::eax_get_eax_default_effect_guid() const noexcept
{ {
switch(eax_fx_slot_index_) switch(mEaxFXSlotIndex)
{ {
case 0: return EAX_REVERB_EFFECT; case 0: return EAX_REVERB_EFFECT;
case 1: return EAX_CHORUS_EFFECT; case 1: return EAX_CHORUS_EFFECT;
@ -1124,7 +1135,7 @@ long ALeffectslot::eax_get_eax_default_lock() const noexcept
return eax4_fx_slot_is_legacy() ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED; return eax4_fx_slot_is_legacy() ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED;
} }
void ALeffectslot::eax4_fx_slot_set_defaults(Eax4Props& props) noexcept void ALeffectslot::eax4_fx_slot_set_defaults(EAX40FXSLOTPROPERTIES& props) noexcept
{ {
props.guidLoadEffect = eax_get_eax_default_effect_guid(); props.guidLoadEffect = eax_get_eax_default_effect_guid();
props.lVolume = EAXFXSLOT_DEFAULTVOLUME; props.lVolume = EAXFXSLOT_DEFAULTVOLUME;
@ -1132,7 +1143,7 @@ void ALeffectslot::eax4_fx_slot_set_defaults(Eax4Props& props) noexcept
props.ulFlags = EAX40FXSLOT_DEFAULTFLAGS; props.ulFlags = EAX40FXSLOT_DEFAULTFLAGS;
} }
void ALeffectslot::eax5_fx_slot_set_defaults(Eax5Props& props) noexcept void ALeffectslot::eax5_fx_slot_set_defaults(EAX50FXSLOTPROPERTIES& props) noexcept
{ {
props.guidLoadEffect = eax_get_eax_default_effect_guid(); props.guidLoadEffect = eax_get_eax_default_effect_guid();
props.lVolume = EAXFXSLOT_DEFAULTVOLUME; props.lVolume = EAXFXSLOT_DEFAULTVOLUME;
@ -1144,14 +1155,14 @@ void ALeffectslot::eax5_fx_slot_set_defaults(Eax5Props& props) noexcept
void ALeffectslot::eax_fx_slot_set_defaults() void ALeffectslot::eax_fx_slot_set_defaults()
{ {
eax5_fx_slot_set_defaults(eax123_.i); eax5_fx_slot_set_defaults(mEax123.i);
eax4_fx_slot_set_defaults(eax4_.i); eax4_fx_slot_set_defaults(mEax4.i);
eax5_fx_slot_set_defaults(eax5_.i); eax5_fx_slot_set_defaults(mEax5.i);
eax_ = eax5_.i; mEax = mEax5.i;
eax_df_ = EaxDirtyFlags{}; mEaxDf.reset();
} }
void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const EAX40FXSLOTPROPERTIES& props)
{ {
switch(call.get_property_id()) switch(call.get_property_id())
{ {
@ -1175,7 +1186,7 @@ void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props)
} }
} }
void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const EAX50FXSLOTPROPERTIES& props)
{ {
switch(call.get_property_id()) switch(call.get_property_id())
{ {
@ -1209,8 +1220,8 @@ void ALeffectslot::eax_fx_slot_get(const EaxCall& call) const
{ {
switch(call.get_version()) switch(call.get_version())
{ {
case 4: eax4_fx_slot_get(call, eax4_.i); break; case 4: eax4_fx_slot_get(call, mEax4.i); break;
case 5: eax5_fx_slot_get(call, eax5_.i); break; case 5: eax5_fx_slot_get(call, mEax5.i); break;
default: eax_fail_unknown_version(); default: eax_fail_unknown_version();
} }
} }
@ -1223,7 +1234,7 @@ bool ALeffectslot::eax_get(const EaxCall& call)
eax_fx_slot_get(call); eax_fx_slot_get(call);
break; break;
case EaxCallPropertySetId::fx_slot_effect: case EaxCallPropertySetId::fx_slot_effect:
eax_effect_->get(call); mEaxEffect->get(call);
break; break;
default: default:
eax_fail_unknown_property_id(); eax_fail_unknown_property_id();
@ -1236,19 +1247,19 @@ void ALeffectslot::eax_fx_slot_load_effect(int version, ALenum altype)
{ {
if(!IsValidEffectType(altype)) if(!IsValidEffectType(altype))
altype = AL_EFFECT_NULL; altype = AL_EFFECT_NULL;
eax_effect_->set_defaults(version, altype); mEaxEffect->set_defaults(version, altype);
} }
void ALeffectslot::eax_fx_slot_set_volume() void ALeffectslot::eax_fx_slot_set_volume()
{ {
const auto volume = std::clamp(eax_.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); const auto volume = std::clamp(mEax.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME);
const auto gain = level_mb_to_gain(static_cast<float>(volume)); const auto gain = level_mb_to_gain(static_cast<float>(volume));
eax_set_efx_slot_gain(gain); eax_set_efx_slot_gain(gain);
} }
void ALeffectslot::eax_fx_slot_set_environment_flag() void ALeffectslot::eax_fx_slot_set_environment_flag()
{ {
eax_set_efx_slot_send_auto((eax_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u); eax_set_efx_slot_send_auto((mEax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u);
} }
void ALeffectslot::eax_fx_slot_set_flags() void ALeffectslot::eax_fx_slot_set_flags()
@ -1261,11 +1272,11 @@ void ALeffectslot::eax4_fx_slot_set_all(const EaxCall& call)
eax4_fx_slot_ensure_unlocked(); eax4_fx_slot_ensure_unlocked();
const auto& src = call.get_value<Exception, const EAX40FXSLOTPROPERTIES>(); const auto& src = call.get_value<Exception, const EAX40FXSLOTPROPERTIES>();
Eax4AllValidator{}(src); Eax4AllValidator{}(src);
auto& dst = eax4_.i; auto& dst = mEax4.i;
eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect. mEaxDf.set(eax_load_effect_dirty_bit); // Always reset the effect.
eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{}); if(dst.lVolume != src.lVolume) mEaxDf.set(eax_volume_dirty_bit);
eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{}); if(dst.lLock != src.lLock) mEaxDf.set(eax_lock_dirty_bit);
eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{}); if(dst.ulFlags != src.ulFlags) mEaxDf.set(eax_flags_dirty_bit);
dst = src; dst = src;
} }
@ -1273,30 +1284,30 @@ void ALeffectslot::eax5_fx_slot_set_all(const EaxCall& call)
{ {
const auto& src = call.get_value<Exception, const EAX50FXSLOTPROPERTIES>(); const auto& src = call.get_value<Exception, const EAX50FXSLOTPROPERTIES>();
Eax5AllValidator{}(src); Eax5AllValidator{}(src);
auto& dst = eax5_.i; auto& dst = mEax5.i;
eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect. mEaxDf.set(eax_load_effect_dirty_bit); // Always reset the effect.
eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{}); if(dst.lVolume != src.lVolume) mEaxDf.set(eax_volume_dirty_bit);
eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{}); if(dst.lLock != src.lLock) mEaxDf.set(eax_lock_dirty_bit);
eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{}); if(dst.ulFlags != src.ulFlags) mEaxDf.set(eax_flags_dirty_bit);
eax_df_ |= (dst.lOcclusion != src.lOcclusion ? eax_flags_dirty_bit : EaxDirtyFlags{}); if(dst.lOcclusion != src.lOcclusion) mEaxDf.set(eax_flags_dirty_bit);
eax_df_ |= (dst.flOcclusionLFRatio != src.flOcclusionLFRatio ? eax_flags_dirty_bit : EaxDirtyFlags{}); if(dst.flOcclusionLFRatio != src.flOcclusionLFRatio) mEaxDf.set(eax_flags_dirty_bit);
dst = src; dst = src;
} }
bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept
{ {
static constexpr auto dirty_bits = static constexpr auto dirty_bits = std::bitset<eax_dirty_bit_count>{
eax_occlusion_dirty_bit | (1u << eax_occlusion_dirty_bit)
eax_occlusion_lf_ratio_dirty_bit | | (1u << eax_occlusion_lf_ratio_dirty_bit)
eax_flags_dirty_bit; | (1u << eax_flags_dirty_bit)
};
return (eax_df_ & dirty_bits) != EaxDirtyFlags{}; return (mEaxDf & dirty_bits).any();
} }
// Returns `true` if all sources should be updated, or `false` otherwise. // Returns `true` if all sources should be updated, or `false` otherwise.
bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call) bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call)
{ {
auto& dst = eax4_.i; auto& dst = mEax4.i;
switch(call.get_property_id()) switch(call.get_property_id())
{ {
@ -1304,24 +1315,25 @@ bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call)
break; break;
case EAXFXSLOT_ALLPARAMETERS: case EAXFXSLOT_ALLPARAMETERS:
eax4_fx_slot_set_all(call); eax4_fx_slot_set_all(call);
if((eax_df_ & eax_load_effect_dirty_bit)) if(mEaxDf.test(eax_load_effect_dirty_bit))
eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect)); eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect));
break; break;
case EAXFXSLOT_LOADEFFECT: case EAXFXSLOT_LOADEFFECT:
eax4_fx_slot_ensure_unlocked(); eax4_fx_slot_ensure_unlocked();
eax_fx_slot_set_dirty<Eax4GuidLoadEffectValidator, eax_load_effect_dirty_bit>(call, dst.guidLoadEffect, eax_df_); eax_fx_slot_set_dirty<Eax4GuidLoadEffectValidator, eax_load_effect_dirty_bit>(call,
if((eax_df_ & eax_load_effect_dirty_bit)) dst.guidLoadEffect, mEaxDf);
if(mEaxDf.test(eax_load_effect_dirty_bit))
eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect)); eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect));
break; break;
case EAXFXSLOT_VOLUME: case EAXFXSLOT_VOLUME:
eax_fx_slot_set<Eax4VolumeValidator, eax_volume_dirty_bit>(call, dst.lVolume, eax_df_); eax_fx_slot_set<Eax4VolumeValidator, eax_volume_dirty_bit>(call, dst.lVolume, mEaxDf);
break; break;
case EAXFXSLOT_LOCK: case EAXFXSLOT_LOCK:
eax4_fx_slot_ensure_unlocked(); eax4_fx_slot_ensure_unlocked();
eax_fx_slot_set<Eax4LockValidator, eax_lock_dirty_bit>(call, dst.lLock, eax_df_); eax_fx_slot_set<Eax4LockValidator, eax_lock_dirty_bit>(call, dst.lLock, mEaxDf);
break; break;
case EAXFXSLOT_FLAGS: case EAXFXSLOT_FLAGS:
eax_fx_slot_set<Eax4FlagsValidator, eax_flags_dirty_bit>(call, dst.ulFlags, eax_df_); eax_fx_slot_set<Eax4FlagsValidator, eax_flags_dirty_bit>(call, dst.ulFlags, mEaxDf);
break; break;
default: default:
eax_fail_unknown_property_id(); eax_fail_unknown_property_id();
@ -1333,7 +1345,7 @@ bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call)
// Returns `true` if all sources should be updated, or `false` otherwise. // Returns `true` if all sources should be updated, or `false` otherwise.
bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call) bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call)
{ {
auto& dst = eax5_.i; auto& dst = mEax5.i;
switch(call.get_property_id()) switch(call.get_property_id())
{ {
@ -1341,28 +1353,31 @@ bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call)
break; break;
case EAXFXSLOT_ALLPARAMETERS: case EAXFXSLOT_ALLPARAMETERS:
eax5_fx_slot_set_all(call); eax5_fx_slot_set_all(call);
if((eax_df_ & eax_load_effect_dirty_bit)) if(mEaxDf.test(eax_load_effect_dirty_bit))
eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect)); eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect));
break; break;
case EAXFXSLOT_LOADEFFECT: case EAXFXSLOT_LOADEFFECT:
eax_fx_slot_set_dirty<Eax4GuidLoadEffectValidator, eax_load_effect_dirty_bit>(call, dst.guidLoadEffect, eax_df_); eax_fx_slot_set_dirty<Eax4GuidLoadEffectValidator, eax_load_effect_dirty_bit>(call,
if((eax_df_ & eax_load_effect_dirty_bit)) dst.guidLoadEffect, mEaxDf);
if(mEaxDf.test(eax_load_effect_dirty_bit))
eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect)); eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect));
break; break;
case EAXFXSLOT_VOLUME: case EAXFXSLOT_VOLUME:
eax_fx_slot_set<Eax4VolumeValidator, eax_volume_dirty_bit>(call, dst.lVolume, eax_df_); eax_fx_slot_set<Eax4VolumeValidator, eax_volume_dirty_bit>(call, dst.lVolume, mEaxDf);
break; break;
case EAXFXSLOT_LOCK: case EAXFXSLOT_LOCK:
eax_fx_slot_set<Eax4LockValidator, eax_lock_dirty_bit>(call, dst.lLock, eax_df_); eax_fx_slot_set<Eax4LockValidator, eax_lock_dirty_bit>(call, dst.lLock, mEaxDf);
break; break;
case EAXFXSLOT_FLAGS: case EAXFXSLOT_FLAGS:
eax_fx_slot_set<Eax5FlagsValidator, eax_flags_dirty_bit>(call, dst.ulFlags, eax_df_); eax_fx_slot_set<Eax5FlagsValidator, eax_flags_dirty_bit>(call, dst.ulFlags, mEaxDf);
break; break;
case EAXFXSLOT_OCCLUSION: case EAXFXSLOT_OCCLUSION:
eax_fx_slot_set<Eax5OcclusionValidator, eax_occlusion_dirty_bit>(call, dst.lOcclusion, eax_df_); eax_fx_slot_set<Eax5OcclusionValidator, eax_occlusion_dirty_bit>(call, dst.lOcclusion,
mEaxDf);
break; break;
case EAXFXSLOT_OCCLUSIONLFRATIO: case EAXFXSLOT_OCCLUSIONLFRATIO:
eax_fx_slot_set<Eax5OcclusionLfRatioValidator, eax_occlusion_lf_ratio_dirty_bit>(call, dst.flOcclusionLFRatio, eax_df_); eax_fx_slot_set<Eax5OcclusionLfRatioValidator, eax_occlusion_lf_ratio_dirty_bit>(call,
dst.flOcclusionLFRatio, mEaxDf);
break; break;
default: default:
eax_fail_unknown_property_id(); eax_fail_unknown_property_id();
@ -1374,7 +1389,7 @@ bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call)
// Returns `true` if all sources should be updated, or `false` otherwise. // Returns `true` if all sources should be updated, or `false` otherwise.
bool ALeffectslot::eax_fx_slot_set(const EaxCall& call) bool ALeffectslot::eax_fx_slot_set(const EaxCall& call)
{ {
switch (call.get_version()) switch(call.get_version())
{ {
case 4: return eax4_fx_slot_set(call); case 4: return eax4_fx_slot_set(call);
case 5: return eax5_fx_slot_set(call); case 5: return eax5_fx_slot_set(call);
@ -1390,39 +1405,41 @@ bool ALeffectslot::eax_set(const EaxCall& call)
switch(call.get_property_set_id()) switch(call.get_property_set_id())
{ {
case EaxCallPropertySetId::fx_slot: ret = eax_fx_slot_set(call); break; case EaxCallPropertySetId::fx_slot: ret = eax_fx_slot_set(call); break;
case EaxCallPropertySetId::fx_slot_effect: eax_effect_->set(call); break; case EaxCallPropertySetId::fx_slot_effect: mEaxEffect->set(call); break;
default: eax_fail_unknown_property_id(); default: eax_fail_unknown_property_id();
} }
const auto version = call.get_version(); const auto version = call.get_version();
if(eax_version_ != version) if(mEaxVersion != version)
eax_df_ = ~EaxDirtyFlags{}; mEaxDf.set();
eax_version_ = version; mEaxVersion = version;
return ret; return ret;
} }
void ALeffectslot::eax4_fx_slot_commit(EaxDirtyFlags& dst_df) void ALeffectslot::eax4_fx_slot_commit(std::bitset<eax_dirty_bit_count>& dst_df)
{ {
eax_fx_slot_commit_property<eax_load_effect_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::guidLoadEffect); eax_fx_slot_commit_property<eax_load_effect_dirty_bit>(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::guidLoadEffect);
eax_fx_slot_commit_property<eax_volume_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lVolume); eax_fx_slot_commit_property<eax_volume_dirty_bit>(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::lVolume);
eax_fx_slot_commit_property<eax_lock_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lLock); eax_fx_slot_commit_property<eax_lock_dirty_bit>(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::lLock);
eax_fx_slot_commit_property<eax_flags_dirty_bit>(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::ulFlags); eax_fx_slot_commit_property<eax_flags_dirty_bit>(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::ulFlags);
auto& dst_i = eax_; auto& dst_i = mEax;
if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION) { if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION)
dst_df |= eax_occlusion_dirty_bit; {
dst_df.set(eax_occlusion_dirty_bit);
dst_i.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; dst_i.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION;
} }
if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO) { if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO)
dst_df |= eax_occlusion_lf_ratio_dirty_bit; {
dst_df.set(eax_occlusion_lf_ratio_dirty_bit);
dst_i.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; dst_i.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO;
} }
} }
void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df) void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, std::bitset<eax_dirty_bit_count>& dst_df)
{ {
eax_fx_slot_commit_property<eax_load_effect_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::guidLoadEffect); eax_fx_slot_commit_property<eax_load_effect_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::guidLoadEffect);
eax_fx_slot_commit_property<eax_volume_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::lVolume); eax_fx_slot_commit_property<eax_volume_dirty_bit>(state, dst_df, &EAX50FXSLOTPROPERTIES::lVolume);
@ -1437,18 +1454,19 @@ void ALeffectslot::eax_set_efx_slot_effect(EaxEffect &effect)
#define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] " #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] "
const auto error = initEffect(0, effect.al_effect_type_, effect.al_effect_props_, const auto error = initEffect(0, effect.al_effect_type_, effect.al_effect_props_,
eax_al_context_); mEaxALContext);
if(error != AL_NO_ERROR)
if(error != AL_NO_ERROR) { {
ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect."); ERR(EAX_PREFIX "Failed to initialize an effect.");
return; return;
} }
if(mState == SlotState::Initial) { if(mState == SlotState::Initial)
{
mPropsDirty = false; mPropsDirty = false;
updateProps(eax_al_context_); updateProps(mEaxALContext);
auto effect_slot_ptr = this; auto effect_slot_ptr = this;
AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_); AddActiveEffectSlots({&effect_slot_ptr, 1}, mEaxALContext);
mState = SlotState::Playing; mState = SlotState::Playing;
return; return;
} }
@ -1474,7 +1492,7 @@ void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain)
if(gain == Gain) if(gain == Gain)
return; return;
if(gain < 0.0f || gain > 1.0f) if(gain < 0.0f || gain > 1.0f)
ERR(EAX_PREFIX "Gain out of range (%f)\n", gain); ERR(EAX_PREFIX "Slot gain out of range ({:f})", gain);
Gain = std::clamp(gain, 0.0f, 1.0f); Gain = std::clamp(gain, 0.0f, 1.0f);
mPropsDirty = true; mPropsDirty = true;
@ -1484,7 +1502,7 @@ void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain)
void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot) void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot)
{ {
eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot); eax_delete_al_effect_slot(*effect_slot->mEaxALContext, *effect_slot);
} }
EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context) EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context)
@ -1494,13 +1512,15 @@ EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context)
std::lock_guard<std::mutex> slotlock{context.mEffectSlotLock}; std::lock_guard<std::mutex> slotlock{context.mEffectSlotLock};
auto& device = *context.mALDevice; auto& device = *context.mALDevice;
if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) { if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax)
ERR(EAX_PREFIX "%s\n", "Out of memory."); {
ERR(EAX_PREFIX "Out of memory.");
return nullptr; return nullptr;
} }
if(!EnsureEffectSlots(&context, 1)) { if(!EnsureEffectSlots(&context, 1))
ERR(EAX_PREFIX "%s\n", "Failed to ensure."); {
ERR(EAX_PREFIX "Failed to ensure.");
return nullptr; return nullptr;
} }
@ -1517,7 +1537,7 @@ void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot)
if(effect_slot.ref.load(std::memory_order_relaxed) != 0) if(effect_slot.ref.load(std::memory_order_relaxed) != 0)
{ {
ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id); ERR(EAX_PREFIX "Deleting in-use effect slot {}.", effect_slot.id);
return; return;
} }

View file

@ -1,8 +1,11 @@
#ifndef AL_AUXEFFECTSLOT_H #ifndef AL_AUXEFFECTSLOT_H
#define AL_AUXEFFECTSLOT_H #define AL_AUXEFFECTSLOT_H
#include "config.h"
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <bitset>
#include <cstdint> #include <cstdint>
#include <string_view> #include <string_view>
#include <utility> #include <utility>
@ -16,7 +19,7 @@
#include "core/effectslot.h" #include "core/effectslot.h"
#include "intrusive_ptr.h" #include "intrusive_ptr.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include <memory> #include <memory>
#include "eax/api.h" #include "eax/api.h"
#include "eax/call.h" #include "eax/call.h"
@ -28,7 +31,7 @@
struct ALbuffer; struct ALbuffer;
#ifdef ALSOFT_EAX #if ALSOFT_EAX
class EaxFxSlotException : public EaxException { class EaxFxSlotException : public EaxException {
public: public:
explicit EaxFxSlotException(const char* message) explicit EaxFxSlotException(const char* message)
@ -50,7 +53,7 @@ struct ALeffectslot {
struct EffectData { struct EffectData {
EffectSlotType Type{EffectSlotType::None}; EffectSlotType Type{EffectSlotType::None};
EffectProps Props{}; EffectProps Props;
al::intrusive_ptr<EffectState> State; al::intrusive_ptr<EffectState> State;
}; };
@ -67,7 +70,7 @@ struct ALeffectslot {
/* Self ID */ /* Self ID */
ALuint id{}; ALuint id{};
ALeffectslot(ALCcontext *context); explicit ALeffectslot(ALCcontext *context);
ALeffectslot(const ALeffectslot&) = delete; ALeffectslot(const ALeffectslot&) = delete;
ALeffectslot& operator=(const ALeffectslot&) = delete; ALeffectslot& operator=(const ALeffectslot&) = delete;
~ALeffectslot(); ~ALeffectslot();
@ -79,13 +82,14 @@ struct ALeffectslot {
static void SetName(ALCcontext *context, ALuint id, std::string_view name); static void SetName(ALCcontext *context, ALuint id, std::string_view name);
#ifdef ALSOFT_EAX #if ALSOFT_EAX
public: public:
void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index); void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index);
[[nodiscard]] auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return eax_fx_slot_index_; } [[nodiscard]]
[[nodiscard]] auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES& auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return mEaxFXSlotIndex; }
{ return eax_; } [[nodiscard]]
auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES& { return mEax; }
// Returns `true` if all sources should be updated, or `false` otherwise. // Returns `true` if all sources should be updated, or `false` otherwise.
[[nodiscard]] auto eax_dispatch(const EaxCall& call) -> bool [[nodiscard]] auto eax_dispatch(const EaxCall& call) -> bool
@ -94,25 +98,24 @@ public:
void eax_commit(); void eax_commit();
private: private:
static constexpr auto eax_load_effect_dirty_bit = EaxDirtyFlags{1} << 0; enum {
static constexpr auto eax_volume_dirty_bit = EaxDirtyFlags{1} << 1; eax_load_effect_dirty_bit,
static constexpr auto eax_lock_dirty_bit = EaxDirtyFlags{1} << 2; eax_volume_dirty_bit,
static constexpr auto eax_flags_dirty_bit = EaxDirtyFlags{1} << 3; eax_lock_dirty_bit,
static constexpr auto eax_occlusion_dirty_bit = EaxDirtyFlags{1} << 4; eax_flags_dirty_bit,
static constexpr auto eax_occlusion_lf_ratio_dirty_bit = EaxDirtyFlags{1} << 5; eax_occlusion_dirty_bit,
eax_occlusion_lf_ratio_dirty_bit,
eax_dirty_bit_count
};
using Exception = EaxFxSlotException; using Exception = EaxFxSlotException;
using Eax4Props = EAX40FXSLOTPROPERTIES;
struct Eax4State { struct Eax4State {
Eax4Props i; // Immediate. EAX40FXSLOTPROPERTIES i; // Immediate.
}; };
using Eax5Props = EAX50FXSLOTPROPERTIES;
struct Eax5State { struct Eax5State {
Eax5Props i; // Immediate. EAX50FXSLOTPROPERTIES i; // Immediate.
}; };
struct EaxRangeValidator { struct EaxRangeValidator {
@ -237,15 +240,15 @@ private:
} }
}; };
ALCcontext* eax_al_context_{}; ALCcontext* mEaxALContext{};
EaxFxSlotIndexValue eax_fx_slot_index_{}; EaxFxSlotIndexValue mEaxFXSlotIndex{};
int eax_version_{}; // Current EAX version. int mEaxVersion{}; // Current EAX version.
EaxDirtyFlags eax_df_{}; // Dirty flags for the current EAX version. std::bitset<eax_dirty_bit_count> mEaxDf; // Dirty flags for the current EAX version.
EaxEffectUPtr eax_effect_{}; EaxEffectUPtr mEaxEffect;
Eax5State eax123_{}; // EAX1/EAX2/EAX3 state. Eax5State mEax123{}; // EAX1/EAX2/EAX3 state.
Eax4State eax4_{}; // EAX4 state. Eax4State mEax4{}; // EAX4 state.
Eax5State eax5_{}; // EAX5 state. Eax5State mEax5{}; // EAX5 state.
Eax5Props eax_{}; // Current EAX state. EAX50FXSLOTPROPERTIES mEax{}; // Current EAX state.
[[noreturn]] static void eax_fail(const char* message); [[noreturn]] static void eax_fail(const char* message);
[[noreturn]] static void eax_fail_unknown_effect_id(); [[noreturn]] static void eax_fail_unknown_effect_id();
@ -256,12 +259,14 @@ private:
// validates it, // validates it,
// sets a dirty flag only if the new value differs form the old one, // sets a dirty flag only if the new value differs form the old one,
// and assigns the new value. // and assigns the new value.
template<typename TValidator, EaxDirtyFlags TDirtyBit, typename TProperties> template<typename TValidator, size_t DirtyBit, typename TProperties>
static void eax_fx_slot_set(const EaxCall& call, TProperties& dst, EaxDirtyFlags& dirty_flags) static void eax_fx_slot_set(const EaxCall& call, TProperties& dst,
std::bitset<eax_dirty_bit_count>& dirty_flags)
{ {
const auto& src = call.get_value<Exception, const TProperties>(); const auto& src = call.get_value<Exception, const TProperties>();
TValidator{}(src); TValidator{}(src);
dirty_flags |= (dst != src ? TDirtyBit : EaxDirtyFlags{}); if(dst != src)
dirty_flags.set(DirtyBit);
dst = src; dst = src;
} }
@ -269,18 +274,18 @@ private:
// validates it, // validates it,
// sets a dirty flag without comparing the values, // sets a dirty flag without comparing the values,
// and assigns the new value. // and assigns the new value.
template<typename TValidator, EaxDirtyFlags TDirtyBit, typename TProperties> template<typename TValidator, size_t DirtyBit, typename TProperties>
static void eax_fx_slot_set_dirty(const EaxCall& call, TProperties& dst, static void eax_fx_slot_set_dirty(const EaxCall& call, TProperties& dst,
EaxDirtyFlags& dirty_flags) std::bitset<eax_dirty_bit_count>& dirty_flags)
{ {
const auto& src = call.get_value<Exception, const TProperties>(); const auto& src = call.get_value<Exception, const TProperties>();
TValidator{}(src); TValidator{}(src);
dirty_flags |= TDirtyBit; dirty_flags.set(DirtyBit);
dst = src; dst = src;
} }
[[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool [[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool
{ return eax_fx_slot_index_ < 2; } { return mEaxFXSlotIndex < 2; }
void eax4_fx_slot_ensure_unlocked() const; void eax4_fx_slot_ensure_unlocked() const;
@ -288,15 +293,15 @@ private:
[[nodiscard]] auto eax_get_eax_default_effect_guid() const noexcept -> const GUID&; [[nodiscard]] auto eax_get_eax_default_effect_guid() const noexcept -> const GUID&;
[[nodiscard]] auto eax_get_eax_default_lock() const noexcept -> long; [[nodiscard]] auto eax_get_eax_default_lock() const noexcept -> long;
void eax4_fx_slot_set_defaults(Eax4Props& props) noexcept; void eax4_fx_slot_set_defaults(EAX40FXSLOTPROPERTIES& props) noexcept;
void eax5_fx_slot_set_defaults(Eax5Props& props) noexcept; void eax5_fx_slot_set_defaults(EAX50FXSLOTPROPERTIES& props) noexcept;
void eax4_fx_slot_set_current_defaults(const Eax4Props& props) noexcept; void eax4_fx_slot_set_current_defaults(const EAX40FXSLOTPROPERTIES& props) noexcept;
void eax5_fx_slot_set_current_defaults(const Eax5Props& props) noexcept; void eax5_fx_slot_set_current_defaults(const EAX50FXSLOTPROPERTIES& props) noexcept;
void eax_fx_slot_set_current_defaults(); void eax_fx_slot_set_current_defaults();
void eax_fx_slot_set_defaults(); void eax_fx_slot_set_defaults();
static void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props); static void eax4_fx_slot_get(const EaxCall& call, const EAX40FXSLOTPROPERTIES& props);
static void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props); static void eax5_fx_slot_get(const EaxCall& call, const EAX50FXSLOTPROPERTIES& props);
void eax_fx_slot_get(const EaxCall& call) const; void eax_fx_slot_get(const EaxCall& call) const;
// Returns `true` if all sources should be updated, or `false` otherwise. // Returns `true` if all sources should be updated, or `false` otherwise.
bool eax_get(const EaxCall& call); bool eax_get(const EaxCall& call);
@ -321,25 +326,25 @@ private:
bool eax_set(const EaxCall& call); bool eax_set(const EaxCall& call);
template< template<
EaxDirtyFlags TDirtyBit, size_t DirtyBit,
typename TMemberResult, typename TMemberResult,
typename TProps, typename TProps,
typename TState> typename TState>
void eax_fx_slot_commit_property(TState& state, EaxDirtyFlags& dst_df, void eax_fx_slot_commit_property(TState& state, std::bitset<eax_dirty_bit_count>& dst_df,
TMemberResult TProps::*member) noexcept TMemberResult TProps::*member) noexcept
{ {
auto& src_i = state.i; auto& src_i = state.i;
auto& dst_i = eax_; auto& dst_i = mEax;
if((eax_df_ & TDirtyBit) != EaxDirtyFlags{}) if(mEaxDf.test(DirtyBit))
{ {
dst_df |= TDirtyBit; dst_df.set(DirtyBit);
dst_i.*member = src_i.*member; dst_i.*member = src_i.*member;
} }
} }
void eax4_fx_slot_commit(EaxDirtyFlags& dst_df); void eax4_fx_slot_commit(std::bitset<eax_dirty_bit_count>& dst_df);
void eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df); void eax5_fx_slot_commit(Eax5State& state, std::bitset<eax_dirty_bit_count>& dst_df);
// `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)` // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)`
void eax_set_efx_slot_effect(EaxEffect &effect); void eax_set_efx_slot_effect(EaxEffect &effect);
@ -360,7 +365,7 @@ public:
void UpdateAllEffectSlotProps(ALCcontext *context); void UpdateAllEffectSlotProps(ALCcontext *context);
#ifdef ALSOFT_EAX #if ALSOFT_EAX
using EaxAlEffectSlotUPtr = std::unique_ptr<ALeffectslot, ALeffectslot::EaxDeleter>; using EaxAlEffectSlotUPtr = std::unique_ptr<ALeffectslot, ALeffectslot::EaxDeleter>;
EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context); EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context);

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,8 @@
#ifndef AL_BUFFER_H #ifndef AL_BUFFER_H
#define AL_BUFFER_H #define AL_BUFFER_H
#include "config.h"
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <cstddef> #include <cstddef>
@ -17,7 +19,7 @@
#include "core/buffer_storage.h" #include "core/buffer_storage.h"
#include "vector.h" #include "vector.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
enum class EaxStorage : uint8_t { enum class EaxStorage : uint8_t {
Automatic, Automatic,
Accessible, Accessible,
@ -54,7 +56,7 @@ struct ALbuffer : public BufferStorage {
DISABLE_ALLOC DISABLE_ALLOC
#ifdef ALSOFT_EAX #if ALSOFT_EAX
EaxStorage eax_x_ram_mode{EaxStorage::Automatic}; EaxStorage eax_x_ram_mode{EaxStorage::Automatic};
bool eax_x_ram_is_hardware{}; bool eax_x_ram_is_hardware{};
#endif // ALSOFT_EAX #endif // ALSOFT_EAX

View file

@ -21,18 +21,17 @@
#include "alc/context.h" #include "alc/context.h"
#include "alc/device.h" #include "alc/device.h"
#include "alc/inprogext.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alspan.h" #include "alspan.h"
#include "alstring.h"
#include "auxeffectslot.h" #include "auxeffectslot.h"
#include "buffer.h" #include "buffer.h"
#include "core/except.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/voice.h" #include "core/voice.h"
#include "direct_defs.h" #include "direct_defs.h"
#include "effect.h" #include "effect.h"
#include "error.h"
#include "filter.h" #include "filter.h"
#include "fmt/core.h"
#include "intrusive_ptr.h" #include "intrusive_ptr.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "source.h" #include "source.h"
@ -45,6 +44,8 @@ DebugGroup::~DebugGroup() = default;
namespace { namespace {
using namespace std::string_view_literals;
static_assert(DebugSeverityBase+DebugSeverityCount <= 32, "Too many debug bits"); static_assert(DebugSeverityBase+DebugSeverityCount <= 32, "Too many debug bits");
template<typename T, T ...Vals> template<typename T, T ...Vals>
@ -109,7 +110,8 @@ constexpr auto GetDebugSourceEnum(DebugSource source) -> ALenum
case DebugSource::Application: return AL_DEBUG_SOURCE_APPLICATION_EXT; case DebugSource::Application: return AL_DEBUG_SOURCE_APPLICATION_EXT;
case DebugSource::Other: return AL_DEBUG_SOURCE_OTHER_EXT; case DebugSource::Other: return AL_DEBUG_SOURCE_OTHER_EXT;
} }
throw std::runtime_error{"Unexpected debug source value "+std::to_string(al::to_underlying(source))}; throw std::runtime_error{fmt::format("Unexpected debug source value: {}",
int{al::to_underlying(source)})};
} }
constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum
@ -126,7 +128,8 @@ constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum
case DebugType::PopGroup: return AL_DEBUG_TYPE_POP_GROUP_EXT; case DebugType::PopGroup: return AL_DEBUG_TYPE_POP_GROUP_EXT;
case DebugType::Other: return AL_DEBUG_TYPE_OTHER_EXT; case DebugType::Other: return AL_DEBUG_TYPE_OTHER_EXT;
} }
throw std::runtime_error{"Unexpected debug type value "+std::to_string(al::to_underlying(type))}; throw std::runtime_error{fmt::format("Unexpected debug type value: {}",
int{al::to_underlying(type)})};
} }
constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum
@ -138,50 +141,51 @@ constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum
case DebugSeverity::Low: return AL_DEBUG_SEVERITY_LOW_EXT; case DebugSeverity::Low: return AL_DEBUG_SEVERITY_LOW_EXT;
case DebugSeverity::Notification: return AL_DEBUG_SEVERITY_NOTIFICATION_EXT; case DebugSeverity::Notification: return AL_DEBUG_SEVERITY_NOTIFICATION_EXT;
} }
throw std::runtime_error{"Unexpected debug severity value "+std::to_string(al::to_underlying(severity))}; throw std::runtime_error{fmt::format("Unexpected debug severity value: {}",
int{al::to_underlying(severity)})};
} }
constexpr auto GetDebugSourceName(DebugSource source) noexcept -> const char* constexpr auto GetDebugSourceName(DebugSource source) noexcept -> std::string_view
{ {
switch(source) switch(source)
{ {
case DebugSource::API: return "API"; case DebugSource::API: return "API"sv;
case DebugSource::System: return "Audio System"; case DebugSource::System: return "Audio System"sv;
case DebugSource::ThirdParty: return "Third Party"; case DebugSource::ThirdParty: return "Third Party"sv;
case DebugSource::Application: return "Application"; case DebugSource::Application: return "Application"sv;
case DebugSource::Other: return "Other"; case DebugSource::Other: return "Other"sv;
} }
return "<invalid source>"; return "<invalid source>"sv;
} }
constexpr auto GetDebugTypeName(DebugType type) noexcept -> const char* constexpr auto GetDebugTypeName(DebugType type) noexcept -> std::string_view
{ {
switch(type) switch(type)
{ {
case DebugType::Error: return "Error"; case DebugType::Error: return "Error"sv;
case DebugType::DeprecatedBehavior: return "Deprecated Behavior"; case DebugType::DeprecatedBehavior: return "Deprecated Behavior"sv;
case DebugType::UndefinedBehavior: return "Undefined Behavior"; case DebugType::UndefinedBehavior: return "Undefined Behavior"sv;
case DebugType::Portability: return "Portability"; case DebugType::Portability: return "Portability"sv;
case DebugType::Performance: return "Performance"; case DebugType::Performance: return "Performance"sv;
case DebugType::Marker: return "Marker"; case DebugType::Marker: return "Marker"sv;
case DebugType::PushGroup: return "Push Group"; case DebugType::PushGroup: return "Push Group"sv;
case DebugType::PopGroup: return "Pop Group"; case DebugType::PopGroup: return "Pop Group"sv;
case DebugType::Other: return "Other"; case DebugType::Other: return "Other"sv;
} }
return "<invalid type>"; return "<invalid type>"sv;
} }
constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> const char* constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> std::string_view
{ {
switch(severity) switch(severity)
{ {
case DebugSeverity::High: return "High"; case DebugSeverity::High: return "High"sv;
case DebugSeverity::Medium: return "Medium"; case DebugSeverity::Medium: return "Medium"sv;
case DebugSeverity::Low: return "Low"; case DebugSeverity::Low: return "Low"sv;
case DebugSeverity::Notification: return "Notification"; case DebugSeverity::Notification: return "Notification"sv;
} }
return "<invalid severity>"; return "<invalid severity>"sv;
} }
} // namespace } // namespace
@ -195,8 +199,8 @@ void ALCcontext::sendDebugMessage(std::unique_lock<std::mutex> &debuglock, Debug
if(message.length() >= MaxDebugMessageLength) UNLIKELY if(message.length() >= MaxDebugMessageLength) UNLIKELY
{ {
ERR("Debug message too long (%zu >= %d):\n-> %.*s\n", message.length(), ERR("Debug message too long ({} >= {}):\n-> {}", message.length(),
MaxDebugMessageLength, al::sizei(message), message.data()); MaxDebugMessageLength, message);
return; return;
} }
@ -231,13 +235,13 @@ void ALCcontext::sendDebugMessage(std::unique_lock<std::mutex> &debuglock, Debug
mDebugLog.emplace_back(source, type, id, severity, message); mDebugLog.emplace_back(source, type, id, severity, message);
else UNLIKELY else UNLIKELY
ERR("Debug message log overflow. Lost message:\n" ERR("Debug message log overflow. Lost message:\n"
" Source: %s\n" " Source: {}\n"
" Type: %s\n" " Type: {}\n"
" ID: %u\n" " ID: {}\n"
" Severity: %s\n" " Severity: {}\n"
" Message: \"%.*s\"\n", " Message: \"{}\"",
GetDebugSourceName(source), GetDebugTypeName(type), id, GetDebugSourceName(source), GetDebugTypeName(type), id,
GetDebugSeverityName(severity), al::sizei(message), message.data()); GetDebugSeverityName(severity), message);
} }
} }
@ -260,32 +264,36 @@ try {
return; return;
if(!message) if(!message)
throw al::context_error{AL_INVALID_VALUE, "Null message pointer"}; context->throw_error(AL_INVALID_VALUE, "Null message pointer");
auto msgview = (length < 0) ? std::string_view{message} auto msgview = (length < 0) ? std::string_view{message}
: std::string_view{message, static_cast<uint>(length)}; : std::string_view{message, static_cast<uint>(length)};
if(msgview.size() >= MaxDebugMessageLength) if(msgview.size() >= MaxDebugMessageLength)
throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", msgview.size(),
msgview.size(), MaxDebugMessageLength}; MaxDebugMessageLength);
auto dsource = GetDebugSource(source); auto dsource = GetDebugSource(source);
if(!dsource) if(!dsource)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source));
if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application)
throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source}; context->throw_error(AL_INVALID_ENUM, "Debug source {:#04x} not allowed",
as_unsigned(source));
auto dtype = GetDebugType(type); auto dtype = GetDebugType(type);
if(!dtype) if(!dtype)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type}; context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type));
auto dseverity = GetDebugSeverity(severity); auto dseverity = GetDebugSeverity(severity);
if(!dseverity) if(!dseverity)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity}; context->throw_error(AL_INVALID_ENUM, "Invalid debug severity {:#04x}",
as_unsigned(severity));
context->debugMessage(*dsource, *dtype, id, *dseverity, msgview); context->debugMessage(*dsource, *dtype, id, *dseverity, msgview);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
@ -296,20 +304,20 @@ try {
if(count > 0) if(count > 0)
{ {
if(!ids) if(!ids)
throw al::context_error{AL_INVALID_VALUE, "IDs is null with non-0 count"}; context->throw_error(AL_INVALID_VALUE, "IDs is null with non-0 count");
if(source == AL_DONT_CARE_EXT) if(source == AL_DONT_CARE_EXT)
throw al::context_error{AL_INVALID_OPERATION, context->throw_error(AL_INVALID_OPERATION,
"Debug source cannot be AL_DONT_CARE_EXT with IDs"}; "Debug source cannot be AL_DONT_CARE_EXT with IDs");
if(type == AL_DONT_CARE_EXT) if(type == AL_DONT_CARE_EXT)
throw al::context_error{AL_INVALID_OPERATION, context->throw_error(AL_INVALID_OPERATION,
"Debug type cannot be AL_DONT_CARE_EXT with IDs"}; "Debug type cannot be AL_DONT_CARE_EXT with IDs");
if(severity != AL_DONT_CARE_EXT) if(severity != AL_DONT_CARE_EXT)
throw al::context_error{AL_INVALID_OPERATION, context->throw_error(AL_INVALID_OPERATION,
"Debug severity must be AL_DONT_CARE_EXT with IDs"}; "Debug severity must be AL_DONT_CARE_EXT with IDs");
} }
if(enable != AL_TRUE && enable != AL_FALSE) if(enable != AL_TRUE && enable != AL_FALSE)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug enable %d", enable}; context->throw_error(AL_INVALID_ENUM, "Invalid debug enable {}", enable);
static constexpr size_t ElemCount{DebugSourceCount + DebugTypeCount + DebugSeverityCount}; static constexpr size_t ElemCount{DebugSourceCount + DebugTypeCount + DebugSeverityCount};
static constexpr auto Values = make_array_sequence<uint8_t,ElemCount>(); static constexpr auto Values = make_array_sequence<uint8_t,ElemCount>();
@ -319,7 +327,8 @@ try {
{ {
auto dsource = GetDebugSource(source); auto dsource = GetDebugSource(source);
if(!dsource) if(!dsource)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}",
as_unsigned(source));
srcIndices = srcIndices.subspan(al::to_underlying(*dsource), 1); srcIndices = srcIndices.subspan(al::to_underlying(*dsource), 1);
} }
@ -328,7 +337,7 @@ try {
{ {
auto dtype = GetDebugType(type); auto dtype = GetDebugType(type);
if(!dtype) if(!dtype)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type}; context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type));
typeIndices = typeIndices.subspan(al::to_underlying(*dtype), 1); typeIndices = typeIndices.subspan(al::to_underlying(*dtype), 1);
} }
@ -337,7 +346,8 @@ try {
{ {
auto dseverity = GetDebugSeverity(severity); auto dseverity = GetDebugSeverity(severity);
if(!dseverity) if(!dseverity)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity}; context->throw_error(AL_INVALID_ENUM, "Invalid debug severity {:#04x}",
as_unsigned(severity));
svrIndices = svrIndices.subspan(al::to_underlying(*dseverity), 1); svrIndices = svrIndices.subspan(al::to_underlying(*dseverity), 1);
} }
@ -383,8 +393,10 @@ try {
[apply_type](const uint idx){ apply_type(1<<idx); }); [apply_type](const uint idx){ apply_type(1<<idx); });
} }
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
@ -396,23 +408,24 @@ try {
{ {
size_t newlen{std::strlen(message)}; size_t newlen{std::strlen(message)};
if(newlen >= MaxDebugMessageLength) if(newlen >= MaxDebugMessageLength)
throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", newlen, context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", newlen,
MaxDebugMessageLength}; MaxDebugMessageLength);
length = static_cast<ALsizei>(newlen); length = static_cast<ALsizei>(newlen);
} }
else if(length >= MaxDebugMessageLength) else if(length >= MaxDebugMessageLength)
throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%d >= %d)", length, context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", length,
MaxDebugMessageLength}; MaxDebugMessageLength);
auto dsource = GetDebugSource(source); auto dsource = GetDebugSource(source);
if(!dsource) if(!dsource)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source));
if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application)
throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source}; context->throw_error(AL_INVALID_ENUM, "Debug source {:#04x} not allowed",
as_unsigned(source));
std::unique_lock<std::mutex> debuglock{context->mDebugCbLock}; std::unique_lock<std::mutex> debuglock{context->mDebugCbLock};
if(context->mDebugGroups.size() >= MaxDebugGroupDepth) if(context->mDebugGroups.size() >= MaxDebugGroupDepth)
throw al::context_error{AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups"}; context->throw_error(AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups");
context->mDebugGroups.emplace_back(*dsource, id, context->mDebugGroups.emplace_back(*dsource, id,
std::string_view{message, static_cast<uint>(length)}); std::string_view{message, static_cast<uint>(length)});
@ -426,8 +439,10 @@ try {
context->sendDebugMessage(debuglock, newback.mSource, DebugType::PushGroup, newback.mId, context->sendDebugMessage(debuglock, newback.mSource, DebugType::PushGroup, newback.mId,
DebugSeverity::Notification, newback.mMessage); DebugSeverity::Notification, newback.mMessage);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT) FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT)
@ -435,8 +450,7 @@ FORCE_ALIGN void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) noexc
try { try {
std::unique_lock<std::mutex> debuglock{context->mDebugCbLock}; std::unique_lock<std::mutex> debuglock{context->mDebugCbLock};
if(context->mDebugGroups.size() <= 1) if(context->mDebugGroups.size() <= 1)
throw al::context_error{AL_STACK_UNDERFLOW_EXT, context->throw_error(AL_STACK_UNDERFLOW_EXT, "Attempting to pop the default debug group");
"Attempting to pop the default debug group"};
DebugGroup &debug = context->mDebugGroups.back(); DebugGroup &debug = context->mDebugGroups.back();
const auto source = debug.mSource; const auto source = debug.mSource;
@ -448,8 +462,10 @@ try {
context->sendDebugMessage(debuglock, source, DebugType::PopGroup, id, context->sendDebugMessage(debuglock, source, DebugType::PopGroup, id,
DebugSeverity::Notification, message); DebugSeverity::Notification, message);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
@ -458,66 +474,60 @@ FORCE_ALIGN ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context
ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities,
ALsizei *lengths, ALchar *logBuf) noexcept ALsizei *lengths, ALchar *logBuf) noexcept
try { try {
if(logBufSize < 0) if(logBuf && logBufSize < 0)
throw al::context_error{AL_INVALID_VALUE, "Negative debug log buffer size"}; context->throw_error(AL_INVALID_VALUE, "Negative debug log buffer size");
auto sourcesOut = al::span{sources, sources ? count : 0u}; const auto sourcesSpan = al::span{sources, sources ? count : 0u};
auto typesOut = al::span{types, types ? count : 0u}; const auto typesSpan = al::span{types, types ? count : 0u};
auto idsOut = al::span{ids, ids ? count : 0u}; const auto idsSpan = al::span{ids, ids ? count : 0u};
auto severitiesOut = al::span{severities, severities ? count : 0u}; const auto severitiesSpan = al::span{severities, severities ? count : 0u};
auto lengthsOut = al::span{lengths, lengths ? count : 0u}; const auto lengthsSpan = al::span{lengths, lengths ? count : 0u};
auto logOut = al::span{logBuf, logBuf ? static_cast<ALuint>(logBufSize) : 0u}; const auto logSpan = al::span{logBuf, logBuf ? static_cast<ALuint>(logBufSize) : 0u};
std::lock_guard<std::mutex> debuglock{context->mDebugCbLock}; auto sourceiter = sourcesSpan.begin();
auto typeiter = typesSpan.begin();
auto iditer = idsSpan.begin();
auto severityiter = severitiesSpan.begin();
auto lengthiter = lengthsSpan.begin();
auto logiter = logSpan.begin();
auto debuglock = std::lock_guard{context->mDebugCbLock};
for(ALuint i{0};i < count;++i) for(ALuint i{0};i < count;++i)
{ {
if(context->mDebugLog.empty()) if(context->mDebugLog.empty())
return i; return i;
auto &entry = context->mDebugLog.front(); auto &entry = context->mDebugLog.front();
const size_t tocopy{entry.mMessage.size() + 1}; const auto tocopy = size_t{entry.mMessage.size() + 1};
if(logOut.data() != nullptr) if(al::to_address(logiter) != nullptr)
{ {
if(logOut.size() < tocopy) if(static_cast<size_t>(std::distance(logiter, logSpan.end())) < tocopy)
return i; return i;
auto oiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logOut.begin()); logiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logiter);
*oiter = '\0'; *(logiter++) = '\0';
logOut = {oiter+1, logOut.end()};
} }
if(!sourcesOut.empty()) if(al::to_address(sourceiter) != nullptr)
{ *(sourceiter++) = GetDebugSourceEnum(entry.mSource);
sourcesOut.front() = GetDebugSourceEnum(entry.mSource); if(al::to_address(typeiter) != nullptr)
sourcesOut = sourcesOut.subspan<1>(); *(typeiter++) = GetDebugTypeEnum(entry.mType);
} if(al::to_address(iditer) != nullptr)
if(!typesOut.empty()) *(iditer++) = entry.mId;
{ if(al::to_address(severityiter) != nullptr)
typesOut.front() = GetDebugTypeEnum(entry.mType); *(severityiter++) = GetDebugSeverityEnum(entry.mSeverity);
typesOut = typesOut.subspan<1>(); if(al::to_address(lengthiter) != nullptr)
} *(lengthiter++) = static_cast<ALsizei>(tocopy);
if(!idsOut.empty())
{
idsOut.front() = entry.mId;
idsOut = idsOut.subspan<1>();
}
if(!severitiesOut.empty())
{
severitiesOut.front() = GetDebugSeverityEnum(entry.mSeverity);
severitiesOut = severitiesOut.subspan<1>();
}
if(!lengthsOut.empty())
{
lengthsOut.front() = static_cast<ALsizei>(tocopy);
lengthsOut = lengthsOut.subspan<1>();
}
context->mDebugLog.pop_front(); context->mDebugLog.pop_front();
} }
return count; return count;
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); return 0;
}
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
return 0; return 0;
} }
@ -526,13 +536,13 @@ FORCE_ALIGN void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum
ALuint name, ALsizei length, const ALchar *label) noexcept ALuint name, ALsizei length, const ALchar *label) noexcept
try { try {
if(!label && length != 0) if(!label && length != 0)
throw al::context_error{AL_INVALID_VALUE, "Null label pointer"}; context->throw_error(AL_INVALID_VALUE, "Null label pointer");
auto objname = (length < 0) ? std::string_view{label} auto objname = (length < 0) ? std::string_view{label}
: std::string_view{label, static_cast<uint>(length)}; : std::string_view{label, static_cast<uint>(length)};
if(objname.size() >= MaxObjectLabelLength) if(objname.size() >= MaxObjectLabelLength)
throw al::context_error{AL_INVALID_VALUE, "Object label length too long (%zu >= %d)", context->throw_error(AL_INVALID_VALUE, "Object label length too long ({} >= {})",
objname.size(), MaxObjectLabelLength}; objname.size(), MaxObjectLabelLength);
switch(identifier) switch(identifier)
{ {
@ -543,10 +553,13 @@ try {
case AL_AUXILIARY_EFFECT_SLOT_EXT: ALeffectslot::SetName(context, name, objname); return; case AL_AUXILIARY_EFFECT_SLOT_EXT: ALeffectslot::SetName(context, name, objname); return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; context->throw_error(AL_INVALID_ENUM, "Invalid name identifier {:#04x}",
as_unsigned(identifier));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label) FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label)
@ -554,12 +567,12 @@ FORCE_ALIGN void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALen
ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept
try { try {
if(bufSize < 0) if(bufSize < 0)
throw al::context_error{AL_INVALID_VALUE, "Negative label bufSize"}; context->throw_error(AL_INVALID_VALUE, "Negative label bufSize");
if(!label && !length) if(!label && !length)
throw al::context_error{AL_INVALID_VALUE, "Null length and label"}; context->throw_error(AL_INVALID_VALUE, "Null length and label");
if(label && bufSize == 0) if(label && bufSize == 0)
throw al::context_error{AL_INVALID_VALUE, "Zero label bufSize"}; context->throw_error(AL_INVALID_VALUE, "Zero label bufSize");
const auto labelOut = al::span{label, label ? static_cast<ALuint>(bufSize) : 0u}; const auto labelOut = al::span{label, label ? static_cast<ALuint>(bufSize) : 0u};
auto copy_name = [name,length,labelOut](std::unordered_map<ALuint,std::string> &names) auto copy_name = [name,length,labelOut](std::unordered_map<ALuint,std::string> &names)
@ -589,20 +602,20 @@ try {
} }
else if(identifier == AL_BUFFER) else if(identifier == AL_BUFFER)
{ {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard buflock{device->BufferLock}; auto buflock = std::lock_guard{device->BufferLock};
copy_name(device->mBufferNames); copy_name(device->mBufferNames);
} }
else if(identifier == AL_FILTER_EXT) else if(identifier == AL_FILTER_EXT)
{ {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard filterlock{device->FilterLock}; auto buflock = std::lock_guard{device->FilterLock};
copy_name(device->mFilterNames); copy_name(device->mFilterNames);
} }
else if(identifier == AL_EFFECT_EXT) else if(identifier == AL_EFFECT_EXT)
{ {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard effectlock{device->EffectLock}; auto buflock = std::lock_guard{device->EffectLock};
copy_name(device->mEffectNames); copy_name(device->mEffectNames);
} }
else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT) else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT)
@ -611,8 +624,11 @@ try {
copy_name(context->mEffectSlotNames); copy_name(context->mEffectSlotNames);
} }
else else
throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; context->throw_error(AL_INVALID_ENUM, "Invalid name identifier {:#04x}",
as_unsigned(identifier));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }

View file

@ -21,6 +21,8 @@
#include "AL/al.h" #include "AL/al.h"
#include "opthelpers.h"
#ifndef _WIN32 #ifndef _WIN32
using GUID = struct _GUID { /* NOLINT(*-reserved-identifier) */ using GUID = struct _GUID { /* NOLINT(*-reserved-identifier) */
@ -37,14 +39,16 @@ inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept
{ return !(lhs == rhs); } { return !(lhs == rhs); }
#endif // _WIN32 #endif // _WIN32
/* TODO: This seems to create very inefficient comparisons. C++20 should allow
* creating default comparison operators, avoiding the need for this.
*/
#define DECL_EQOP(T, ...) \ #define DECL_EQOP(T, ...) \
[[nodiscard]] auto get_members() const noexcept { return std::forward_as_tuple(__VA_ARGS__); } \ [[nodiscard]] auto get_members() const noexcept { return std::forward_as_tuple(__VA_ARGS__); } \
[[nodiscard]] friend bool operator==(const T &lhs, const T &rhs) noexcept \ [[nodiscard]] friend bool operator==(const T &lhs, const T &rhs) noexcept \
{ return lhs.get_members() == rhs.get_members(); } \ { return lhs.get_members() == rhs.get_members(); } \
[[nodiscard]] friend bool operator!=(const T &lhs, const T &rhs) noexcept \ [[nodiscard]] friend bool operator!=(const T &lhs, const T &rhs) noexcept { return !(lhs == rhs); }
{ return !(lhs == rhs); }
extern const GUID DSPROPSETID_EAX_ReverbProperties; DECL_HIDDEN extern const GUID DSPROPSETID_EAX_ReverbProperties;
enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int { enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int {
DSPROPERTY_EAX_ALL, DSPROPERTY_EAX_ALL,
@ -62,7 +66,7 @@ struct EAX_REVERBPROPERTIES {
}; // EAX_REVERBPROPERTIES }; // EAX_REVERBPROPERTIES
extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties; DECL_HIDDEN extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties;
enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned int { enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned int {
DSPROPERTY_EAXBUFFER_ALL, DSPROPERTY_EAXBUFFER_ALL,
@ -78,7 +82,7 @@ constexpr auto EAX_BUFFER_MAXREVERBMIX = 1.0F;
constexpr auto EAX_REVERBMIX_USEDISTANCE = -1.0F; constexpr auto EAX_REVERBMIX_USEDISTANCE = -1.0F;
extern const GUID DSPROPSETID_EAX20_ListenerProperties; DECL_HIDDEN extern const GUID DSPROPSETID_EAX20_ListenerProperties;
enum DSPROPERTY_EAX20_LISTENERPROPERTY : unsigned int { enum DSPROPERTY_EAX20_LISTENERPROPERTY : unsigned int {
DSPROPERTY_EAX20LISTENER_NONE, DSPROPERTY_EAX20LISTENER_NONE,
@ -216,7 +220,7 @@ constexpr auto EAX2LISTENER_DEFAULTFLAGS =
EAX2LISTENERFLAGS_DECAYHFLIMIT; EAX2LISTENERFLAGS_DECAYHFLIMIT;
extern const GUID DSPROPSETID_EAX20_BufferProperties; DECL_HIDDEN extern const GUID DSPROPSETID_EAX20_BufferProperties;
enum DSPROPERTY_EAX20_BUFFERPROPERTY : unsigned int { enum DSPROPERTY_EAX20_BUFFERPROPERTY : unsigned int {
DSPROPERTY_EAX20BUFFER_NONE, DSPROPERTY_EAX20BUFFER_NONE,
@ -252,9 +256,9 @@ struct EAX20BUFFERPROPERTIES {
unsigned long dwFlags; // modifies the behavior of properties unsigned long dwFlags; // modifies the behavior of properties
}; // EAX20BUFFERPROPERTIES }; // EAX20BUFFERPROPERTIES
extern const GUID DSPROPSETID_EAX30_ListenerProperties; DECL_HIDDEN extern const GUID DSPROPSETID_EAX30_ListenerProperties;
extern const GUID DSPROPSETID_EAX30_BufferProperties; DECL_HIDDEN extern const GUID DSPROPSETID_EAX30_BufferProperties;
constexpr auto EAX_MAX_FXSLOTS = 4; constexpr auto EAX_MAX_FXSLOTS = 4;
@ -272,31 +276,29 @@ constexpr auto EAXERR_INCOMPATIBLE_SOURCE_TYPE = -5L;
constexpr auto EAXERR_INCOMPATIBLE_EAX_VERSION = -6L; constexpr auto EAXERR_INCOMPATIBLE_EAX_VERSION = -6L;
extern const GUID EAX_NULL_GUID; DECL_HIDDEN extern const GUID EAX_NULL_GUID;
extern const GUID EAX_PrimaryFXSlotID; DECL_HIDDEN extern const GUID EAX_PrimaryFXSlotID;
struct EAXVECTOR { struct EAXVECTOR {
float x; float x;
float y; float y;
float z; float z;
[[nodiscard]] };
auto get_members() const noexcept { return std::forward_as_tuple(x, y, z); }
}; // EAXVECTOR
[[nodiscard]] [[nodiscard]]
inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept
{ return lhs.get_members() == rhs.get_members(); } { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z; }
[[nodiscard]] [[nodiscard]]
inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept
{ return !(lhs == rhs); } { return !(lhs == rhs); }
extern const GUID EAXPROPERTYID_EAX40_Context; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_Context;
extern const GUID EAXPROPERTYID_EAX50_Context; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_Context;
// EAX50 // EAX50
constexpr auto HEADPHONES = 0UL; constexpr auto HEADPHONES = 0UL;
@ -372,17 +374,17 @@ constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F;
constexpr auto EAXCONTEXT_DEFAULTLASTERROR = EAX_OK; constexpr auto EAXCONTEXT_DEFAULTLASTERROR = EAX_OK;
extern const GUID EAXPROPERTYID_EAX40_FXSlot0; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot0;
extern const GUID EAXPROPERTYID_EAX50_FXSlot0; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot0;
extern const GUID EAXPROPERTYID_EAX40_FXSlot1; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot1;
extern const GUID EAXPROPERTYID_EAX50_FXSlot1; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot1;
extern const GUID EAXPROPERTYID_EAX40_FXSlot2; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot2;
extern const GUID EAXPROPERTYID_EAX50_FXSlot2; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot2;
extern const GUID EAXPROPERTYID_EAX40_FXSlot3; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot3;
extern const GUID EAXPROPERTYID_EAX50_FXSlot3; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot3;
extern const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; DECL_HIDDEN extern const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID;
extern const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID; DECL_HIDDEN extern const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID;
enum EAXFXSLOT_PROPERTY : unsigned int { enum EAXFXSLOT_PROPERTY : unsigned int {
EAXFXSLOT_PARAMETER = 0, EAXFXSLOT_PARAMETER = 0,
@ -445,8 +447,8 @@ struct EAX50FXSLOTPROPERTIES : public EAX40FXSLOTPROPERTIES {
float flOcclusionLFRatio; float flOcclusionLFRatio;
}; // EAX50FXSLOTPROPERTIES }; // EAX50FXSLOTPROPERTIES
extern const GUID EAXPROPERTYID_EAX40_Source; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_Source;
extern const GUID EAXPROPERTYID_EAX50_Source; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_Source;
// Source object properties // Source object properties
enum EAXSOURCE_PROPERTY : unsigned int { enum EAXSOURCE_PROPERTY : unsigned int {
@ -713,16 +715,16 @@ struct EAXSOURCEEXCLUSIONSENDPROPERTIES {
float flExclusionLFRatio; float flExclusionLFRatio;
}; // EAXSOURCEEXCLUSIONSENDPROPERTIES }; // EAXSOURCEEXCLUSIONSENDPROPERTIES
extern const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID; DECL_HIDDEN extern const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID;
extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; DECL_HIDDEN extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID;
extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID; DECL_HIDDEN extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID;
// EAX Reverb Effect // EAX Reverb Effect
extern const GUID EAX_REVERB_EFFECT; DECL_HIDDEN extern const GUID EAX_REVERB_EFFECT;
// Reverb effect properties // Reverb effect properties
enum EAXREVERB_PROPERTY : unsigned int { enum EAXREVERB_PROPERTY : unsigned int {
@ -959,18 +961,18 @@ constexpr auto EAXREVERB_DEFAULTFLAGS =
using Eax1ReverbPresets = std::array<EAX_REVERBPROPERTIES, EAX1_ENVIRONMENT_COUNT>; using Eax1ReverbPresets = std::array<EAX_REVERBPROPERTIES, EAX1_ENVIRONMENT_COUNT>;
extern const Eax1ReverbPresets EAX1REVERB_PRESETS; DECL_HIDDEN extern const Eax1ReverbPresets EAX1REVERB_PRESETS;
using Eax2ReverbPresets = std::array<EAX20LISTENERPROPERTIES, EAX2_ENVIRONMENT_COUNT>; using Eax2ReverbPresets = std::array<EAX20LISTENERPROPERTIES, EAX2_ENVIRONMENT_COUNT>;
extern const Eax2ReverbPresets EAX2REVERB_PRESETS; DECL_HIDDEN extern const Eax2ReverbPresets EAX2REVERB_PRESETS;
using EaxReverbPresets = std::array<EAXREVERBPROPERTIES, EAX1_ENVIRONMENT_COUNT>; using EaxReverbPresets = std::array<EAXREVERBPROPERTIES, EAX1_ENVIRONMENT_COUNT>;
extern const EaxReverbPresets EAXREVERB_PRESETS; DECL_HIDDEN extern const EaxReverbPresets EAXREVERB_PRESETS;
// AGC Compressor Effect // AGC Compressor Effect
extern const GUID EAX_AGCCOMPRESSOR_EFFECT; DECL_HIDDEN extern const GUID EAX_AGCCOMPRESSOR_EFFECT;
enum EAXAGCCOMPRESSOR_PROPERTY : unsigned int { enum EAXAGCCOMPRESSOR_PROPERTY : unsigned int {
EAXAGCCOMPRESSOR_NONE, EAXAGCCOMPRESSOR_NONE,
@ -991,7 +993,7 @@ constexpr auto EAXAGCCOMPRESSOR_DEFAULTONOFF = EAXAGCCOMPRESSOR_MAXONOFF;
// Autowah Effect // Autowah Effect
extern const GUID EAX_AUTOWAH_EFFECT; DECL_HIDDEN extern const GUID EAX_AUTOWAH_EFFECT;
enum EAXAUTOWAH_PROPERTY : unsigned int { enum EAXAUTOWAH_PROPERTY : unsigned int {
EAXAUTOWAH_NONE, EAXAUTOWAH_NONE,
@ -1030,7 +1032,7 @@ constexpr auto EAXAUTOWAH_DEFAULTPEAKLEVEL = 2100L;
// Chorus Effect // Chorus Effect
extern const GUID EAX_CHORUS_EFFECT; DECL_HIDDEN extern const GUID EAX_CHORUS_EFFECT;
enum EAXCHORUS_PROPERTY : unsigned int { enum EAXCHORUS_PROPERTY : unsigned int {
EAXCHORUS_NONE, EAXCHORUS_NONE,
@ -1086,7 +1088,7 @@ constexpr auto EAXCHORUS_DEFAULTDELAY = 0.016F;
// Distortion Effect // Distortion Effect
extern const GUID EAX_DISTORTION_EFFECT; DECL_HIDDEN extern const GUID EAX_DISTORTION_EFFECT;
enum EAXDISTORTION_PROPERTY : unsigned int { enum EAXDISTORTION_PROPERTY : unsigned int {
EAXDISTORTION_NONE, EAXDISTORTION_NONE,
@ -1131,7 +1133,7 @@ constexpr auto EAXDISTORTION_DEFAULTEQBANDWIDTH = 3600.0F;
// Echo Effect // Echo Effect
extern const GUID EAX_ECHO_EFFECT; DECL_HIDDEN extern const GUID EAX_ECHO_EFFECT;
enum EAXECHO_PROPERTY : unsigned int { enum EAXECHO_PROPERTY : unsigned int {
EAXECHO_NONE, EAXECHO_NONE,
@ -1176,7 +1178,7 @@ constexpr auto EAXECHO_DEFAULTSPREAD = -1.0F;
// Equalizer Effect // Equalizer Effect
extern const GUID EAX_EQUALIZER_EFFECT; DECL_HIDDEN extern const GUID EAX_EQUALIZER_EFFECT;
enum EAXEQUALIZER_PROPERTY : unsigned int { enum EAXEQUALIZER_PROPERTY : unsigned int {
EAXEQUALIZER_NONE, EAXEQUALIZER_NONE,
@ -1252,7 +1254,7 @@ constexpr auto EAXEQUALIZER_DEFAULTHIGHCUTOFF = 6000.0F;
// Flanger Effect // Flanger Effect
extern const GUID EAX_FLANGER_EFFECT; DECL_HIDDEN extern const GUID EAX_FLANGER_EFFECT;
enum EAXFLANGER_PROPERTY : unsigned int { enum EAXFLANGER_PROPERTY : unsigned int {
EAXFLANGER_NONE, EAXFLANGER_NONE,
@ -1308,7 +1310,7 @@ constexpr auto EAXFLANGER_DEFAULTDELAY = 0.002F;
// Frequency Shifter Effect // Frequency Shifter Effect
extern const GUID EAX_FREQUENCYSHIFTER_EFFECT; DECL_HIDDEN extern const GUID EAX_FREQUENCYSHIFTER_EFFECT;
enum EAXFREQUENCYSHIFTER_PROPERTY : unsigned int { enum EAXFREQUENCYSHIFTER_PROPERTY : unsigned int {
EAXFREQUENCYSHIFTER_NONE, EAXFREQUENCYSHIFTER_NONE,
@ -1347,7 +1349,7 @@ constexpr auto EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION = EAXFREQUENCYSHIFTER_M
// Vocal Morpher Effect // Vocal Morpher Effect
extern const GUID EAX_VOCALMORPHER_EFFECT; DECL_HIDDEN extern const GUID EAX_VOCALMORPHER_EFFECT;
enum EAXVOCALMORPHER_PROPERTY : unsigned int { enum EAXVOCALMORPHER_PROPERTY : unsigned int {
EAXVOCALMORPHER_NONE, EAXVOCALMORPHER_NONE,
@ -1439,7 +1441,7 @@ constexpr auto EAXVOCALMORPHER_DEFAULTRATE = 1.41F;
// Pitch Shifter Effect // Pitch Shifter Effect
extern const GUID EAX_PITCHSHIFTER_EFFECT; DECL_HIDDEN extern const GUID EAX_PITCHSHIFTER_EFFECT;
enum EAXPITCHSHIFTER_PROPERTY : unsigned int { enum EAXPITCHSHIFTER_PROPERTY : unsigned int {
EAXPITCHSHIFTER_NONE, EAXPITCHSHIFTER_NONE,
@ -1466,7 +1468,7 @@ constexpr auto EAXPITCHSHIFTER_DEFAULTFINETUNE = 0L;
// Ring Modulator Effect // Ring Modulator Effect
extern const GUID EAX_RINGMODULATOR_EFFECT; DECL_HIDDEN extern const GUID EAX_RINGMODULATOR_EFFECT;
enum EAXRINGMODULATOR_PROPERTY : unsigned int { enum EAXRINGMODULATOR_PROPERTY : unsigned int {
EAXRINGMODULATOR_NONE, EAXRINGMODULATOR_NONE,

View file

@ -15,13 +15,8 @@ public:
} // namespace } // namespace
EaxCall::EaxCall( EaxCall::EaxCall(EaxCallType type, const GUID &property_set_guid, ALuint property_id,
EaxCallType type, ALuint property_source_id, ALvoid *property_buffer, ALuint property_size)
const GUID& property_set_guid,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_buffer,
ALuint property_size)
: mCallType{type}, mIsDeferred{(property_id & deferred_flag) != 0} : mCallType{type}, mIsDeferred{(property_id & deferred_flag) != 0}
, mPropertyId{property_id & ~deferred_flag}, mPropertySourceId{property_source_id} , mPropertyId{property_id & ~deferred_flag}, mPropertySourceId{property_source_id}
, mPropertyBuffer{property_buffer}, mPropertyBufferSize{property_size} , mPropertyBuffer{property_buffer}, mPropertyBufferSize{property_size}
@ -145,23 +140,34 @@ EaxCall::EaxCall(
fail("Unsupported property set id."); fail("Unsupported property set id.");
} }
switch(mPropertyId) if(mPropertySetId == EaxCallPropertySetId::context)
{ {
case EAXCONTEXT_LASTERROR: switch(mPropertyId)
case EAXCONTEXT_SPEAKERCONFIG: {
case EAXCONTEXT_EAXSESSION: case EAXCONTEXT_LASTERROR:
case EAXFXSLOT_NONE: case EAXCONTEXT_SPEAKERCONFIG:
case EAXFXSLOT_ALLPARAMETERS: case EAXCONTEXT_EAXSESSION:
case EAXFXSLOT_LOADEFFECT: // EAX allow to set "defer" flag on immediate-only properties.
case EAXFXSLOT_VOLUME: // If we don't clear our flag then "applyAllUpdates" in EAX context won't be called.
case EAXFXSLOT_LOCK: mIsDeferred = false;
case EAXFXSLOT_FLAGS: break;
case EAXFXSLOT_OCCLUSION: }
case EAXFXSLOT_OCCLUSIONLFRATIO: }
// EAX allow to set "defer" flag on immediate-only properties. else if(mPropertySetId == EaxCallPropertySetId::fx_slot)
// If we don't clear our flag then "applyAllUpdates" in EAX context won't be called. {
mIsDeferred = false; switch(mPropertyId)
break; {
case EAXFXSLOT_NONE:
case EAXFXSLOT_ALLPARAMETERS:
case EAXFXSLOT_LOADEFFECT:
case EAXFXSLOT_VOLUME:
case EAXFXSLOT_LOCK:
case EAXFXSLOT_FLAGS:
case EAXFXSLOT_OCCLUSION:
case EAXFXSLOT_OCCLUSIONLFRATIO:
mIsDeferred = false;
break;
}
} }
if(!mIsDeferred) if(!mIsDeferred)

View file

@ -1,17 +1,17 @@
#ifndef EAX_EFFECT_INCLUDED #ifndef EAX_EFFECT_INCLUDED
#define EAX_EFFECT_INCLUDED #define EAX_EFFECT_INCLUDED
#include <cassert> #include <cassert>
#include <memory> #include <memory>
#include <variant> #include <variant>
#include "alnumeric.h"
#include "AL/al.h" #include "AL/al.h"
#include "AL/alext.h" #include "AL/alext.h"
#include "core/effects/base.h" #include "core/effects/base.h"
#include "call.h" #include "call.h"
inline bool EaxTraceCommits{false};
struct EaxEffectErrorMessages { struct EaxEffectErrorMessages {
static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; } static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; }
static constexpr auto unknown_version() noexcept { return "Unknown version."; } static constexpr auto unknown_version() noexcept { return "Unknown version."; }
@ -123,10 +123,6 @@ template<typename T>
struct EaxCommitter { struct EaxCommitter {
struct Exception; struct Exception;
EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops)
: mEaxProps{eaxprops}, mAlProps{alprops}
{ }
EaxEffectProps &mEaxProps; EaxEffectProps &mEaxProps;
EffectProps &mAlProps; EffectProps &mAlProps;
@ -141,10 +137,18 @@ struct EaxCommitter {
[[noreturn]] static void fail(const char *message); [[noreturn]] static void fail(const char *message);
[[noreturn]] static void fail_unknown_property_id() [[noreturn]] static void fail_unknown_property_id()
{ fail(EaxEffectErrorMessages::unknown_property_id()); } { fail(EaxEffectErrorMessages::unknown_property_id()); }
private:
EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops)
: mEaxProps{eaxprops}, mAlProps{alprops}
{ }
friend T;
}; };
struct EaxAutowahCommitter : public EaxCommitter<EaxAutowahCommitter> { struct EaxAutowahCommitter : public EaxCommitter<EaxAutowahCommitter> {
using EaxCommitter<EaxAutowahCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxAutowahCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXAUTOWAHPROPERTIES &props); bool commit(const EAXAUTOWAHPROPERTIES &props);
@ -153,7 +157,8 @@ struct EaxAutowahCommitter : public EaxCommitter<EaxAutowahCommitter> {
static void Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props); static void Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props);
}; };
struct EaxChorusCommitter : public EaxCommitter<EaxChorusCommitter> { struct EaxChorusCommitter : public EaxCommitter<EaxChorusCommitter> {
using EaxCommitter<EaxChorusCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxChorusCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXCHORUSPROPERTIES &props); bool commit(const EAXCHORUSPROPERTIES &props);
@ -162,7 +167,8 @@ struct EaxChorusCommitter : public EaxCommitter<EaxChorusCommitter> {
static void Set(const EaxCall &call, EAXCHORUSPROPERTIES &props); static void Set(const EaxCall &call, EAXCHORUSPROPERTIES &props);
}; };
struct EaxCompressorCommitter : public EaxCommitter<EaxCompressorCommitter> { struct EaxCompressorCommitter : public EaxCommitter<EaxCompressorCommitter> {
using EaxCommitter<EaxCompressorCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxCompressorCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXAGCCOMPRESSORPROPERTIES &props); bool commit(const EAXAGCCOMPRESSORPROPERTIES &props);
@ -171,7 +177,8 @@ struct EaxCompressorCommitter : public EaxCommitter<EaxCompressorCommitter> {
static void Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props); static void Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props);
}; };
struct EaxDistortionCommitter : public EaxCommitter<EaxDistortionCommitter> { struct EaxDistortionCommitter : public EaxCommitter<EaxDistortionCommitter> {
using EaxCommitter<EaxDistortionCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxDistortionCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXDISTORTIONPROPERTIES &props); bool commit(const EAXDISTORTIONPROPERTIES &props);
@ -180,7 +187,8 @@ struct EaxDistortionCommitter : public EaxCommitter<EaxDistortionCommitter> {
static void Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props); static void Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props);
}; };
struct EaxEchoCommitter : public EaxCommitter<EaxEchoCommitter> { struct EaxEchoCommitter : public EaxCommitter<EaxEchoCommitter> {
using EaxCommitter<EaxEchoCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxEchoCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXECHOPROPERTIES &props); bool commit(const EAXECHOPROPERTIES &props);
@ -189,7 +197,8 @@ struct EaxEchoCommitter : public EaxCommitter<EaxEchoCommitter> {
static void Set(const EaxCall &call, EAXECHOPROPERTIES &props); static void Set(const EaxCall &call, EAXECHOPROPERTIES &props);
}; };
struct EaxEqualizerCommitter : public EaxCommitter<EaxEqualizerCommitter> { struct EaxEqualizerCommitter : public EaxCommitter<EaxEqualizerCommitter> {
using EaxCommitter<EaxEqualizerCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxEqualizerCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXEQUALIZERPROPERTIES &props); bool commit(const EAXEQUALIZERPROPERTIES &props);
@ -198,7 +207,8 @@ struct EaxEqualizerCommitter : public EaxCommitter<EaxEqualizerCommitter> {
static void Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props); static void Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props);
}; };
struct EaxFlangerCommitter : public EaxCommitter<EaxFlangerCommitter> { struct EaxFlangerCommitter : public EaxCommitter<EaxFlangerCommitter> {
using EaxCommitter<EaxFlangerCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxFlangerCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXFLANGERPROPERTIES &props); bool commit(const EAXFLANGERPROPERTIES &props);
@ -207,7 +217,8 @@ struct EaxFlangerCommitter : public EaxCommitter<EaxFlangerCommitter> {
static void Set(const EaxCall &call, EAXFLANGERPROPERTIES &props); static void Set(const EaxCall &call, EAXFLANGERPROPERTIES &props);
}; };
struct EaxFrequencyShifterCommitter : public EaxCommitter<EaxFrequencyShifterCommitter> { struct EaxFrequencyShifterCommitter : public EaxCommitter<EaxFrequencyShifterCommitter> {
using EaxCommitter<EaxFrequencyShifterCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxFrequencyShifterCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXFREQUENCYSHIFTERPROPERTIES &props); bool commit(const EAXFREQUENCYSHIFTERPROPERTIES &props);
@ -216,7 +227,8 @@ struct EaxFrequencyShifterCommitter : public EaxCommitter<EaxFrequencyShifterCom
static void Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props); static void Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props);
}; };
struct EaxModulatorCommitter : public EaxCommitter<EaxModulatorCommitter> { struct EaxModulatorCommitter : public EaxCommitter<EaxModulatorCommitter> {
using EaxCommitter<EaxModulatorCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxModulatorCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXRINGMODULATORPROPERTIES &props); bool commit(const EAXRINGMODULATORPROPERTIES &props);
@ -225,7 +237,8 @@ struct EaxModulatorCommitter : public EaxCommitter<EaxModulatorCommitter> {
static void Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props); static void Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props);
}; };
struct EaxPitchShifterCommitter : public EaxCommitter<EaxPitchShifterCommitter> { struct EaxPitchShifterCommitter : public EaxCommitter<EaxPitchShifterCommitter> {
using EaxCommitter<EaxPitchShifterCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxPitchShifterCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXPITCHSHIFTERPROPERTIES &props); bool commit(const EAXPITCHSHIFTERPROPERTIES &props);
@ -234,7 +247,8 @@ struct EaxPitchShifterCommitter : public EaxCommitter<EaxPitchShifterCommitter>
static void Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props); static void Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props);
}; };
struct EaxVocalMorpherCommitter : public EaxCommitter<EaxVocalMorpherCommitter> { struct EaxVocalMorpherCommitter : public EaxCommitter<EaxVocalMorpherCommitter> {
using EaxCommitter<EaxVocalMorpherCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxVocalMorpherCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const EAXVOCALMORPHERPROPERTIES &props); bool commit(const EAXVOCALMORPHERPROPERTIES &props);
@ -243,7 +257,8 @@ struct EaxVocalMorpherCommitter : public EaxCommitter<EaxVocalMorpherCommitter>
static void Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props); static void Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props);
}; };
struct EaxNullCommitter : public EaxCommitter<EaxNullCommitter> { struct EaxNullCommitter : public EaxCommitter<EaxNullCommitter> {
using EaxCommitter<EaxNullCommitter>::EaxCommitter; template<typename ...Args>
explicit EaxNullCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
bool commit(const std::monostate &props); bool commit(const std::monostate &props);
@ -279,7 +294,7 @@ public:
~EaxEffect() = default; ~EaxEffect() = default;
ALenum al_effect_type_{AL_EFFECT_NULL}; ALenum al_effect_type_{AL_EFFECT_NULL};
EffectProps al_effect_props_{}; EffectProps al_effect_props_;
using Props1 = EAX_REVERBPROPERTIES; using Props1 = EAX_REVERBPROPERTIES;
using Props2 = EAX20LISTENERPROPERTIES; using Props2 = EAX20LISTENERPROPERTIES;
@ -308,7 +323,7 @@ public:
int version_{}; int version_{};
bool changed_{}; bool changed_{};
Props4 props_{}; Props4 props_;
State1 state1_{}; State1 state1_{};
State2 state2_{}; State2 state2_{};
State3 state3_{}; State3 state3_{};

View file

@ -5,7 +5,6 @@
#include <cassert> #include <cassert>
#include <exception> #include <exception>
#include "alstring.h"
#include "core/logging.h" #include "core/logging.h"
@ -18,9 +17,9 @@ void eax_log_exception(std::string_view message) noexcept
std::rethrow_exception(exception_ptr); std::rethrow_exception(exception_ptr);
} }
catch(const std::exception& ex) { catch(const std::exception& ex) {
ERR("%.*s %s\n", al::sizei(message), message.data(), ex.what()); ERR("{} {}", message, ex.what());
} }
catch(...) { catch(...) {
ERR("%.*s %s\n", al::sizei(message), message.data(), "Generic exception."); ERR("{} {}", message, "Generic exception.");
} }
} }

View file

@ -1,15 +1,11 @@
#ifndef EAX_UTILS_INCLUDED #ifndef EAX_UTILS_INCLUDED
#define EAX_UTILS_INCLUDED #define EAX_UTILS_INCLUDED
#include <algorithm>
#include <cstdint>
#include <string>
#include <string_view> #include <string_view>
#include <type_traits>
#include "fmt/core.h"
#include "opthelpers.h" #include "opthelpers.h"
using EaxDirtyFlags = unsigned int;
struct EaxAlLowPassParam { struct EaxAlLowPassParam {
float gain; float gain;
@ -25,71 +21,9 @@ void eax_validate_range(std::string_view value_name, const TValue& value, const
if(value >= min_value && value <= max_value) LIKELY if(value >= min_value && value <= max_value) LIKELY
return; return;
const auto message = const auto message = fmt::format("{} out of range (value: {}; min: {}; max: {}).", value_name,
std::string{value_name} + value, min_value, max_value);
" out of range (value: " +
std::to_string(value) + "; min: " +
std::to_string(min_value) + "; max: " +
std::to_string(max_value) + ").";
throw TException{message.c_str()}; throw TException{message.c_str()};
} }
namespace detail {
template<typename T>
struct EaxIsBitFieldStruct {
private:
using yes = std::true_type;
using no = std::false_type;
template<typename U>
static auto test(int) -> decltype(std::declval<typename U::EaxIsBitFieldStruct>(), yes{});
template<typename>
static no test(...);
public:
static constexpr auto value = std::is_same<decltype(test<T>(0)), yes>::value;
};
template<typename T, typename TValue>
inline bool eax_bit_fields_are_equal(const T& lhs, const T& rhs) noexcept
{
static_assert(sizeof(T) == sizeof(TValue), "Invalid type size.");
return reinterpret_cast<const TValue&>(lhs) == reinterpret_cast<const TValue&>(rhs);
}
} // namespace detail
template<
typename T,
std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
>
inline bool operator==(const T& lhs, const T& rhs) noexcept
{
using Value = std::conditional_t<
sizeof(T) == 1,
std::uint8_t,
std::conditional_t<
sizeof(T) == 2,
std::uint16_t,
std::conditional_t<
sizeof(T) == 4,
std::uint32_t,
void>>>;
static_assert(!std::is_same<Value, void>::value, "Unsupported type.");
return detail::eax_bit_fields_are_equal<T, Value>(lhs, rhs);
}
template<
typename T,
std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
>
inline bool operator!=(const T& lhs, const T& rhs) noexcept
{
return !(lhs == rhs);
}
#endif // !EAX_UTILS_INCLUDED #endif // !EAX_UTILS_INCLUDED

View file

@ -51,9 +51,9 @@
#include "alnumeric.h" #include "alnumeric.h"
#include "alspan.h" #include "alspan.h"
#include "alstring.h" #include "alstring.h"
#include "core/except.h"
#include "core/logging.h" #include "core/logging.h"
#include "direct_defs.h" #include "direct_defs.h"
#include "error.h"
#include "intrusive_ptr.h" #include "intrusive_ptr.h"
#include "opthelpers.h" #include "opthelpers.h"
@ -139,7 +139,8 @@ void InitEffectParams(ALeffect *effect, ALenum type) noexcept
effect->type = type; effect->type = type;
} }
auto EnsureEffects(ALCdevice *device, size_t needed) noexcept -> bool [[nodiscard]]
auto EnsureEffects(al::Device *device, size_t needed) noexcept -> bool
try { try {
size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz, size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz,
[](size_t cur, const EffectSubList &sublist) noexcept -> size_t [](size_t cur, const EffectSubList &sublist) noexcept -> size_t
@ -162,7 +163,8 @@ catch(...) {
return false; return false;
} }
ALeffect *AllocEffect(ALCdevice *device) noexcept [[nodiscard]]
auto AllocEffect(al::Device *device) noexcept -> ALeffect*
{ {
auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(),
[](const EffectSubList &entry) noexcept -> bool [](const EffectSubList &entry) noexcept -> bool
@ -182,7 +184,7 @@ ALeffect *AllocEffect(ALCdevice *device) noexcept
return effect; return effect;
} }
void FreeEffect(ALCdevice *device, ALeffect *effect) void FreeEffect(al::Device *device, ALeffect *effect)
{ {
device->mEffectNames.erase(effect->id); device->mEffectNames.erase(effect->id);
@ -195,7 +197,8 @@ void FreeEffect(ALCdevice *device, ALeffect *effect)
device->EffectList[lidx].FreeMask |= 1_u64 << slidx; device->EffectList[lidx].FreeMask |= 1_u64 << slidx;
} }
inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* [[nodiscard]] inline
auto LookupEffect(al::Device *device, ALuint id) noexcept -> ALeffect*
{ {
const size_t lidx{(id-1) >> 6}; const size_t lidx{(id-1) >> 6};
const ALuint slidx{(id-1) & 0x3f}; const ALuint slidx{(id-1) & 0x3f};
@ -214,21 +217,23 @@ AL_API DECL_FUNC2(void, alGenEffects, ALsizei,n, ALuint*,effects)
FORCE_ALIGN void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) noexcept FORCE_ALIGN void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) noexcept
try { try {
if(n < 0) if(n < 0)
throw al::context_error{AL_INVALID_VALUE, "Generating %d effects", n}; context->throw_error(AL_INVALID_VALUE, "Generating {} effects", n);
if(n <= 0) UNLIKELY return; if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
const al::span eids{effects, static_cast<ALuint>(n)}; const al::span eids{effects, static_cast<ALuint>(n)};
if(!EnsureEffects(device, eids.size())) if(!EnsureEffects(device, eids.size()))
throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} effect{}", n,
(n == 1) ? "" : "s"}; (n==1) ? "" : "s");
std::generate(eids.begin(), eids.end(), [device]{ return AllocEffect(device)->id; }); std::generate(eids.begin(), eids.end(), [device]{ return AllocEffect(device)->id; });
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects) AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects)
@ -236,11 +241,11 @@ FORCE_ALIGN void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei
const ALuint *effects) noexcept const ALuint *effects) noexcept
try { try {
if(n < 0) if(n < 0)
throw al::context_error{AL_INVALID_VALUE, "Deleting %d effects", n}; context->throw_error(AL_INVALID_VALUE, "Deleting {} effects", n);
if(n <= 0) UNLIKELY return; if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
/* First try to find any effects that are invalid. */ /* First try to find any effects that are invalid. */
auto validate_effect = [device](const ALuint eid) -> bool auto validate_effect = [device](const ALuint eid) -> bool
@ -249,7 +254,7 @@ try {
const al::span eids{effects, static_cast<ALuint>(n)}; const al::span eids{effects, static_cast<ALuint>(n)};
auto inveffect = std::find_if_not(eids.begin(), eids.end(), validate_effect); auto inveffect = std::find_if_not(eids.begin(), eids.end(), validate_effect);
if(inveffect != eids.end()) if(inveffect != eids.end())
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", *inveffect}; context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", *inveffect);
/* All good. Delete non-0 effect IDs. */ /* All good. Delete non-0 effect IDs. */
auto delete_effect = [device](ALuint eid) -> void auto delete_effect = [device](ALuint eid) -> void
@ -259,15 +264,17 @@ try {
}; };
std::for_each(eids.begin(), eids.end(), delete_effect); std::for_each(eids.begin(), eids.end(), delete_effect);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect) AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect)
FORCE_ALIGN ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) noexcept FORCE_ALIGN ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) noexcept
{ {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
if(!effect || LookupEffect(device, effect)) if(!effect || LookupEffect(device, effect))
return AL_TRUE; return AL_TRUE;
return AL_FALSE; return AL_FALSE;
@ -277,12 +284,12 @@ AL_API DECL_FUNC3(void, alEffecti, ALuint,effect, ALenum,param, ALint,value)
FORCE_ALIGN void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, FORCE_ALIGN void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param,
ALint value) noexcept ALint value) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)}; ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect);
switch(param) switch(param)
{ {
@ -292,8 +299,8 @@ try {
auto check_effect = [value](const EffectList &item) -> bool auto check_effect = [value](const EffectList &item) -> bool
{ return value == item.val && !DisabledEffects.test(item.type); }; { return value == item.val && !DisabledEffects.test(item.type); };
if(!std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect)) if(!std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect))
throw al::context_error{AL_INVALID_VALUE, "Effect type 0x%04x not supported", context->throw_error(AL_INVALID_VALUE, "Effect type {:#04x} not supported",
value}; as_unsigned(value));
} }
InitEffectParams(aleffect, value); InitEffectParams(aleffect, value);
@ -301,15 +308,17 @@ try {
} }
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([aleffect,param,value](auto &arg) std::visit([context,aleffect,param,value](auto &arg)
{ {
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>; using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type; using PropType = typename Type::prop_type;
return arg.SetParami(std::get<PropType>(aleffect->Props), param, value); return arg.SetParami(context, std::get<PropType>(aleffect->Props), param, value);
}, aleffect->PropsVariant); }, aleffect->PropsVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alEffectiv, ALuint,effect, ALenum,param, const ALint*,values) AL_API DECL_FUNC3(void, alEffectiv, ALuint,effect, ALenum,param, const ALint*,values)
@ -323,81 +332,87 @@ try {
return; return;
} }
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)}; ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg) std::visit([context,aleffect,param,values](auto &arg)
{ {
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>; using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type; using PropType = typename Type::prop_type;
return arg.SetParamiv(std::get<PropType>(aleffect->Props), param, values); return arg.SetParamiv(context, std::get<PropType>(aleffect->Props), param, values);
}, aleffect->PropsVariant); }, aleffect->PropsVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alEffectf, ALuint,effect, ALenum,param, ALfloat,value) AL_API DECL_FUNC3(void, alEffectf, ALuint,effect, ALenum,param, ALfloat,value)
FORCE_ALIGN void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, FORCE_ALIGN void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param,
ALfloat value) noexcept ALfloat value) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)}; ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([aleffect,param,value](auto &arg) std::visit([context,aleffect,param,value](auto &arg)
{ {
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>; using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type; using PropType = typename Type::prop_type;
return arg.SetParamf(std::get<PropType>(aleffect->Props), param, value); return arg.SetParamf(context, std::get<PropType>(aleffect->Props), param, value);
}, aleffect->PropsVariant); }, aleffect->PropsVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alEffectfv, ALuint,effect, ALenum,param, const ALfloat*,values) AL_API DECL_FUNC3(void, alEffectfv, ALuint,effect, ALenum,param, const ALfloat*,values)
FORCE_ALIGN void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, FORCE_ALIGN void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param,
const ALfloat *values) noexcept const ALfloat *values) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)}; ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg) std::visit([context,aleffect,param,values](auto &arg)
{ {
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>; using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type; using PropType = typename Type::prop_type;
return arg.SetParamfv(std::get<PropType>(aleffect->Props), param, values); return arg.SetParamfv(context, std::get<PropType>(aleffect->Props), param, values);
}, aleffect->PropsVariant); }, aleffect->PropsVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetEffecti, ALuint,effect, ALenum,param, ALint*,value) AL_API DECL_FUNC3(void, alGetEffecti, ALuint,effect, ALenum,param, ALint*,value)
FORCE_ALIGN void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, FORCE_ALIGN void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param,
ALint *value) noexcept ALint *value) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)}; const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect);
switch(param) switch(param)
{ {
@ -407,15 +422,17 @@ try {
} }
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([aleffect,param,value](auto &arg) std::visit([context,aleffect,param,value](auto &arg)
{ {
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>; using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type; using PropType = typename Type::prop_type;
return arg.GetParami(std::get<PropType>(aleffect->Props), param, value); return arg.GetParami(context, std::get<PropType>(aleffect->Props), param, value);
}, aleffect->PropsVariant); }, aleffect->PropsVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetEffectiv, ALuint,effect, ALenum,param, ALint*,values) AL_API DECL_FUNC3(void, alGetEffectiv, ALuint,effect, ALenum,param, ALint*,values)
@ -429,69 +446,75 @@ try {
return; return;
} }
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)}; const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg) std::visit([context,aleffect,param,values](auto &arg)
{ {
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>; using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type; using PropType = typename Type::prop_type;
return arg.GetParamiv(std::get<PropType>(aleffect->Props), param, values); return arg.GetParamiv(context, std::get<PropType>(aleffect->Props), param, values);
}, aleffect->PropsVariant); }, aleffect->PropsVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetEffectf, ALuint,effect, ALenum,param, ALfloat*,value) AL_API DECL_FUNC3(void, alGetEffectf, ALuint,effect, ALenum,param, ALfloat*,value)
FORCE_ALIGN void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, FORCE_ALIGN void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param,
ALfloat *value) noexcept ALfloat *value) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)}; const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([aleffect,param,value](auto &arg) std::visit([context,aleffect,param,value](auto &arg)
{ {
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>; using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type; using PropType = typename Type::prop_type;
return arg.GetParamf(std::get<PropType>(aleffect->Props), param, value); return arg.GetParamf(context, std::get<PropType>(aleffect->Props), param, value);
}, aleffect->PropsVariant); }, aleffect->PropsVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetEffectfv, ALuint,effect, ALenum,param, ALfloat*,values) AL_API DECL_FUNC3(void, alGetEffectfv, ALuint,effect, ALenum,param, ALfloat*,values)
FORCE_ALIGN void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, FORCE_ALIGN void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param,
ALfloat *values) noexcept ALfloat *values) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)}; const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg) std::visit([context,aleffect,param,values](auto &arg)
{ {
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>; using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type; using PropType = typename Type::prop_type;
return arg.GetParamfv(std::get<PropType>(aleffect->Props), param, values); return arg.GetParamfv(context, std::get<PropType>(aleffect->Props), param, values);
}, aleffect->PropsVariant); }, aleffect->PropsVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
@ -502,12 +525,12 @@ void InitEffect(ALeffect *effect)
void ALeffect::SetName(ALCcontext* context, ALuint id, std::string_view name) void ALeffect::SetName(ALCcontext* context, ALuint id, std::string_view name)
{ {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> effectlock{device->EffectLock}; auto effectlock = std::lock_guard{device->EffectLock};
auto effect = LookupEffect(device, id); auto effect = LookupEffect(device, id);
if(!effect) if(!effect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", id}; context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", id);
device->mEffectNames.insert_or_assign(id, name); device->mEffectNames.insert_or_assign(id, name);
} }
@ -673,7 +696,7 @@ void LoadReverbPreset(const std::string_view name, ALeffect *effect)
if(al::case_compare(name, "NONE"sv) == 0) if(al::case_compare(name, "NONE"sv) == 0)
{ {
InitEffectParams(effect, AL_EFFECT_NULL); InitEffectParams(effect, AL_EFFECT_NULL);
TRACE("Loading reverb '%s'\n", "NONE"); TRACE("Loading reverb '{}'", "NONE");
return; return;
} }
@ -688,7 +711,7 @@ void LoadReverbPreset(const std::string_view name, ALeffect *effect)
if(al::case_compare(name, std::data(reverbitem.name)) != 0) if(al::case_compare(name, std::data(reverbitem.name)) != 0)
continue; continue;
TRACE("Loading reverb '%s'\n", std::data(reverbitem.name)); TRACE("Loading reverb '{}'", std::data(reverbitem.name));
const auto &props = reverbitem.props; const auto &props = reverbitem.props;
auto &dst = std::get<ReverbProps>(effect->Props); auto &dst = std::get<ReverbProps>(effect->Props);
dst.Density = props.flDensity; dst.Density = props.flDensity;
@ -721,7 +744,7 @@ void LoadReverbPreset(const std::string_view name, ALeffect *effect)
return; return;
} }
WARN("Reverb preset '%.*s' not found\n", al::sizei(name), name.data()); WARN("Reverb preset '{}' not found", name);
} }
bool IsValidEffectType(ALenum type) noexcept bool IsValidEffectType(ALenum type) noexcept

View file

@ -6,6 +6,7 @@
#include <cstdint> #include <cstdint>
#include <string_view> #include <string_view>
#include <utility> #include <utility>
#include <variant>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
@ -43,7 +44,7 @@ struct EffectList {
ALuint type; ALuint type;
ALenum val; ALenum val;
}; };
extern const std::array<EffectList,16> gEffectList; DECL_HIDDEN extern const std::array<EffectList,16> gEffectList;
using EffectHandlerVariant = std::variant<NullEffectHandler,ReverbEffectHandler, using EffectHandlerVariant = std::variant<NullEffectHandler,ReverbEffectHandler,
StdReverbEffectHandler,AutowahEffectHandler,ChorusEffectHandler,CompressorEffectHandler, StdReverbEffectHandler,AutowahEffectHandler,ChorusEffectHandler,CompressorEffectHandler,
@ -56,7 +57,7 @@ struct ALeffect {
ALenum type{AL_EFFECT_NULL}; ALenum type{AL_EFFECT_NULL};
EffectHandlerVariant PropsVariant; EffectHandlerVariant PropsVariant;
EffectProps Props{}; EffectProps Props;
/* Self ID */ /* Self ID */
ALuint id{0u}; ALuint id{0u};

View file

@ -4,15 +4,13 @@
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include <algorithm>
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
@ -35,75 +33,68 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps AutowahEffectProps{genDefaultProps()}; const EffectProps AutowahEffectProps{genDefaultProps()};
void AutowahEffectHandler::SetParami(AutowahProps&, ALenum param, int) void AutowahEffectHandler::SetParami(ALCcontext *context, AutowahProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer property {:#04x}", as_unsigned(param)); }
void AutowahEffectHandler::SetParamiv(AutowahProps&, ALenum param, const int*) void AutowahEffectHandler::SetParamiv(ALCcontext *context, AutowahProps&, ALenum param, const int*)
{ { context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer vector property {:#04x}", as_unsigned(param)); }
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
param};
}
void AutowahEffectHandler::SetParamf(AutowahProps &props, ALenum param, float val) void AutowahEffectHandler::SetParamf(ALCcontext *context, AutowahProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_AUTOWAH_ATTACK_TIME: case AL_AUTOWAH_ATTACK_TIME:
if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
throw effect_exception{AL_INVALID_VALUE, "Autowah attack time out of range"}; context->throw_error(AL_INVALID_VALUE, "Autowah attack time out of range");
props.AttackTime = val; props.AttackTime = val;
break; return;
case AL_AUTOWAH_RELEASE_TIME: case AL_AUTOWAH_RELEASE_TIME:
if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME))
throw effect_exception{AL_INVALID_VALUE, "Autowah release time out of range"}; context->throw_error(AL_INVALID_VALUE, "Autowah release time out of range");
props.ReleaseTime = val; props.ReleaseTime = val;
break; return;
case AL_AUTOWAH_RESONANCE: case AL_AUTOWAH_RESONANCE:
if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
throw effect_exception{AL_INVALID_VALUE, "Autowah resonance out of range"}; context->throw_error(AL_INVALID_VALUE, "Autowah resonance out of range");
props.Resonance = val; props.Resonance = val;
break; return;
case AL_AUTOWAH_PEAK_GAIN: case AL_AUTOWAH_PEAK_GAIN:
if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Autowah peak gain out of range"}; context->throw_error(AL_INVALID_VALUE, "Autowah peak gain out of range");
props.PeakGain = val; props.PeakGain = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param};
} }
}
void AutowahEffectHandler::SetParamfv(AutowahProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void AutowahEffectHandler::GetParami(const AutowahProps&, ALenum param, int*) context->throw_error(AL_INVALID_ENUM, "Invalid autowah float property {:#04x}",
{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } as_unsigned(param));
void AutowahEffectHandler::GetParamiv(const AutowahProps&, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
param};
} }
void AutowahEffectHandler::SetParamfv(ALCcontext *context, AutowahProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void AutowahEffectHandler::GetParamf(const AutowahProps &props, ALenum param, float *val) void AutowahEffectHandler::GetParami(ALCcontext *context, const AutowahProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer property {:#04x}", as_unsigned(param)); }
void AutowahEffectHandler::GetParamiv(ALCcontext *context, const AutowahProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer vector property {:#04x}", as_unsigned(param)); }
void AutowahEffectHandler::GetParamf(ALCcontext *context, const AutowahProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; break; case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; return;
case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; break; case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; return;
case AL_AUTOWAH_RESONANCE: *val = props.Resonance; break; case AL_AUTOWAH_RESONANCE: *val = props.Resonance; return;
case AL_AUTOWAH_PEAK_GAIN: *val = props.PeakGain; break; case AL_AUTOWAH_PEAK_GAIN: *val = props.PeakGain; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid autowah float property {:#04x}",
as_unsigned(param));
} }
void AutowahEffectHandler::GetParamfv(const AutowahProps &props, ALenum param, float *vals) void AutowahEffectHandler::GetParamfv(ALCcontext *context, const AutowahProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
using AutowahCommitter = EaxCommitter<EaxAutowahCommitter>; using AutowahCommitter = EaxCommitter<EaxAutowahCommitter>;

View file

@ -7,9 +7,12 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/context.h"
#include "alnumeric.h"
#include "core/logging.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include <cassert> #include <cassert>
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
@ -41,7 +44,8 @@ constexpr ALenum EnumFromWaveform(ChorusWaveform type)
case ChorusWaveform::Sinusoid: return AL_CHORUS_WAVEFORM_SINUSOID; case ChorusWaveform::Sinusoid: return AL_CHORUS_WAVEFORM_SINUSOID;
case ChorusWaveform::Triangle: return AL_CHORUS_WAVEFORM_TRIANGLE; case ChorusWaveform::Triangle: return AL_CHORUS_WAVEFORM_TRIANGLE;
} }
throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast<int>(type))}; throw std::runtime_error{fmt::format("Invalid chorus waveform: {}",
int{al::to_underlying(type)})};
} }
constexpr EffectProps genDefaultChorusProps() noexcept constexpr EffectProps genDefaultChorusProps() noexcept
@ -72,7 +76,7 @@ constexpr EffectProps genDefaultFlangerProps() noexcept
const EffectProps ChorusEffectProps{genDefaultChorusProps()}; const EffectProps ChorusEffectProps{genDefaultChorusProps()};
void ChorusEffectHandler::SetParami(ChorusProps &props, ALenum param, int val) void ChorusEffectHandler::SetParami(ALCcontext *context, ChorusProps &props, ALenum param, int val)
{ {
switch(param) switch(param)
{ {
@ -80,89 +84,90 @@ void ChorusEffectHandler::SetParami(ChorusProps &props, ALenum param, int val)
if(auto formopt = WaveformFromEnum(val)) if(auto formopt = WaveformFromEnum(val))
props.Waveform = *formopt; props.Waveform = *formopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val}; context->throw_error(AL_INVALID_VALUE, "Invalid chorus waveform: {:#04x}",
break; as_unsigned(val));
return;
case AL_CHORUS_PHASE: case AL_CHORUS_PHASE:
if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE))
throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val}; context->throw_error(AL_INVALID_VALUE, "Chorus phase out of range: {}", val);
props.Phase = val; props.Phase = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid chorus integer property {:#04x}",
as_unsigned(param));
} }
void ChorusEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals) void ChorusEffectHandler::SetParamiv(ALCcontext *context, ChorusProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); } { SetParami(context, props, param, *vals); }
void ChorusEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val) void ChorusEffectHandler::SetParamf(ALCcontext *context, ChorusProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_CHORUS_RATE: case AL_CHORUS_RATE:
if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE))
throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val}; context->throw_error(AL_INVALID_VALUE, "Chorus rate out of range: {:f}", val);
props.Rate = val; props.Rate = val;
break; return;
case AL_CHORUS_DEPTH: case AL_CHORUS_DEPTH:
if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH))
throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val}; context->throw_error(AL_INVALID_VALUE, "Chorus depth out of range: {:f}", val);
props.Depth = val; props.Depth = val;
break; return;
case AL_CHORUS_FEEDBACK: case AL_CHORUS_FEEDBACK:
if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK))
throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val}; context->throw_error(AL_INVALID_VALUE, "Chorus feedback out of range: {:f}", val);
props.Feedback = val; props.Feedback = val;
break; return;
case AL_CHORUS_DELAY: case AL_CHORUS_DELAY:
if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY))
throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val}; context->throw_error(AL_INVALID_VALUE, "Chorus delay out of range: {:f}", val);
props.Delay = val; props.Delay = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param};
} }
}
void ChorusEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void ChorusEffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val) context->throw_error(AL_INVALID_ENUM, "Invalid chorus float property {:#04x}",
as_unsigned(param));
}
void ChorusEffectHandler::SetParamfv(ALCcontext *context, ChorusProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void ChorusEffectHandler::GetParami(ALCcontext *context, const ChorusProps &props, ALenum param, int *val)
{ {
switch(param) switch(param)
{ {
case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return;
case AL_CHORUS_PHASE: *val = props.Phase; break; case AL_CHORUS_PHASE: *val = props.Phase; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid chorus integer property {:#04x}",
as_unsigned(param));
} }
void ChorusEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals) void ChorusEffectHandler::GetParamiv(ALCcontext *context, const ChorusProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); } { GetParami(context, props, param, vals); }
void ChorusEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val) void ChorusEffectHandler::GetParamf(ALCcontext *context, const ChorusProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_CHORUS_RATE: *val = props.Rate; break; case AL_CHORUS_RATE: *val = props.Rate; return;
case AL_CHORUS_DEPTH: *val = props.Depth; break; case AL_CHORUS_DEPTH: *val = props.Depth; return;
case AL_CHORUS_FEEDBACK: *val = props.Feedback; break; case AL_CHORUS_FEEDBACK: *val = props.Feedback; return;
case AL_CHORUS_DELAY: *val = props.Delay; break; case AL_CHORUS_DELAY: *val = props.Delay; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid chorus float property {:#04x}",
as_unsigned(param));
} }
void ChorusEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals) void ChorusEffectHandler::GetParamfv(ALCcontext *context, const ChorusProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
void FlangerEffectHandler::SetParami(ChorusProps &props, ALenum param, int val) void FlangerEffectHandler::SetParami(ALCcontext *context, ChorusProps &props, ALenum param, int val)
{ {
switch(param) switch(param)
{ {
@ -170,87 +175,88 @@ void FlangerEffectHandler::SetParami(ChorusProps &props, ALenum param, int val)
if(auto formopt = WaveformFromEnum(val)) if(auto formopt = WaveformFromEnum(val))
props.Waveform = *formopt; props.Waveform = *formopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val}; context->throw_error(AL_INVALID_VALUE, "Invalid flanger waveform: {:#04x}",
break; as_unsigned(val));
return;
case AL_FLANGER_PHASE: case AL_FLANGER_PHASE:
if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE))
throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val}; context->throw_error(AL_INVALID_VALUE, "Flanger phase out of range: {}", val);
props.Phase = val; props.Phase = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid flanger integer property {:#04x}",
as_unsigned(param));
} }
void FlangerEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals) void FlangerEffectHandler::SetParamiv(ALCcontext *context, ChorusProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); } { SetParami(context, props, param, *vals); }
void FlangerEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val) void FlangerEffectHandler::SetParamf(ALCcontext *context, ChorusProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_FLANGER_RATE: case AL_FLANGER_RATE:
if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE))
throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val}; context->throw_error(AL_INVALID_VALUE, "Flanger rate out of range: {:f}", val);
props.Rate = val; props.Rate = val;
break; return;
case AL_FLANGER_DEPTH: case AL_FLANGER_DEPTH:
if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH))
throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val}; context->throw_error(AL_INVALID_VALUE, "Flanger depth out of range: {:f}", val);
props.Depth = val; props.Depth = val;
break; return;
case AL_FLANGER_FEEDBACK: case AL_FLANGER_FEEDBACK:
if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK))
throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val}; context->throw_error(AL_INVALID_VALUE, "Flanger feedback out of range: {:f}", val);
props.Feedback = val; props.Feedback = val;
break; return;
case AL_FLANGER_DELAY: case AL_FLANGER_DELAY:
if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY))
throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val}; context->throw_error(AL_INVALID_VALUE, "Flanger delay out of range: {:f}", val);
props.Delay = val; props.Delay = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param};
} }
}
void FlangerEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void FlangerEffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val) context->throw_error(AL_INVALID_ENUM, "Invalid flanger float property {:#04x}",
as_unsigned(param));
}
void FlangerEffectHandler::SetParamfv(ALCcontext *context, ChorusProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void FlangerEffectHandler::GetParami(ALCcontext *context, const ChorusProps &props, ALenum param, int *val)
{ {
switch(param) switch(param)
{ {
case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return;
case AL_FLANGER_PHASE: *val = props.Phase; break; case AL_FLANGER_PHASE: *val = props.Phase; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid flanger integer property {:#04x}",
as_unsigned(param));
} }
void FlangerEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals) void FlangerEffectHandler::GetParamiv(ALCcontext *context, const ChorusProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); } { GetParami(context, props, param, vals); }
void FlangerEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val) void FlangerEffectHandler::GetParamf(ALCcontext *context, const ChorusProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_FLANGER_RATE: *val = props.Rate; break; case AL_FLANGER_RATE: *val = props.Rate; return;
case AL_FLANGER_DEPTH: *val = props.Depth; break; case AL_FLANGER_DEPTH: *val = props.Depth; return;
case AL_FLANGER_FEEDBACK: *val = props.Feedback; break; case AL_FLANGER_FEEDBACK: *val = props.Feedback; return;
case AL_FLANGER_DELAY: *val = props.Delay; break; case AL_FLANGER_DELAY: *val = props.Delay; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid flanger float property {:#04x}",
as_unsigned(param));
} }
void FlangerEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals) void FlangerEffectHandler::GetParamfv(ALCcontext *context, const ChorusProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
struct EaxChorusTraits { struct EaxChorusTraits {
@ -558,6 +564,17 @@ public:
al_props_.Depth = props.flDepth; al_props_.Depth = props.flDepth;
al_props_.Feedback = props.flFeedback; al_props_.Feedback = props.flFeedback;
al_props_.Delay = props.flDelay; al_props_.Delay = props.flDelay;
if(EaxTraceCommits) UNLIKELY
{
TRACE("Chorus/flanger commit:\n"
" Waveform: {}\n"
" Phase: {}\n"
" Rate: {:f}\n"
" Depth: {:f}\n"
" Feedback: {:f}\n"
" Delay: {:f}", al::to_underlying(al_props_.Waveform), al_props_.Phase,
al_props_.Rate, al_props_.Depth, al_props_.Feedback, al_props_.Delay);
}
return true; return true;
} }

View file

@ -4,11 +4,11 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
@ -28,53 +28,46 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps CompressorEffectProps{genDefaultProps()}; const EffectProps CompressorEffectProps{genDefaultProps()};
void CompressorEffectHandler::SetParami(CompressorProps &props, ALenum param, int val) void CompressorEffectHandler::SetParami(ALCcontext *context, CompressorProps &props, ALenum param, int val)
{ {
switch(param) switch(param)
{ {
case AL_COMPRESSOR_ONOFF: case AL_COMPRESSOR_ONOFF:
if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF))
throw effect_exception{AL_INVALID_VALUE, "Compressor state out of range"}; context->throw_error(AL_INVALID_VALUE, "Compressor state out of range");
props.OnOff = (val != AL_FALSE); props.OnOff = (val != AL_FALSE);
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
param};
} }
}
void CompressorEffectHandler::SetParamiv(CompressorProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); }
void CompressorEffectHandler::SetParamf(CompressorProps&, ALenum param, float)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; }
void CompressorEffectHandler::SetParamfv(CompressorProps&, ALenum param, const float*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x",
param};
}
void CompressorEffectHandler::GetParami(const CompressorProps &props, ALenum param, int *val) context->throw_error(AL_INVALID_ENUM, "Invalid compressor integer property {:#04x}",
as_unsigned(param));
}
void CompressorEffectHandler::SetParamiv(ALCcontext *context, CompressorProps &props, ALenum param, const int *vals)
{ SetParami(context, props, param, *vals); }
void CompressorEffectHandler::SetParamf(ALCcontext *context, CompressorProps&, ALenum param, float)
{ context->throw_error(AL_INVALID_ENUM, "Invalid compressor float property {:#04x}", as_unsigned(param)); }
void CompressorEffectHandler::SetParamfv(ALCcontext *context, CompressorProps&, ALenum param, const float*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid compressor float-vector property {:#04x}", as_unsigned(param)); }
void CompressorEffectHandler::GetParami(ALCcontext *context, const CompressorProps &props, ALenum param, int *val)
{ {
switch(param) switch(param)
{ {
case AL_COMPRESSOR_ONOFF: *val = props.OnOff; break; case AL_COMPRESSOR_ONOFF: *val = props.OnOff; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid compressor integer property {:#04x}",
as_unsigned(param));
} }
void CompressorEffectHandler::GetParamiv(const CompressorProps &props, ALenum param, int *vals) void CompressorEffectHandler::GetParamiv(ALCcontext *context, const CompressorProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); } { GetParami(context, props, param, vals); }
void CompressorEffectHandler::GetParamf(const CompressorProps&, ALenum param, float*) void CompressorEffectHandler::GetParamf(ALCcontext *context, const CompressorProps&, ALenum param, float*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid compressor float property {:#04x}", as_unsigned(param)); }
void CompressorEffectHandler::GetParamfv(const CompressorProps&, ALenum param, float*) void CompressorEffectHandler::GetParamfv(ALCcontext *context, const CompressorProps&, ALenum param, float*)
{ { context->throw_error(AL_INVALID_ENUM, "Invalid compressor float-vector property {:#04x}", as_unsigned(param)); }
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x",
param};
}
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
using CompressorCommitter = EaxCommitter<EaxCompressorCommitter>; using CompressorCommitter = EaxCommitter<EaxCompressorCommitter>;

View file

@ -7,10 +7,10 @@
#include "AL/al.h" #include "AL/al.h"
#include "alc/context.h"
#include "alc/inprogext.h" #include "alc/inprogext.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alspan.h" #include "alspan.h"
#include "core/effects/base.h"
#include "effects.h" #include "effects.h"
@ -28,90 +28,49 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps ConvolutionEffectProps{genDefaultProps()}; const EffectProps ConvolutionEffectProps{genDefaultProps()};
void ConvolutionEffectHandler::SetParami(ConvolutionProps& /*props*/, ALenum param, int /*val*/) void ConvolutionEffectHandler::SetParami(ALCcontext *context, ConvolutionProps& /*props*/, ALenum param, int /*val*/)
{ { context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect integer property {:#04x}", as_unsigned(param)); }
switch(param) void ConvolutionEffectHandler::SetParamiv(ALCcontext *context, ConvolutionProps &props, ALenum param, const int *vals)
{ { SetParami(context, props, param, *vals); }
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x", void ConvolutionEffectHandler::SetParamf(ALCcontext *context, ConvolutionProps& /*props*/, ALenum param, float /*val*/)
param}; { context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect float property {:#04x}", as_unsigned(param)); }
} void ConvolutionEffectHandler::SetParamfv(ALCcontext *context, ConvolutionProps &props, ALenum param, const float *values)
}
void ConvolutionEffectHandler::SetParamiv(ConvolutionProps &props, ALenum param, const int *vals)
{
switch(param)
{
default:
SetParami(props, param, *vals);
}
}
void ConvolutionEffectHandler::SetParamf(ConvolutionProps& /*props*/, ALenum param, float /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x",
param};
}
}
void ConvolutionEffectHandler::SetParamfv(ConvolutionProps &props, ALenum param, const float *values)
{ {
static constexpr auto finite_checker = [](float val) -> bool { return std::isfinite(val); }; static constexpr auto finite_checker = [](float val) -> bool { return std::isfinite(val); };
al::span<const float> vals;
switch(param) switch(param)
{ {
case AL_CONVOLUTION_ORIENTATION_SOFT: case AL_CONVOLUTION_ORIENTATION_SOFT:
vals = {values, 6_uz}; auto vals = al::span{values, 6_uz};
if(!std::all_of(vals.cbegin(), vals.cend(), finite_checker)) if(!std::all_of(vals.cbegin(), vals.cend(), finite_checker))
throw effect_exception{AL_INVALID_VALUE, "Property 0x%04x value out of range", param}; context->throw_error(AL_INVALID_VALUE, "Convolution orientation out of range", param);
std::copy_n(vals.cbegin(), props.OrientAt.size(), props.OrientAt.begin()); std::copy_n(vals.cbegin(), props.OrientAt.size(), props.OrientAt.begin());
std::copy_n(vals.cbegin()+3, props.OrientUp.size(), props.OrientUp.begin()); std::copy_n(vals.cbegin()+3, props.OrientUp.size(), props.OrientUp.begin());
break; return;
default:
SetParamf(props, param, *values);
} }
SetParamf(context, props, param, *values);
} }
void ConvolutionEffectHandler::GetParami(const ConvolutionProps& /*props*/, ALenum param, int* /*val*/) void ConvolutionEffectHandler::GetParami(ALCcontext *context, const ConvolutionProps& /*props*/, ALenum param, int* /*val*/)
{ context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect integer property {:#04x}", as_unsigned(param)); }
void ConvolutionEffectHandler::GetParamiv(ALCcontext *context, const ConvolutionProps &props, ALenum param, int *vals)
{ GetParami(context, props, param, vals); }
void ConvolutionEffectHandler::GetParamf(ALCcontext *context, const ConvolutionProps& /*props*/, ALenum param, float* /*val*/)
{ context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect float property {:#04x}", as_unsigned(param)); }
void ConvolutionEffectHandler::GetParamfv(ALCcontext *context, const ConvolutionProps &props, ALenum param, float *values)
{ {
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x",
param};
}
}
void ConvolutionEffectHandler::GetParamiv(const ConvolutionProps &props, ALenum param, int *vals)
{
switch(param)
{
default:
GetParami(props, param, vals);
}
}
void ConvolutionEffectHandler::GetParamf(const ConvolutionProps& /*props*/, ALenum param, float* /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x",
param};
}
}
void ConvolutionEffectHandler::GetParamfv(const ConvolutionProps &props, ALenum param, float *values)
{
al::span<float> vals;
switch(param) switch(param)
{ {
case AL_CONVOLUTION_ORIENTATION_SOFT: case AL_CONVOLUTION_ORIENTATION_SOFT:
vals = {values, 6_uz}; auto vals = al::span{values, 6_uz};
std::copy(props.OrientAt.cbegin(), props.OrientAt.cend(), vals.begin()); std::copy(props.OrientAt.cbegin(), props.OrientAt.cend(), vals.begin());
std::copy(props.OrientUp.cbegin(), props.OrientUp.cend(), vals.begin()+3); std::copy(props.OrientUp.cbegin(), props.OrientUp.cend(), vals.begin()+3);
break; return;
default:
GetParamf(props, param, values);
} }
GetParamf(context, props, param, values);
} }

View file

@ -6,7 +6,8 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/alext.h" #include "AL/alext.h"
#include "alc/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
@ -32,91 +33,81 @@ constexpr EffectProps genDefaultLfeProps() noexcept
const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()}; const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()};
void DedicatedDialogEffectHandler::SetParami(DedicatedProps&, ALenum param, int) void DedicatedDialogEffectHandler::SetParami(ALCcontext *context, DedicatedProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); }
void DedicatedDialogEffectHandler::SetParamiv(DedicatedProps&, ALenum param, const int*) void DedicatedDialogEffectHandler::SetParamiv(ALCcontext *context, DedicatedProps&, ALenum param, const int*)
{ { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); }
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", void DedicatedDialogEffectHandler::SetParamf(ALCcontext *context, DedicatedProps &props, ALenum param, float val)
param};
}
void DedicatedDialogEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_DEDICATED_GAIN: case AL_DEDICATED_GAIN:
if(!(val >= 0.0f && std::isfinite(val))) if(!(val >= 0.0f && std::isfinite(val)))
throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; context->throw_error(AL_INVALID_VALUE, "Dedicated gain out of range");
props.Gain = val; props.Gain = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
} }
}
void DedicatedDialogEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void DedicatedDialogEffectHandler::GetParami(const DedicatedProps&, ALenum param, int*) context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}",
{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } as_unsigned(param));
void DedicatedDialogEffectHandler::GetParamiv(const DedicatedProps&, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
param};
} }
void DedicatedDialogEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val) void DedicatedDialogEffectHandler::SetParamfv(ALCcontext *context, DedicatedProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void DedicatedDialogEffectHandler::GetParami(ALCcontext *context, const DedicatedProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); }
void DedicatedDialogEffectHandler::GetParamiv(ALCcontext *context, const DedicatedProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); }
void DedicatedDialogEffectHandler::GetParamf(ALCcontext *context, const DedicatedProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_DEDICATED_GAIN: *val = props.Gain; break; case AL_DEDICATED_GAIN: *val = props.Gain; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}",
as_unsigned(param));
} }
void DedicatedDialogEffectHandler::GetParamfv(const DedicatedProps &props, ALenum param, float *vals) void DedicatedDialogEffectHandler::GetParamfv(ALCcontext *context, const DedicatedProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()}; const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()};
void DedicatedLfeEffectHandler::SetParami(DedicatedProps&, ALenum param, int) void DedicatedLfeEffectHandler::SetParami(ALCcontext *context, DedicatedProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); }
void DedicatedLfeEffectHandler::SetParamiv(DedicatedProps&, ALenum param, const int*) void DedicatedLfeEffectHandler::SetParamiv(ALCcontext *context, DedicatedProps&, ALenum param, const int*)
{ { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); }
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", void DedicatedLfeEffectHandler::SetParamf(ALCcontext *context, DedicatedProps &props, ALenum param, float val)
param};
}
void DedicatedLfeEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_DEDICATED_GAIN: case AL_DEDICATED_GAIN:
if(!(val >= 0.0f && std::isfinite(val))) if(!(val >= 0.0f && std::isfinite(val)))
throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; context->throw_error(AL_INVALID_VALUE, "Dedicated gain out of range");
props.Gain = val; props.Gain = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
} }
}
void DedicatedLfeEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void DedicatedLfeEffectHandler::GetParami(const DedicatedProps&, ALenum param, int*) context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}",
{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } as_unsigned(param));
void DedicatedLfeEffectHandler::GetParamiv(const DedicatedProps&, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
param};
} }
void DedicatedLfeEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val) void DedicatedLfeEffectHandler::SetParamfv(ALCcontext *context, DedicatedProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void DedicatedLfeEffectHandler::GetParami(ALCcontext *context, const DedicatedProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); }
void DedicatedLfeEffectHandler::GetParamiv(ALCcontext *context, const DedicatedProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); }
void DedicatedLfeEffectHandler::GetParamf(ALCcontext *context, const DedicatedProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_DEDICATED_GAIN: *val = props.Gain; break; case AL_DEDICATED_GAIN: *val = props.Gain; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}",
as_unsigned(param));
} }
void DedicatedLfeEffectHandler::GetParamfv(const DedicatedProps &props, ALenum param, float *vals) void DedicatedLfeEffectHandler::GetParamfv(ALCcontext *context, const DedicatedProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }

View file

@ -4,11 +4,11 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
@ -32,80 +32,76 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps DistortionEffectProps{genDefaultProps()}; const EffectProps DistortionEffectProps{genDefaultProps()};
void DistortionEffectHandler::SetParami(DistortionProps&, ALenum param, int) void DistortionEffectHandler::SetParami(ALCcontext *context, DistortionProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer property {:#04x}", as_unsigned(param)); }
void DistortionEffectHandler::SetParamiv(DistortionProps&, ALenum param, const int*) void DistortionEffectHandler::SetParamiv(ALCcontext *context, DistortionProps&, ALenum param, const int*)
{ { context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer-vector property {:#04x}", as_unsigned(param)); }
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
param}; void DistortionEffectHandler::SetParamf(ALCcontext *context, DistortionProps &props, ALenum param, float val)
}
void DistortionEffectHandler::SetParamf(DistortionProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_DISTORTION_EDGE: case AL_DISTORTION_EDGE:
if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE))
throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"}; context->throw_error(AL_INVALID_VALUE, "Distortion edge out of range");
props.Edge = val; props.Edge = val;
break; return;
case AL_DISTORTION_GAIN: case AL_DISTORTION_GAIN:
if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"}; context->throw_error(AL_INVALID_VALUE, "Distortion gain out of range");
props.Gain = val; props.Gain = val;
break; return;
case AL_DISTORTION_LOWPASS_CUTOFF: case AL_DISTORTION_LOWPASS_CUTOFF:
if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF))
throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"}; context->throw_error(AL_INVALID_VALUE, "Distortion low-pass cutoff out of range");
props.LowpassCutoff = val; props.LowpassCutoff = val;
break; return;
case AL_DISTORTION_EQCENTER: case AL_DISTORTION_EQCENTER:
if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER))
throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"}; context->throw_error(AL_INVALID_VALUE, "Distortion EQ center out of range");
props.EQCenter = val; props.EQCenter = val;
break; return;
case AL_DISTORTION_EQBANDWIDTH: case AL_DISTORTION_EQBANDWIDTH:
if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH))
throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"}; context->throw_error(AL_INVALID_VALUE, "Distortion EQ bandwidth out of range");
props.EQBandwidth = val; props.EQBandwidth = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
} }
}
void DistortionEffectHandler::SetParamfv(DistortionProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void DistortionEffectHandler::GetParami(const DistortionProps&, ALenum param, int*) context->throw_error(AL_INVALID_ENUM, "Invalid distortion float property {:#04x}",
{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } as_unsigned(param));
void DistortionEffectHandler::GetParamiv(const DistortionProps&, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
param};
} }
void DistortionEffectHandler::GetParamf(const DistortionProps &props, ALenum param, float *val) void DistortionEffectHandler::SetParamfv(ALCcontext *context, DistortionProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void DistortionEffectHandler::GetParami(ALCcontext *context, const DistortionProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer property {:#04x}", as_unsigned(param)); }
void DistortionEffectHandler::GetParamiv(ALCcontext *context, const DistortionProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer-vector property {:#04x}", as_unsigned(param)); }
void DistortionEffectHandler::GetParamf(ALCcontext *context, const DistortionProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_DISTORTION_EDGE: *val = props.Edge; break; case AL_DISTORTION_EDGE: *val = props.Edge; return;
case AL_DISTORTION_GAIN: *val = props.Gain; break; case AL_DISTORTION_GAIN: *val = props.Gain; return;
case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; break; case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; return;
case AL_DISTORTION_EQCENTER: *val = props.EQCenter; break; case AL_DISTORTION_EQCENTER: *val = props.EQCenter; return;
case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; break; case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid distortion float property {:#04x}",
as_unsigned(param));
} }
void DistortionEffectHandler::GetParamfv(const DistortionProps &props, ALenum param, float *vals) void DistortionEffectHandler::GetParamfv(ALCcontext *context, const DistortionProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
using DistortionCommitter = EaxCommitter<EaxDistortionCommitter>; using DistortionCommitter = EaxCommitter<EaxDistortionCommitter>;

View file

@ -4,11 +4,11 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
@ -35,74 +35,74 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps EchoEffectProps{genDefaultProps()}; const EffectProps EchoEffectProps{genDefaultProps()};
void EchoEffectHandler::SetParami(EchoProps&, ALenum param, int) void EchoEffectHandler::SetParami(ALCcontext *context, EchoProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid echo integer property {:#04x}", as_unsigned(param)); }
void EchoEffectHandler::SetParamiv(EchoProps&, ALenum param, const int*) void EchoEffectHandler::SetParamiv(ALCcontext *context, EchoProps&, ALenum param, const int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid echo integer-vector property {:#04x}", as_unsigned(param)); }
void EchoEffectHandler::SetParamf(EchoProps &props, ALenum param, float val) void EchoEffectHandler::SetParamf(ALCcontext *context, EchoProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_ECHO_DELAY: case AL_ECHO_DELAY:
if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
throw effect_exception{AL_INVALID_VALUE, "Echo delay out of range"}; context->throw_error(AL_INVALID_VALUE, "Echo delay out of range");
props.Delay = val; props.Delay = val;
break; return;
case AL_ECHO_LRDELAY: case AL_ECHO_LRDELAY:
if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY))
throw effect_exception{AL_INVALID_VALUE, "Echo LR delay out of range"}; context->throw_error(AL_INVALID_VALUE, "Echo LR delay out of range");
props.LRDelay = val; props.LRDelay = val;
break; return;
case AL_ECHO_DAMPING: case AL_ECHO_DAMPING:
if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING))
throw effect_exception{AL_INVALID_VALUE, "Echo damping out of range"}; context->throw_error(AL_INVALID_VALUE, "Echo damping out of range");
props.Damping = val; props.Damping = val;
break; return;
case AL_ECHO_FEEDBACK: case AL_ECHO_FEEDBACK:
if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK))
throw effect_exception{AL_INVALID_VALUE, "Echo feedback out of range"}; context->throw_error(AL_INVALID_VALUE, "Echo feedback out of range");
props.Feedback = val; props.Feedback = val;
break; return;
case AL_ECHO_SPREAD: case AL_ECHO_SPREAD:
if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD))
throw effect_exception{AL_INVALID_VALUE, "Echo spread out of range"}; context->throw_error(AL_INVALID_VALUE, "Echo spread out of range");
props.Spread = val; props.Spread = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param};
} }
}
void EchoEffectHandler::SetParamfv(EchoProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void EchoEffectHandler::GetParami(const EchoProps&, ALenum param, int*) context->throw_error(AL_INVALID_ENUM, "Invalid echo float property {:#04x}",
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } as_unsigned(param));
void EchoEffectHandler::GetParamiv(const EchoProps&, ALenum param, int*) }
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } void EchoEffectHandler::SetParamfv(ALCcontext *context, EchoProps &props, ALenum param, const float *vals)
void EchoEffectHandler::GetParamf(const EchoProps &props, ALenum param, float *val) { SetParamf(context, props, param, *vals); }
void EchoEffectHandler::GetParami(ALCcontext *context, const EchoProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid echo integer property {:#04x}", as_unsigned(param)); }
void EchoEffectHandler::GetParamiv(ALCcontext *context, const EchoProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid echo integer-vector property {:#04x}", as_unsigned(param)); }
void EchoEffectHandler::GetParamf(ALCcontext *context, const EchoProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_ECHO_DELAY: *val = props.Delay; break; case AL_ECHO_DELAY: *val = props.Delay; return;
case AL_ECHO_LRDELAY: *val = props.LRDelay; break; case AL_ECHO_LRDELAY: *val = props.LRDelay; return;
case AL_ECHO_DAMPING: *val = props.Damping; break; case AL_ECHO_DAMPING: *val = props.Damping; return;
case AL_ECHO_FEEDBACK: *val = props.Feedback; break; case AL_ECHO_FEEDBACK: *val = props.Feedback; return;
case AL_ECHO_SPREAD: *val = props.Spread; break; case AL_ECHO_SPREAD: *val = props.Spread; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid echo float property {:#04x}",
as_unsigned(param));
} }
void EchoEffectHandler::GetParamfv(const EchoProps &props, ALenum param, float *vals) void EchoEffectHandler::GetParamfv(ALCcontext *context, const EchoProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
using EchoCommitter = EaxCommitter<EaxEchoCommitter>; using EchoCommitter = EaxCommitter<EaxEchoCommitter>;

View file

@ -3,23 +3,24 @@
#include <variant> #include <variant>
#include "AL/alc.h"
#include "AL/al.h" #include "AL/al.h"
#include "al/error.h"
#include "core/effects/base.h" #include "core/effects/base.h"
#include "opthelpers.h"
#define DECL_HANDLER(N, T) \ #define DECL_HANDLER(N, T) \
struct N { \ struct N { \
using prop_type = T; \ using prop_type = T; \
\ \
static void SetParami(prop_type &props, ALenum param, int val); \ static void SetParami(ALCcontext *context, prop_type &props, ALenum param, int val); \
static void SetParamiv(prop_type &props, ALenum param, const int *vals); \ static void SetParamiv(ALCcontext *context, prop_type &props, ALenum param, const int *vals); \
static void SetParamf(prop_type &props, ALenum param, float val); \ static void SetParamf(ALCcontext *context, prop_type &props, ALenum param, float val); \
static void SetParamfv(prop_type &props, ALenum param, const float *vals);\ static void SetParamfv(ALCcontext *context, prop_type &props, ALenum param, const float *vals);\
static void GetParami(const prop_type &props, ALenum param, int *val); \ static void GetParami(ALCcontext *context, const prop_type &props, ALenum param, int *val); \
static void GetParamiv(const prop_type &props, ALenum param, int *vals); \ static void GetParamiv(ALCcontext *context, const prop_type &props, ALenum param, int *vals); \
static void GetParamf(const prop_type &props, ALenum param, float *val); \ static void GetParamf(ALCcontext *context, const prop_type &props, ALenum param, float *val); \
static void GetParamfv(const prop_type &props, ALenum param, float *vals);\ static void GetParamfv(ALCcontext *context, const prop_type &props, ALenum param, float *vals);\
}; };
DECL_HANDLER(NullEffectHandler, std::monostate) DECL_HANDLER(NullEffectHandler, std::monostate)
DECL_HANDLER(ReverbEffectHandler, ReverbProps) DECL_HANDLER(ReverbEffectHandler, ReverbProps)
@ -41,26 +42,23 @@ DECL_HANDLER(ConvolutionEffectHandler, ConvolutionProps)
#undef DECL_HANDLER #undef DECL_HANDLER
using effect_exception = al::context_error;
/* Default properties for the given effect types. */ /* Default properties for the given effect types. */
extern const EffectProps NullEffectProps; DECL_HIDDEN extern const EffectProps NullEffectProps;
extern const EffectProps ReverbEffectProps; DECL_HIDDEN extern const EffectProps ReverbEffectProps;
extern const EffectProps StdReverbEffectProps; DECL_HIDDEN extern const EffectProps StdReverbEffectProps;
extern const EffectProps AutowahEffectProps; DECL_HIDDEN extern const EffectProps AutowahEffectProps;
extern const EffectProps ChorusEffectProps; DECL_HIDDEN extern const EffectProps ChorusEffectProps;
extern const EffectProps CompressorEffectProps; DECL_HIDDEN extern const EffectProps CompressorEffectProps;
extern const EffectProps DistortionEffectProps; DECL_HIDDEN extern const EffectProps DistortionEffectProps;
extern const EffectProps EchoEffectProps; DECL_HIDDEN extern const EffectProps EchoEffectProps;
extern const EffectProps EqualizerEffectProps; DECL_HIDDEN extern const EffectProps EqualizerEffectProps;
extern const EffectProps FlangerEffectProps; DECL_HIDDEN extern const EffectProps FlangerEffectProps;
extern const EffectProps FshifterEffectProps; DECL_HIDDEN extern const EffectProps FshifterEffectProps;
extern const EffectProps ModulatorEffectProps; DECL_HIDDEN extern const EffectProps ModulatorEffectProps;
extern const EffectProps PshifterEffectProps; DECL_HIDDEN extern const EffectProps PshifterEffectProps;
extern const EffectProps VmorpherEffectProps; DECL_HIDDEN extern const EffectProps VmorpherEffectProps;
extern const EffectProps DedicatedDialogEffectProps; DECL_HIDDEN extern const EffectProps DedicatedDialogEffectProps;
extern const EffectProps DedicatedLfeEffectProps; DECL_HIDDEN extern const EffectProps DedicatedLfeEffectProps;
extern const EffectProps ConvolutionEffectProps; DECL_HIDDEN extern const EffectProps ConvolutionEffectProps;
#endif /* AL_EFFECTS_EFFECTS_H */ #endif /* AL_EFFECTS_EFFECTS_H */

View file

@ -4,11 +4,11 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
@ -37,115 +37,109 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps EqualizerEffectProps{genDefaultProps()}; const EffectProps EqualizerEffectProps{genDefaultProps()};
void EqualizerEffectHandler::SetParami(EqualizerProps&, ALenum param, int) void EqualizerEffectHandler::SetParami(ALCcontext *context, EqualizerProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer property {:#04x}", as_unsigned(param)); }
void EqualizerEffectHandler::SetParamiv(EqualizerProps&, ALenum param, const int*) void EqualizerEffectHandler::SetParamiv(ALCcontext *context, EqualizerProps&, ALenum param, const int*)
{ { context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer-vector property {:#04x}", as_unsigned(param)); }
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", void EqualizerEffectHandler::SetParamf(ALCcontext *context, EqualizerProps &props, ALenum param, float val)
param};
}
void EqualizerEffectHandler::SetParamf(EqualizerProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_EQUALIZER_LOW_GAIN: case AL_EQUALIZER_LOW_GAIN:
if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band gain out of range"}; context->throw_error(AL_INVALID_VALUE, "Equalizer low-band gain out of range");
props.LowGain = val; props.LowGain = val;
break; return;
case AL_EQUALIZER_LOW_CUTOFF: case AL_EQUALIZER_LOW_CUTOFF:
if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF))
throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"}; context->throw_error(AL_INVALID_VALUE, "Equalizer low-band cutoff out of range");
props.LowCutoff = val; props.LowCutoff = val;
break; return;
case AL_EQUALIZER_MID1_GAIN: case AL_EQUALIZER_MID1_GAIN:
if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"}; context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band gain out of range");
props.Mid1Gain = val; props.Mid1Gain = val;
break; return;
case AL_EQUALIZER_MID1_CENTER: case AL_EQUALIZER_MID1_CENTER:
if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band center out of range"}; context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band center out of range");
props.Mid1Center = val; props.Mid1Center = val;
break; return;
case AL_EQUALIZER_MID1_WIDTH: case AL_EQUALIZER_MID1_WIDTH:
if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band width out of range"}; context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band width out of range");
props.Mid1Width = val; props.Mid1Width = val;
break; return;
case AL_EQUALIZER_MID2_GAIN: case AL_EQUALIZER_MID2_GAIN:
if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"}; context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band gain out of range");
props.Mid2Gain = val; props.Mid2Gain = val;
break; return;
case AL_EQUALIZER_MID2_CENTER: case AL_EQUALIZER_MID2_CENTER:
if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band center out of range"}; context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band center out of range");
props.Mid2Center = val; props.Mid2Center = val;
break; return;
case AL_EQUALIZER_MID2_WIDTH: case AL_EQUALIZER_MID2_WIDTH:
if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band width out of range"}; context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band width out of range");
props.Mid2Width = val; props.Mid2Width = val;
break; return;
case AL_EQUALIZER_HIGH_GAIN: case AL_EQUALIZER_HIGH_GAIN:
if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band gain out of range"}; context->throw_error(AL_INVALID_VALUE, "Equalizer high-band gain out of range");
props.HighGain = val; props.HighGain = val;
break; return;
case AL_EQUALIZER_HIGH_CUTOFF: case AL_EQUALIZER_HIGH_CUTOFF:
if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF))
throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"}; context->throw_error(AL_INVALID_VALUE, "Equalizer high-band cutoff out of range");
props.HighCutoff = val; props.HighCutoff = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param};
} }
}
void EqualizerEffectHandler::SetParamfv(EqualizerProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void EqualizerEffectHandler::GetParami(const EqualizerProps&, ALenum param, int*) context->throw_error(AL_INVALID_ENUM, "Invalid equalizer float property {:#04x}",
{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } as_unsigned(param));
void EqualizerEffectHandler::GetParamiv(const EqualizerProps&, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x",
param};
} }
void EqualizerEffectHandler::GetParamf(const EqualizerProps &props, ALenum param, float *val) void EqualizerEffectHandler::SetParamfv(ALCcontext *context, EqualizerProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void EqualizerEffectHandler::GetParami(ALCcontext *context, const EqualizerProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer property {:#04x}", as_unsigned(param)); }
void EqualizerEffectHandler::GetParamiv(ALCcontext *context, const EqualizerProps&, ALenum param, int*)
{ context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer-vector property {:#04x}", as_unsigned(param)); }
void EqualizerEffectHandler::GetParamf(ALCcontext *context, const EqualizerProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; break; case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; return;
case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; break; case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; return;
case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; break; case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; return;
case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; break; case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; return;
case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; break; case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; return;
case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; break; case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; return;
case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; break; case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; return;
case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; break; case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; return;
case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; break; case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; return;
case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; break; case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid equalizer float property {:#04x}",
as_unsigned(param));
} }
void EqualizerEffectHandler::GetParamfv(const EqualizerProps &props, ALenum param, float *vals) void EqualizerEffectHandler::GetParamfv(ALCcontext *context, const EqualizerProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
using EqualizerCommitter = EaxCommitter<EaxEqualizerCommitter>; using EqualizerCommitter = EaxCommitter<EaxEqualizerCommitter>;

View file

@ -7,12 +7,13 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include <cassert> #include <cassert>
#include "alnumeric.h"
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
@ -39,7 +40,7 @@ constexpr ALenum EnumFromDirection(FShifterDirection dir)
case FShifterDirection::Up: return AL_FREQUENCY_SHIFTER_DIRECTION_UP; case FShifterDirection::Up: return AL_FREQUENCY_SHIFTER_DIRECTION_UP;
case FShifterDirection::Off: return AL_FREQUENCY_SHIFTER_DIRECTION_OFF; case FShifterDirection::Off: return AL_FREQUENCY_SHIFTER_DIRECTION_OFF;
} }
throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast<int>(dir))}; throw std::runtime_error{fmt::format("Invalid direction: {}", int{al::to_underlying(dir)})};
} }
constexpr EffectProps genDefaultProps() noexcept constexpr EffectProps genDefaultProps() noexcept
@ -55,7 +56,7 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps FshifterEffectProps{genDefaultProps()}; const EffectProps FshifterEffectProps{genDefaultProps()};
void FshifterEffectHandler::SetParami(FshifterProps &props, ALenum param, int val) void FshifterEffectHandler::SetParami(ALCcontext *context, FshifterProps &props, ALenum param, int val)
{ {
switch(param) switch(param)
{ {
@ -63,81 +64,75 @@ void FshifterEffectHandler::SetParami(FshifterProps &props, ALenum param, int va
if(auto diropt = DirectionFromEmum(val)) if(auto diropt = DirectionFromEmum(val))
props.LeftDirection = *diropt; props.LeftDirection = *diropt;
else else
throw effect_exception{AL_INVALID_VALUE, context->throw_error(AL_INVALID_VALUE,
"Unsupported frequency shifter left direction: 0x%04x", val}; "Unsupported frequency shifter left direction: {:#04x}", as_unsigned(val));
break; return;
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
if(auto diropt = DirectionFromEmum(val)) if(auto diropt = DirectionFromEmum(val))
props.RightDirection = *diropt; props.RightDirection = *diropt;
else else
throw effect_exception{AL_INVALID_VALUE, context->throw_error(AL_INVALID_VALUE,
"Unsupported frequency shifter right direction: 0x%04x", val}; "Unsupported frequency shifter right direction: {:#04x}", as_unsigned(val));
break; return;
default:
throw effect_exception{AL_INVALID_ENUM,
"Invalid frequency shifter integer property 0x%04x", param};
} }
}
void FshifterEffectHandler::SetParamiv(FshifterProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); }
void FshifterEffectHandler::SetParamf(FshifterProps &props, ALenum param, float val) context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter integer property {:#04x}",
as_unsigned(param));
}
void FshifterEffectHandler::SetParamiv(ALCcontext *context, FshifterProps &props, ALenum param, const int *vals)
{ SetParami(context, props, param, *vals); }
void FshifterEffectHandler::SetParamf(ALCcontext *context, FshifterProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_FREQUENCY_SHIFTER_FREQUENCY: case AL_FREQUENCY_SHIFTER_FREQUENCY:
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY))
throw effect_exception{AL_INVALID_VALUE, "Frequency shifter frequency out of range"}; context->throw_error(AL_INVALID_VALUE, "Frequency shifter frequency out of range");
props.Frequency = val; props.Frequency = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
param};
} }
}
void FshifterEffectHandler::SetParamfv(FshifterProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void FshifterEffectHandler::GetParami(const FshifterProps &props, ALenum param, int *val) context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter float property {:#04x}",
as_unsigned(param));
}
void FshifterEffectHandler::SetParamfv(ALCcontext *context, FshifterProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void FshifterEffectHandler::GetParami(ALCcontext *context, const FshifterProps &props, ALenum param, int *val)
{ {
switch(param) switch(param)
{ {
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
*val = EnumFromDirection(props.LeftDirection); *val = EnumFromDirection(props.LeftDirection);
break; return;
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
*val = EnumFromDirection(props.RightDirection); *val = EnumFromDirection(props.RightDirection);
break; return;
default:
throw effect_exception{AL_INVALID_ENUM,
"Invalid frequency shifter integer property 0x%04x", param};
} }
}
void FshifterEffectHandler::GetParamiv(const FshifterProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); }
void FshifterEffectHandler::GetParamf(const FshifterProps &props, ALenum param, float *val) context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter integer property {:#04x}",
as_unsigned(param));
}
void FshifterEffectHandler::GetParamiv(ALCcontext *context, const FshifterProps &props, ALenum param, int *vals)
{ GetParami(context, props, param, vals); }
void FshifterEffectHandler::GetParamf(ALCcontext *context, const FshifterProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_FREQUENCY_SHIFTER_FREQUENCY: case AL_FREQUENCY_SHIFTER_FREQUENCY: *val = props.Frequency; return;
*val = props.Frequency;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter float property {:#04x}",
as_unsigned(param));
} }
void FshifterEffectHandler::GetParamfv(const FshifterProps &props, ALenum param, float *vals) void FshifterEffectHandler::GetParamfv(ALCcontext *context, const FshifterProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
using FrequencyShifterCommitter = EaxCommitter<EaxFrequencyShifterCommitter>; using FrequencyShifterCommitter = EaxCommitter<EaxFrequencyShifterCommitter>;

View file

@ -7,12 +7,13 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include <cassert> #include <cassert>
#include "alnumeric.h"
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
@ -39,8 +40,8 @@ constexpr ALenum EnumFromWaveform(ModulatorWaveform type)
case ModulatorWaveform::Sawtooth: return AL_RING_MODULATOR_SAWTOOTH; case ModulatorWaveform::Sawtooth: return AL_RING_MODULATOR_SAWTOOTH;
case ModulatorWaveform::Square: return AL_RING_MODULATOR_SQUARE; case ModulatorWaveform::Square: return AL_RING_MODULATOR_SQUARE;
} }
throw std::runtime_error{"Invalid modulator waveform: " + throw std::runtime_error{fmt::format("Invalid modulator waveform: {}",
std::to_string(static_cast<int>(type))}; int{al::to_underlying(type)})};
} }
constexpr EffectProps genDefaultProps() noexcept constexpr EffectProps genDefaultProps() noexcept
@ -56,84 +57,84 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps ModulatorEffectProps{genDefaultProps()}; const EffectProps ModulatorEffectProps{genDefaultProps()};
void ModulatorEffectHandler::SetParami(ModulatorProps &props, ALenum param, int val) void ModulatorEffectHandler::SetParami(ALCcontext *context, ModulatorProps &props, ALenum param, int val)
{ {
switch(param) switch(param)
{ {
case AL_RING_MODULATOR_FREQUENCY: case AL_RING_MODULATOR_FREQUENCY:
case AL_RING_MODULATOR_HIGHPASS_CUTOFF: case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
SetParamf(props, param, static_cast<float>(val)); SetParamf(context, props, param, static_cast<float>(val));
break; return;
case AL_RING_MODULATOR_WAVEFORM: case AL_RING_MODULATOR_WAVEFORM:
if(auto formopt = WaveformFromEmum(val)) if(auto formopt = WaveformFromEmum(val))
props.Waveform = *formopt; props.Waveform = *formopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Invalid modulator waveform: 0x%04x", val}; context->throw_error(AL_INVALID_VALUE, "Invalid modulator waveform: {:#04x}",
break; as_unsigned(val));
return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x",
param};
} }
}
void ModulatorEffectHandler::SetParamiv(ModulatorProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); }
void ModulatorEffectHandler::SetParamf(ModulatorProps &props, ALenum param, float val) context->throw_error(AL_INVALID_ENUM, "Invalid modulator integer property {:#04x}",
as_unsigned(param));
}
void ModulatorEffectHandler::SetParamiv(ALCcontext *context, ModulatorProps &props, ALenum param, const int *vals)
{ SetParami(context, props, param, *vals); }
void ModulatorEffectHandler::SetParamf(ALCcontext *context, ModulatorProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_RING_MODULATOR_FREQUENCY: case AL_RING_MODULATOR_FREQUENCY:
if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY))
throw effect_exception{AL_INVALID_VALUE, "Modulator frequency out of range: %f", val}; context->throw_error(AL_INVALID_VALUE, "Modulator frequency out of range: {:f}", val);
props.Frequency = val; props.Frequency = val;
break; return;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF: case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF))
throw effect_exception{AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: %f", val}; context->throw_error(AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: {:f}",
val);
props.HighPassCutoff = val; props.HighPassCutoff = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param};
} }
}
void ModulatorEffectHandler::SetParamfv(ModulatorProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void ModulatorEffectHandler::GetParami(const ModulatorProps &props, ALenum param, int *val) context->throw_error(AL_INVALID_ENUM, "Invalid modulator float property {:#04x}",
as_unsigned(param));
}
void ModulatorEffectHandler::SetParamfv(ALCcontext *context, ModulatorProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void ModulatorEffectHandler::GetParami(ALCcontext *context, const ModulatorProps &props, ALenum param, int *val)
{ {
switch(param) switch(param)
{ {
case AL_RING_MODULATOR_FREQUENCY: *val = static_cast<int>(props.Frequency); break; case AL_RING_MODULATOR_FREQUENCY: *val = static_cast<int>(props.Frequency); return;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast<int>(props.HighPassCutoff); break; case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast<int>(props.HighPassCutoff); return;
case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid modulator integer property {:#04x}",
as_unsigned(param));
} }
void ModulatorEffectHandler::GetParamiv(const ModulatorProps &props, ALenum param, int *vals) void ModulatorEffectHandler::GetParamiv(ALCcontext *context, const ModulatorProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); } { GetParami(context, props, param, vals); }
void ModulatorEffectHandler::GetParamf(const ModulatorProps &props, ALenum param, float *val) void ModulatorEffectHandler::GetParamf(ALCcontext *context, const ModulatorProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_RING_MODULATOR_FREQUENCY: *val = props.Frequency; break; case AL_RING_MODULATOR_FREQUENCY: *val = props.Frequency; return;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = props.HighPassCutoff; break; case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = props.HighPassCutoff; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid modulator float property {:#04x}",
as_unsigned(param));
} }
void ModulatorEffectHandler::GetParamfv(const ModulatorProps &props, ALenum param, float *vals) void ModulatorEffectHandler::GetParamfv(ALCcontext *context, const ModulatorProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
using ModulatorCommitter = EaxCommitter<EaxModulatorCommitter>; using ModulatorCommitter = EaxCommitter<EaxModulatorCommitter>;

View file

@ -4,10 +4,11 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -24,78 +25,46 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps NullEffectProps{genDefaultProps()}; const EffectProps NullEffectProps{genDefaultProps()};
void NullEffectHandler::SetParami(std::monostate& /*props*/, ALenum param, int /*val*/) void NullEffectHandler::SetParami(ALCcontext *context, std::monostate& /*props*/, ALenum param, int /*val*/)
{ {
switch(param) context->throw_error(AL_INVALID_ENUM, "Invalid null effect integer property {:#04x}",
{ as_unsigned(param));
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
param};
}
} }
void NullEffectHandler::SetParamiv(std::monostate &props, ALenum param, const int *vals) void NullEffectHandler::SetParamiv(ALCcontext *context, std::monostate &props, ALenum param, const int *vals)
{ {
switch(param) SetParami(context, props, param, *vals);
{
default:
SetParami(props, param, *vals);
}
} }
void NullEffectHandler::SetParamf(std::monostate& /*props*/, ALenum param, float /*val*/) void NullEffectHandler::SetParamf(ALCcontext *context, std::monostate& /*props*/, ALenum param, float /*val*/)
{ {
switch(param) context->throw_error(AL_INVALID_ENUM, "Invalid null effect float property {:#04x}",
{ as_unsigned(param));
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
param};
}
} }
void NullEffectHandler::SetParamfv(std::monostate &props, ALenum param, const float *vals) void NullEffectHandler::SetParamfv(ALCcontext *context, std::monostate &props, ALenum param, const float *vals)
{ {
switch(param) SetParamf(context, props, param, *vals);
{
default:
SetParamf(props, param, *vals);
}
} }
void NullEffectHandler::GetParami(const std::monostate& /*props*/, ALenum param, int* /*val*/) void NullEffectHandler::GetParami(ALCcontext *context, const std::monostate& /*props*/, ALenum param, int* /*val*/)
{ {
switch(param) context->throw_error(AL_INVALID_ENUM, "Invalid null effect integer property {:#04x}",
{ as_unsigned(param));
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
param};
}
} }
void NullEffectHandler::GetParamiv(const std::monostate &props, ALenum param, int *vals) void NullEffectHandler::GetParamiv(ALCcontext *context, const std::monostate &props, ALenum param, int *vals)
{ {
switch(param) GetParami(context, props, param, vals);
{
default:
GetParami(props, param, vals);
}
} }
void NullEffectHandler::GetParamf(const std::monostate& /*props*/, ALenum param, float* /*val*/) void NullEffectHandler::GetParamf(ALCcontext *context, const std::monostate& /*props*/, ALenum param, float* /*val*/)
{ {
switch(param) context->throw_error(AL_INVALID_ENUM, "Invalid null effect float property {:#04x}",
{ as_unsigned(param));
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
param};
}
} }
void NullEffectHandler::GetParamfv(const std::monostate &props, ALenum param, float *vals) void NullEffectHandler::GetParamfv(ALCcontext *context, const std::monostate &props, ALenum param, float *vals)
{ {
switch(param) GetParamf(context, props, param, vals);
{
default:
GetParamf(props, param, vals);
}
} }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
using NullCommitter = EaxCommitter<EaxNullCommitter>; using NullCommitter = EaxCommitter<EaxNullCommitter>;

View file

@ -4,11 +4,11 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
@ -29,63 +29,55 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps PshifterEffectProps{genDefaultProps()}; const EffectProps PshifterEffectProps{genDefaultProps()};
void PshifterEffectHandler::SetParami(PshifterProps &props, ALenum param, int val) void PshifterEffectHandler::SetParami(ALCcontext *context, PshifterProps &props, ALenum param, int val)
{ {
switch(param) switch(param)
{ {
case AL_PITCH_SHIFTER_COARSE_TUNE: case AL_PITCH_SHIFTER_COARSE_TUNE:
if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE))
throw effect_exception{AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"}; context->throw_error(AL_INVALID_VALUE, "Pitch shifter coarse tune out of range");
props.CoarseTune = val; props.CoarseTune = val;
break; return;
case AL_PITCH_SHIFTER_FINE_TUNE: case AL_PITCH_SHIFTER_FINE_TUNE:
if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE))
throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"}; context->throw_error(AL_INVALID_VALUE, "Pitch shifter fine tune out of range");
props.FineTune = val; props.FineTune = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
param};
} }
}
void PshifterEffectHandler::SetParamiv(PshifterProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); }
void PshifterEffectHandler::SetParamf(PshifterProps&, ALenum param, float) context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter integer property {:#04x}",
{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } as_unsigned(param));
void PshifterEffectHandler::SetParamfv(PshifterProps&, ALenum param, const float*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x",
param};
} }
void PshifterEffectHandler::SetParamiv(ALCcontext *context, PshifterProps &props, ALenum param, const int *vals)
{ SetParami(context, props, param, *vals); }
void PshifterEffectHandler::GetParami(const PshifterProps &props, ALenum param, int *val) void PshifterEffectHandler::SetParamf(ALCcontext *context, PshifterProps&, ALenum param, float)
{ context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); }
void PshifterEffectHandler::SetParamfv(ALCcontext *context, PshifterProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void PshifterEffectHandler::GetParami(ALCcontext *context, const PshifterProps &props, ALenum param, int *val)
{ {
switch(param) switch(param)
{ {
case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; break; case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; return;
case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; break; case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter integer property {:#04x}",
as_unsigned(param));
} }
void PshifterEffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals) void PshifterEffectHandler::GetParamiv(ALCcontext *context, const PshifterProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); } { GetParami(context, props, param, vals); }
void PshifterEffectHandler::GetParamf(const PshifterProps&, ALenum param, float*) void PshifterEffectHandler::GetParamf(ALCcontext *context, const PshifterProps&, ALenum param, float*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); }
void PshifterEffectHandler::GetParamfv(const PshifterProps&, ALenum param, float*) void PshifterEffectHandler::GetParamfv(ALCcontext *context, const PshifterProps &props, ALenum param, float *vals)
{ { GetParamf(context, props, param, vals); }
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x",
param};
}
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
using PitchShifterCommitter = EaxCommitter<EaxPitchShifterCommitter>; using PitchShifterCommitter = EaxCommitter<EaxPitchShifterCommitter>;

View file

@ -9,13 +9,17 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/context.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alspan.h" #include "alspan.h"
#include "core/effects/base.h" #include "core/logging.h"
#include "effects.h" #include "effects.h"
#include "fmt/ranges.h"
#include "opthelpers.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include <cassert> #include <cassert>
#include "al/eax/api.h" #include "al/eax/api.h"
#include "al/eax/call.h" #include "al/eax/call.h"
#include "al/eax/effect.h" #include "al/eax/effect.h"
@ -92,152 +96,149 @@ constexpr EffectProps genDefaultStdProps() noexcept
const EffectProps ReverbEffectProps{genDefaultProps()}; const EffectProps ReverbEffectProps{genDefaultProps()};
void ReverbEffectHandler::SetParami(ReverbProps &props, ALenum param, int val) void ReverbEffectHandler::SetParami(ALCcontext *context, ReverbProps &props, ALenum param, int val)
{ {
switch(param) switch(param)
{ {
case AL_EAXREVERB_DECAY_HFLIMIT: case AL_EAXREVERB_DECAY_HFLIMIT:
if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range");
props.DecayHFLimit = val != AL_FALSE; props.DecayHFLimit = val != AL_FALSE;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}",
as_unsigned(param));
} }
void ReverbEffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals) void ReverbEffectHandler::SetParamiv(ALCcontext *context, ReverbProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); } { SetParami(context, props, param, *vals); }
void ReverbEffectHandler::SetParamf(ReverbProps &props, ALenum param, float val) void ReverbEffectHandler::SetParamf(ALCcontext *context, ReverbProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_EAXREVERB_DENSITY: case AL_EAXREVERB_DENSITY:
if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb density out of range");
props.Density = val; props.Density = val;
break; return;
case AL_EAXREVERB_DIFFUSION: case AL_EAXREVERB_DIFFUSION:
if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb diffusion out of range");
props.Diffusion = val; props.Diffusion = val;
break; return;
case AL_EAXREVERB_GAIN: case AL_EAXREVERB_GAIN:
if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb gain out of range");
props.Gain = val; props.Gain = val;
break; return;
case AL_EAXREVERB_GAINHF: case AL_EAXREVERB_GAINHF:
if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainhf out of range");
props.GainHF = val; props.GainHF = val;
break; return;
case AL_EAXREVERB_GAINLF: case AL_EAXREVERB_GAINLF:
if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainlf out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainlf out of range");
props.GainLF = val; props.GainLF = val;
break; return;
case AL_EAXREVERB_DECAY_TIME: case AL_EAXREVERB_DECAY_TIME:
if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay time out of range");
props.DecayTime = val; props.DecayTime = val;
break; return;
case AL_EAXREVERB_DECAY_HFRATIO: case AL_EAXREVERB_DECAY_HFRATIO:
if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range");
props.DecayHFRatio = val; props.DecayHFRatio = val;
break; return;
case AL_EAXREVERB_DECAY_LFRATIO: case AL_EAXREVERB_DECAY_LFRATIO:
if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range");
props.DecayLFRatio = val; props.DecayLFRatio = val;
break; return;
case AL_EAXREVERB_REFLECTIONS_GAIN: case AL_EAXREVERB_REFLECTIONS_GAIN:
if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections gain out of range");
props.ReflectionsGain = val; props.ReflectionsGain = val;
break; return;
case AL_EAXREVERB_REFLECTIONS_DELAY: case AL_EAXREVERB_REFLECTIONS_DELAY:
if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections delay out of range");
props.ReflectionsDelay = val; props.ReflectionsDelay = val;
break; return;
case AL_EAXREVERB_LATE_REVERB_GAIN: case AL_EAXREVERB_LATE_REVERB_GAIN:
if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range");
props.LateReverbGain = val; props.LateReverbGain = val;
break; return;
case AL_EAXREVERB_LATE_REVERB_DELAY: case AL_EAXREVERB_LATE_REVERB_DELAY:
if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range");
props.LateReverbDelay = val; props.LateReverbDelay = val;
break; return;
case AL_EAXREVERB_ECHO_TIME: case AL_EAXREVERB_ECHO_TIME:
if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo time out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb echo time out of range");
props.EchoTime = val; props.EchoTime = val;
break; return;
case AL_EAXREVERB_ECHO_DEPTH: case AL_EAXREVERB_ECHO_DEPTH:
if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo depth out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb echo depth out of range");
props.EchoDepth = val; props.EchoDepth = val;
break; return;
case AL_EAXREVERB_MODULATION_TIME: case AL_EAXREVERB_MODULATION_TIME:
if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation time out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb modulation time out of range");
props.ModulationTime = val; props.ModulationTime = val;
break; return;
case AL_EAXREVERB_MODULATION_DEPTH: case AL_EAXREVERB_MODULATION_DEPTH:
if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb modulation depth out of range");
props.ModulationDepth = val; props.ModulationDepth = val;
break; return;
case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: case AL_EAXREVERB_AIR_ABSORPTION_GAINHF:
if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range");
props.AirAbsorptionGainHF = val; props.AirAbsorptionGainHF = val;
break; return;
case AL_EAXREVERB_HFREFERENCE: case AL_EAXREVERB_HFREFERENCE:
if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb hfreference out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb hfreference out of range");
props.HFReference = val; props.HFReference = val;
break; return;
case AL_EAXREVERB_LFREFERENCE: case AL_EAXREVERB_LFREFERENCE:
if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb lfreference out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb lfreference out of range");
props.LFReference = val; props.LFReference = val;
break; return;
case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR:
if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range");
props.RoomRolloffFactor = val; props.RoomRolloffFactor = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}",
as_unsigned(param));
} }
void ReverbEffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals) void ReverbEffectHandler::SetParamfv(ALCcontext *context, ReverbProps &props, ALenum param, const float *vals)
{ {
static constexpr auto finite_checker = [](float f) -> bool { return std::isfinite(f); }; static constexpr auto finite_checker = [](float f) -> bool { return std::isfinite(f); };
al::span<const float> values; al::span<const float> values;
@ -246,64 +247,60 @@ void ReverbEffectHandler::SetParamfv(ReverbProps &props, ALenum param, const flo
case AL_EAXREVERB_REFLECTIONS_PAN: case AL_EAXREVERB_REFLECTIONS_PAN:
values = {vals, 3_uz}; values = {vals, 3_uz};
if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) if(!std::all_of(values.cbegin(), values.cend(), finite_checker))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections pan out of range");
std::copy(values.cbegin(), values.cend(), props.ReflectionsPan.begin()); std::copy(values.cbegin(), values.cend(), props.ReflectionsPan.begin());
break; return;
case AL_EAXREVERB_LATE_REVERB_PAN: case AL_EAXREVERB_LATE_REVERB_PAN:
values = {vals, 3_uz}; values = {vals, 3_uz};
if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) if(!std::all_of(values.cbegin(), values.cend(), finite_checker))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range");
std::copy(values.cbegin(), values.cend(), props.LateReverbPan.begin()); std::copy(values.cbegin(), values.cend(), props.LateReverbPan.begin());
break; return;
default:
SetParamf(props, param, *vals);
break;
} }
SetParamf(context, props, param, *vals);
} }
void ReverbEffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val) void ReverbEffectHandler::GetParami(ALCcontext *context, const ReverbProps &props, ALenum param, int *val)
{ {
switch(param) switch(param)
{ {
case AL_EAXREVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; case AL_EAXREVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}",
as_unsigned(param));
} }
void ReverbEffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals) void ReverbEffectHandler::GetParamiv(ALCcontext *context, const ReverbProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); } { GetParami(context, props, param, vals); }
void ReverbEffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val) void ReverbEffectHandler::GetParamf(ALCcontext *context, const ReverbProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_EAXREVERB_DENSITY: *val = props.Density; break; case AL_EAXREVERB_DENSITY: *val = props.Density; return;
case AL_EAXREVERB_DIFFUSION: *val = props.Diffusion; break; case AL_EAXREVERB_DIFFUSION: *val = props.Diffusion; return;
case AL_EAXREVERB_GAIN: *val = props.Gain; break; case AL_EAXREVERB_GAIN: *val = props.Gain; return;
case AL_EAXREVERB_GAINHF: *val = props.GainHF; break; case AL_EAXREVERB_GAINHF: *val = props.GainHF; return;
case AL_EAXREVERB_GAINLF: *val = props.GainLF; break; case AL_EAXREVERB_GAINLF: *val = props.GainLF; return;
case AL_EAXREVERB_DECAY_TIME: *val = props.DecayTime; break; case AL_EAXREVERB_DECAY_TIME: *val = props.DecayTime; return;
case AL_EAXREVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break; case AL_EAXREVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; return;
case AL_EAXREVERB_DECAY_LFRATIO: *val = props.DecayLFRatio; break; case AL_EAXREVERB_DECAY_LFRATIO: *val = props.DecayLFRatio; return;
case AL_EAXREVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break; case AL_EAXREVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; return;
case AL_EAXREVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break; case AL_EAXREVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; return;
case AL_EAXREVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break; case AL_EAXREVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; return;
case AL_EAXREVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break; case AL_EAXREVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; return;
case AL_EAXREVERB_ECHO_TIME: *val = props.EchoTime; break; case AL_EAXREVERB_ECHO_TIME: *val = props.EchoTime; return;
case AL_EAXREVERB_ECHO_DEPTH: *val = props.EchoDepth; break; case AL_EAXREVERB_ECHO_DEPTH: *val = props.EchoDepth; return;
case AL_EAXREVERB_MODULATION_TIME: *val = props.ModulationTime; break; case AL_EAXREVERB_MODULATION_TIME: *val = props.ModulationTime; return;
case AL_EAXREVERB_MODULATION_DEPTH: *val = props.ModulationDepth; break; case AL_EAXREVERB_MODULATION_DEPTH: *val = props.ModulationDepth; return;
case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break; case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; return;
case AL_EAXREVERB_HFREFERENCE: *val = props.HFReference; break; case AL_EAXREVERB_HFREFERENCE: *val = props.HFReference; return;
case AL_EAXREVERB_LFREFERENCE: *val = props.LFReference; break; case AL_EAXREVERB_LFREFERENCE: *val = props.LFReference; return;
case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break; case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}",
as_unsigned(param));
} }
void ReverbEffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *vals) void ReverbEffectHandler::GetParamfv(ALCcontext *context, const ReverbProps &props, ALenum param, float *vals)
{ {
al::span<float> values; al::span<float> values;
switch(param) switch(param)
@ -311,159 +308,155 @@ void ReverbEffectHandler::GetParamfv(const ReverbProps &props, ALenum param, flo
case AL_EAXREVERB_REFLECTIONS_PAN: case AL_EAXREVERB_REFLECTIONS_PAN:
values = {vals, 3_uz}; values = {vals, 3_uz};
std::copy(props.ReflectionsPan.cbegin(), props.ReflectionsPan.cend(), values.begin()); std::copy(props.ReflectionsPan.cbegin(), props.ReflectionsPan.cend(), values.begin());
break; return;
case AL_EAXREVERB_LATE_REVERB_PAN: case AL_EAXREVERB_LATE_REVERB_PAN:
values = {vals, 3_uz}; values = {vals, 3_uz};
std::copy(props.LateReverbPan.cbegin(), props.LateReverbPan.cend(), values.begin()); std::copy(props.LateReverbPan.cbegin(), props.LateReverbPan.cend(), values.begin());
break; return;
default:
GetParamf(props, param, vals);
break;
} }
GetParamf(context, props, param, vals);
} }
const EffectProps StdReverbEffectProps{genDefaultStdProps()}; const EffectProps StdReverbEffectProps{genDefaultStdProps()};
void StdReverbEffectHandler::SetParami(ReverbProps &props, ALenum param, int val) void StdReverbEffectHandler::SetParami(ALCcontext *context, ReverbProps &props, ALenum param, int val)
{ {
switch(param) switch(param)
{ {
case AL_REVERB_DECAY_HFLIMIT: case AL_REVERB_DECAY_HFLIMIT:
if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range");
props.DecayHFLimit = val != AL_FALSE; props.DecayHFLimit = val != AL_FALSE;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}",
as_unsigned(param));
} }
void StdReverbEffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals) void StdReverbEffectHandler::SetParamiv(ALCcontext *context, ReverbProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); } { SetParami(context, props, param, *vals); }
void StdReverbEffectHandler::SetParamf(ReverbProps &props, ALenum param, float val) void StdReverbEffectHandler::SetParamf(ALCcontext *context, ReverbProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_REVERB_DENSITY: case AL_REVERB_DENSITY:
if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb density out of range");
props.Density = val; props.Density = val;
break; return;
case AL_REVERB_DIFFUSION: case AL_REVERB_DIFFUSION:
if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb diffusion out of range");
props.Diffusion = val; props.Diffusion = val;
break; return;
case AL_REVERB_GAIN: case AL_REVERB_GAIN:
if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb gain out of range");
props.Gain = val; props.Gain = val;
break; return;
case AL_REVERB_GAINHF: case AL_REVERB_GAINHF:
if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainhf out of range");
props.GainHF = val; props.GainHF = val;
break; return;
case AL_REVERB_DECAY_TIME: case AL_REVERB_DECAY_TIME:
if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay time out of range");
props.DecayTime = val; props.DecayTime = val;
break; return;
case AL_REVERB_DECAY_HFRATIO: case AL_REVERB_DECAY_HFRATIO:
if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range");
props.DecayHFRatio = val; props.DecayHFRatio = val;
break; return;
case AL_REVERB_REFLECTIONS_GAIN: case AL_REVERB_REFLECTIONS_GAIN:
if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections gain out of range");
props.ReflectionsGain = val; props.ReflectionsGain = val;
break; return;
case AL_REVERB_REFLECTIONS_DELAY: case AL_REVERB_REFLECTIONS_DELAY:
if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections delay out of range");
props.ReflectionsDelay = val; props.ReflectionsDelay = val;
break; return;
case AL_REVERB_LATE_REVERB_GAIN: case AL_REVERB_LATE_REVERB_GAIN:
if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range");
props.LateReverbGain = val; props.LateReverbGain = val;
break; return;
case AL_REVERB_LATE_REVERB_DELAY: case AL_REVERB_LATE_REVERB_DELAY:
if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range");
props.LateReverbDelay = val; props.LateReverbDelay = val;
break; return;
case AL_REVERB_AIR_ABSORPTION_GAINHF: case AL_REVERB_AIR_ABSORPTION_GAINHF:
if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range");
props.AirAbsorptionGainHF = val; props.AirAbsorptionGainHF = val;
break; return;
case AL_REVERB_ROOM_ROLLOFF_FACTOR: case AL_REVERB_ROOM_ROLLOFF_FACTOR:
if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR))
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; context->throw_error(AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range");
props.RoomRolloffFactor = val; props.RoomRolloffFactor = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
} }
}
void StdReverbEffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void StdReverbEffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val) context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}",
as_unsigned(param));
}
void StdReverbEffectHandler::SetParamfv(ALCcontext *context, ReverbProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void StdReverbEffectHandler::GetParami(ALCcontext *context, const ReverbProps &props, ALenum param, int *val)
{ {
switch(param) switch(param)
{ {
case AL_REVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; case AL_REVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}",
as_unsigned(param));
} }
void StdReverbEffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals) void StdReverbEffectHandler::GetParamiv(ALCcontext *context, const ReverbProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); } { GetParami(context, props, param, vals); }
void StdReverbEffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val) void StdReverbEffectHandler::GetParamf(ALCcontext *context, const ReverbProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_REVERB_DENSITY: *val = props.Density; break; case AL_REVERB_DENSITY: *val = props.Density; return;
case AL_REVERB_DIFFUSION: *val = props.Diffusion; break; case AL_REVERB_DIFFUSION: *val = props.Diffusion; return;
case AL_REVERB_GAIN: *val = props.Gain; break; case AL_REVERB_GAIN: *val = props.Gain; return;
case AL_REVERB_GAINHF: *val = props.GainHF; break; case AL_REVERB_GAINHF: *val = props.GainHF; return;
case AL_REVERB_DECAY_TIME: *val = props.DecayTime; break; case AL_REVERB_DECAY_TIME: *val = props.DecayTime; return;
case AL_REVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break; case AL_REVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; return;
case AL_REVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break; case AL_REVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; return;
case AL_REVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break; case AL_REVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; return;
case AL_REVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break; case AL_REVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; return;
case AL_REVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break; case AL_REVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; return;
case AL_REVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break; case AL_REVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; return;
case AL_REVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break; case AL_REVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}",
as_unsigned(param));
} }
void StdReverbEffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *vals) void StdReverbEffectHandler::GetParamfv(ALCcontext *context, const ReverbProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
class EaxReverbEffectException : public EaxException class EaxReverbEffectException : public EaxException
@ -1064,6 +1057,38 @@ bool EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props)
ret.LFReference = props.flLFReference; ret.LFReference = props.flLFReference;
ret.RoomRolloffFactor = props.flRoomRolloffFactor; ret.RoomRolloffFactor = props.flRoomRolloffFactor;
ret.DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); ret.DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0);
if(EaxTraceCommits) UNLIKELY
{
TRACE("Reverb commit:\n"
" Density: {:f}\n"
" Diffusion: {:f}\n"
" Gain: {:f}\n"
" GainHF: {:f}\n"
" GainLF: {:f}\n"
" DecayTime: {:f}\n"
" DecayHFRatio: {:f}\n"
" DecayLFRatio: {:f}\n"
" ReflectionsGain: {:f}\n"
" ReflectionsDelay: {:f}\n"
" ReflectionsPan: {}\n"
" LateReverbGain: {:f}\n"
" LateReverbDelay: {:f}\n"
" LateRevernPan: {}\n"
" EchoTime: {:f}\n"
" EchoDepth: {:f}\n"
" ModulationTime: {:f}\n"
" ModulationDepth: {:f}\n"
" AirAbsorptionGainHF: {:f}\n"
" HFReference: {:f}\n"
" LFReference: {:f}\n"
" RoomRolloffFactor: {:f}\n"
" DecayHFLimit: {}", ret.Density, ret.Diffusion, ret.Gain, ret.GainHF, ret.GainLF,
ret.DecayTime, ret.DecayHFRatio, ret.DecayLFRatio, ret.ReflectionsGain,
ret.ReflectionsDelay, ret.ReflectionsPan, ret.LateReverbGain, ret.LateReverbDelay,
ret.LateReverbPan, ret.EchoTime, ret.EchoDepth, ret.ModulationTime,
ret.ModulationDepth, ret.AirAbsorptionGainHF, ret.HFReference, ret.LFReference,
ret.RoomRolloffFactor, ret.DecayHFLimit ? "true" : "false");
}
return ret; return ret;
}(); }();

View file

@ -7,10 +7,11 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "core/effects/base.h" #include "alc/context.h"
#include "alnumeric.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include <cassert> #include <cassert>
#include "al/eax/effect.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
@ -96,7 +97,7 @@ constexpr ALenum EnumFromPhenome(VMorpherPhenome phenome)
HANDLE_PHENOME(V); HANDLE_PHENOME(V);
HANDLE_PHENOME(Z); HANDLE_PHENOME(Z);
} }
throw std::runtime_error{"Invalid phenome: "+std::to_string(static_cast<int>(phenome))}; throw std::runtime_error{fmt::format("Invalid phenome: {}", int{al::to_underlying(phenome)})};
#undef HANDLE_PHENOME #undef HANDLE_PHENOME
} }
@ -118,8 +119,8 @@ constexpr ALenum EnumFromWaveform(VMorpherWaveform type)
case VMorpherWaveform::Triangle: return AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE; case VMorpherWaveform::Triangle: return AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE;
case VMorpherWaveform::Sawtooth: return AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH; case VMorpherWaveform::Sawtooth: return AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH;
} }
throw std::runtime_error{"Invalid vocal morpher waveform: " + throw std::runtime_error{fmt::format("Invalid vocal morpher waveform: {}",
std::to_string(static_cast<int>(type))}; int{al::to_underlying(type)})};
} }
constexpr EffectProps genDefaultProps() noexcept constexpr EffectProps genDefaultProps() noexcept
@ -138,7 +139,7 @@ constexpr EffectProps genDefaultProps() noexcept
const EffectProps VmorpherEffectProps{genDefaultProps()}; const EffectProps VmorpherEffectProps{genDefaultProps()};
void VmorpherEffectHandler::SetParami(VmorpherProps &props, ALenum param, int val) void VmorpherEffectHandler::SetParami(ALCcontext *context, VmorpherProps &props, ALenum param, int val)
{ {
switch(param) switch(param)
{ {
@ -146,101 +147,94 @@ void VmorpherEffectHandler::SetParami(VmorpherProps &props, ALenum param, int va
if(auto phenomeopt = PhenomeFromEnum(val)) if(auto phenomeopt = PhenomeFromEnum(val))
props.PhonemeA = *phenomeopt; props.PhonemeA = *phenomeopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val}; context->throw_error(AL_INVALID_VALUE,
break; "Vocal morpher phoneme-a out of range: {:#04x}", as_unsigned(val));
return;
case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING)) if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING))
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"}; context->throw_error(AL_INVALID_VALUE,
"Vocal morpher phoneme-a coarse tuning out of range");
props.PhonemeACoarseTuning = val; props.PhonemeACoarseTuning = val;
break; return;
case AL_VOCAL_MORPHER_PHONEMEB: case AL_VOCAL_MORPHER_PHONEMEB:
if(auto phenomeopt = PhenomeFromEnum(val)) if(auto phenomeopt = PhenomeFromEnum(val))
props.PhonemeB = *phenomeopt; props.PhonemeB = *phenomeopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val}; context->throw_error(AL_INVALID_VALUE,
break; "Vocal morpher phoneme-b out of range: {:#04x}", as_unsigned(val));
return;
case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING)) if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING))
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"}; context->throw_error(AL_INVALID_VALUE,
"Vocal morpher phoneme-b coarse tuning out of range");
props.PhonemeBCoarseTuning = val; props.PhonemeBCoarseTuning = val;
break; return;
case AL_VOCAL_MORPHER_WAVEFORM: case AL_VOCAL_MORPHER_WAVEFORM:
if(auto formopt = WaveformFromEmum(val)) if(auto formopt = WaveformFromEmum(val))
props.Waveform = *formopt; props.Waveform = *formopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val}; context->throw_error(AL_INVALID_VALUE, "Vocal morpher waveform out of range: {:#04x}",
break; as_unsigned(val));
return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher integer property {:#04x}",
as_unsigned(param));
} }
void VmorpherEffectHandler::SetParamiv(VmorpherProps&, ALenum param, const int*) void VmorpherEffectHandler::SetParamiv(ALCcontext *context, VmorpherProps &props, ALenum param, const int *vals)
{ { SetParami(context, props, param, *vals); }
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", void VmorpherEffectHandler::SetParamf(ALCcontext *context, VmorpherProps &props, ALenum param, float val)
param};
}
void VmorpherEffectHandler::SetParamf(VmorpherProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_VOCAL_MORPHER_RATE: case AL_VOCAL_MORPHER_RATE:
if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE))
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"}; context->throw_error(AL_INVALID_VALUE, "Vocal morpher rate out of range");
props.Rate = val; props.Rate = val;
break; return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x",
param};
} }
}
void VmorpherEffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void VmorpherEffectHandler::GetParami(const VmorpherProps &props, ALenum param, int* val) context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher float property {:#04x}",
as_unsigned(param));
}
void VmorpherEffectHandler::SetParamfv(ALCcontext *context, VmorpherProps &props, ALenum param, const float *vals)
{ SetParamf(context, props, param, *vals); }
void VmorpherEffectHandler::GetParami(ALCcontext *context, const VmorpherProps &props, ALenum param, int* val)
{ {
switch(param) switch(param)
{ {
case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); break; case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); return;
case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; break; case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; return;
case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); break; case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); return;
case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; break; case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; return;
case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher integer property {:#04x}",
as_unsigned(param));
} }
void VmorpherEffectHandler::GetParamiv(const VmorpherProps&, ALenum param, int*) void VmorpherEffectHandler::GetParamiv(ALCcontext *context, const VmorpherProps &props, ALenum param, int *vals)
{ { GetParami(context, props, param, vals); }
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", void VmorpherEffectHandler::GetParamf(ALCcontext *context, const VmorpherProps &props, ALenum param, float *val)
param};
}
void VmorpherEffectHandler::GetParamf(const VmorpherProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_VOCAL_MORPHER_RATE: case AL_VOCAL_MORPHER_RATE: *val = props.Rate; return;
*val = props.Rate;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x",
param};
} }
context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher float property {:#04x}",
as_unsigned(param));
} }
void VmorpherEffectHandler::GetParamfv(const VmorpherProps &props, ALenum param, float *vals) void VmorpherEffectHandler::GetParamfv(ALCcontext *context, const VmorpherProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); } { GetParamf(context, props, param, vals); }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
using VocalMorpherCommitter = EaxCommitter<EaxVocalMorpherCommitter>; using VocalMorpherCommitter = EaxCommitter<EaxVocalMorpherCommitter>;

View file

@ -20,24 +20,19 @@
#include "config.h" #include "config.h"
#include "error.h"
#ifdef _WIN32 #ifdef _WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
#endif #endif
#include <atomic>
#include <csignal> #include <csignal>
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <limits>
#include <optional> #include <optional>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
@ -45,53 +40,19 @@
#include "al/debug.h" #include "al/debug.h"
#include "alc/alconfig.h" #include "alc/alconfig.h"
#include "alc/context.h" #include "alc/context.h"
#include "alc/inprogext.h" #include "alnumeric.h"
#include "core/except.h"
#include "core/logging.h" #include "core/logging.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "strutils.h" #include "strutils.h"
namespace al { void ALCcontext::setErrorImpl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args)
context_error::context_error(ALenum code, const char *msg, ...) : mErrorCode{code}
{ {
/* NOLINTBEGIN(*-array-to-pointer-decay) */ const auto msg = fmt::vformat(fmt, std::move(args));
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
}
context_error::~context_error() = default;
} /* namespace al */
void ALCcontext::setError(ALenum errorCode, const char *msg, ...) WARN("Error generated on context {}, code {:#04x}, \"{}\"",
{ decltype(std::declval<void*>()){this}, as_unsigned(errorCode), msg);
auto message = std::vector<char>(256);
/* NOLINTBEGIN(*-array-to-pointer-decay) */
std::va_list args, args2;
va_start(args, msg);
va_copy(args2, args);
int msglen{std::vsnprintf(message.data(), message.size(), msg, args)};
if(msglen >= 0 && static_cast<size_t>(msglen) >= message.size())
{
message.resize(static_cast<size_t>(msglen) + 1u);
msglen = std::vsnprintf(message.data(), message.size(), msg, args2);
}
va_end(args2);
va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
if(msglen >= 0)
msg = message.data();
else
{
msg = "<internal error constructing message>";
msglen = static_cast<int>(strlen(msg));
}
WARN("Error generated on context %p, code 0x%04x, \"%s\"\n",
decltype(std::declval<void*>()){this}, errorCode, msg);
if(TrapALError) if(TrapALError)
{ {
#ifdef _WIN32 #ifdef _WIN32
@ -106,10 +67,18 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
if(mLastThreadError.get() == AL_NO_ERROR) if(mLastThreadError.get() == AL_NO_ERROR)
mLastThreadError.set(errorCode); mLastThreadError.set(errorCode);
debugMessage(DebugSource::API, DebugType::Error, 0, DebugSeverity::High, debugMessage(DebugSource::API, DebugType::Error, static_cast<ALuint>(errorCode),
{msg, static_cast<uint>(msglen)}); DebugSeverity::High, msg);
} }
void ALCcontext::throw_error_impl(ALenum errorCode, const fmt::string_view fmt,
fmt::format_args args)
{
setErrorImpl(errorCode, fmt, std::move(args));
throw al::base_exception{};
}
/* Special-case alGetError since it (potentially) raises a debug signal and /* Special-case alGetError since it (potentially) raises a debug signal and
* returns a non-default value for a null context. * returns a non-default value for a null context.
*/ */
@ -125,17 +94,20 @@ AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum
optstr = ConfigValueStr({}, "game_compat", optname); optstr = ConfigValueStr({}, "game_compat", optname);
if(optstr) if(optstr)
{ {
char *end{}; try {
auto value = std::strtoul(optstr->c_str(), &end, 0); auto idx = 0_uz;
if(end && *end == '\0' && value <= std::numeric_limits<ALenum>::max()) auto value = std::stoi(*optstr, &idx, 0);
return static_cast<ALenum>(value); if(idx >= optstr->size() || std::isspace(optstr->at(idx)))
ERR("Invalid default error value: \"%s\"", optstr->c_str()); return static_cast<ALenum>(value);
} catch(...) {
}
ERR("Invalid default error value: \"{}\"", *optstr);
} }
return AL_INVALID_OPERATION; return AL_INVALID_OPERATION;
}; };
static const ALenum deferror{get_value("__ALSOFT_DEFAULT_ERROR", "default-error")}; static const ALenum deferror{get_value("__ALSOFT_DEFAULT_ERROR", "default-error")};
WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); WARN("Querying error state on null context (implicitly {:#04x})", as_unsigned(deferror));
if(TrapALError) if(TrapALError)
{ {
#ifdef _WIN32 #ifdef _WIN32

View file

@ -1,27 +0,0 @@
#ifndef AL_ERROR_H
#define AL_ERROR_H
#include "AL/al.h"
#include "core/except.h"
namespace al {
class context_error final : public al::base_exception {
ALenum mErrorCode{};
public:
#ifdef __MINGW32__
[[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
context_error(ALenum code, const char *msg, ...);
~context_error() final;
[[nodiscard]] auto errorCode() const noexcept -> ALenum { return mErrorCode; }
};
} /* namespace al */
#endif /* AL_ERROR_H */

View file

@ -3,7 +3,6 @@
#include "event.h" #include "event.h"
#include <array>
#include <atomic> #include <atomic>
#include <bitset> #include <bitset>
#include <exception> #include <exception>
@ -12,10 +11,8 @@
#include <new> #include <new>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view>
#include <thread> #include <thread>
#include <tuple> #include <tuple>
#include <utility>
#include <variant> #include <variant>
#include "AL/al.h" #include "AL/al.h"
@ -23,15 +20,17 @@
#include "AL/alext.h" #include "AL/alext.h"
#include "alc/context.h" #include "alc/context.h"
#include "alnumeric.h"
#include "alsem.h" #include "alsem.h"
#include "alspan.h" #include "alspan.h"
#include "alstring.h"
#include "core/async_event.h" #include "core/async_event.h"
#include "core/context.h" #include "core/context.h"
#include "core/effects/base.h" #include "core/effects/base.h"
#include "core/except.h"
#include "core/logging.h" #include "core/logging.h"
#include "debug.h" #include "debug.h"
#include "direct_defs.h" #include "direct_defs.h"
#include "error.h"
#include "intrusive_ptr.h" #include "intrusive_ptr.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "ringbuffer.h" #include "ringbuffer.h"
@ -51,14 +50,15 @@ int EventThread(ALCcontext *context)
bool quitnow{false}; bool quitnow{false};
while(!quitnow) while(!quitnow)
{ {
auto evt_data = ring->getReadVector().first; auto evt_data = ring->getReadVector()[0];
if(evt_data.len == 0) if(evt_data.len == 0)
{ {
context->mEventSem.wait(); context->mEventSem.wait();
continue; continue;
} }
std::lock_guard<std::mutex> eventlock{context->mEventCbLock}; auto eventlock = std::lock_guard{context->mEventCbLock};
const auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire);
auto evt_span = al::span{std::launder(reinterpret_cast<AsyncEvent*>(evt_data.buf)), auto evt_span = al::span{std::launder(reinterpret_cast<AsyncEvent*>(evt_data.buf)),
evt_data.len}; evt_data.len};
for(auto &event : evt_span) for(auto &event : evt_span)
@ -66,7 +66,6 @@ int EventThread(ALCcontext *context)
quitnow = std::holds_alternative<AsyncKillThread>(event); quitnow = std::holds_alternative<AsyncKillThread>(event);
if(quitnow) UNLIKELY break; if(quitnow) UNLIKELY break;
auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire);
auto proc_killthread = [](AsyncKillThread&) { }; auto proc_killthread = [](AsyncKillThread&) { };
auto proc_release = [](AsyncEffectReleaseEvent &evt) auto proc_release = [](AsyncEffectReleaseEvent &evt)
{ {
@ -101,7 +100,7 @@ int EventThread(ALCcontext *context)
break; break;
} }
context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state, context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state,
static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam); al::sizei(msg), msg.c_str(), context->mEventParam);
}; };
auto proc_buffercomp = [context,enabledevts](AsyncBufferCompleteEvent &evt) auto proc_buffercomp = [context,enabledevts](AsyncBufferCompleteEvent &evt)
{ {
@ -113,18 +112,16 @@ int EventThread(ALCcontext *context)
if(evt.mCount == 1) msg += " buffer completed"; if(evt.mCount == 1) msg += " buffer completed";
else msg += " buffers completed"; else msg += " buffers completed";
context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount, context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount,
static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam); al::sizei(msg), msg.c_str(), context->mEventParam);
}; };
auto proc_disconnect = [context,enabledevts](AsyncDisconnectEvent &evt) auto proc_disconnect = [context,enabledevts](AsyncDisconnectEvent &evt)
{ {
context->debugMessage(DebugSource::System, DebugType::Error, 0, if(!context->mEventCb
DebugSeverity::High, evt.msg); || !enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected)))
return;
if(context->mEventCb context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, al::sizei(evt.msg),
&& enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected))) evt.msg.c_str(), context->mEventParam);
context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0,
static_cast<ALsizei>(evt.msg.length()), evt.msg.c_str(),
context->mEventParam);
}; };
std::visit(overloaded{proc_srcstate, proc_buffercomp, proc_release, proc_disconnect, std::visit(overloaded{proc_srcstate, proc_buffercomp, proc_release, proc_disconnect,
@ -156,22 +153,22 @@ void StartEventThrd(ALCcontext *ctx)
ctx->mEventThread = std::thread{EventThread, ctx}; ctx->mEventThread = std::thread{EventThread, ctx};
} }
catch(std::exception& e) { catch(std::exception& e) {
ERR("Failed to start event thread: %s\n", e.what()); ERR("Failed to start event thread: {}", e.what());
} }
catch(...) { catch(...) {
ERR("Failed to start event thread! Expect problems.\n"); ERR("Failed to start event thread! Expect problems.");
} }
} }
void StopEventThrd(ALCcontext *ctx) void StopEventThrd(ALCcontext *ctx)
{ {
RingBuffer *ring{ctx->mAsyncEvents.get()}; RingBuffer *ring{ctx->mAsyncEvents.get()};
auto evt_data = ring->getWriteVector().first; auto evt_data = ring->getWriteVector()[0];
if(evt_data.len == 0) if(evt_data.len == 0)
{ {
do { do {
std::this_thread::yield(); std::this_thread::yield();
evt_data = ring->getWriteVector().first; evt_data = ring->getWriteVector()[0];
} while(evt_data.len == 0); } while(evt_data.len == 0);
} }
std::ignore = InitAsyncEvent<AsyncKillThread>(evt_data.buf); std::ignore = InitAsyncEvent<AsyncKillThread>(evt_data.buf);
@ -187,18 +184,19 @@ FORCE_ALIGN void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsiz
const ALenum *types, ALboolean enable) noexcept const ALenum *types, ALboolean enable) noexcept
try { try {
if(count < 0) if(count < 0)
throw al::context_error{AL_INVALID_VALUE, "Controlling %d events", count}; context->throw_error(AL_INVALID_VALUE, "Controlling {} events", count);
if(count <= 0) UNLIKELY return; if(count <= 0) UNLIKELY return;
if(!types) if(!types)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; context->throw_error(AL_INVALID_VALUE, "NULL pointer");
ContextBase::AsyncEventBitset flags{}; ContextBase::AsyncEventBitset flags{};
for(ALenum evttype : al::span{types, static_cast<uint>(count)}) for(ALenum evttype : al::span{types, static_cast<uint>(count)})
{ {
auto etype = GetEventType(evttype); auto etype = GetEventType(evttype);
if(!etype) if(!etype)
throw al::context_error{AL_INVALID_ENUM, "Invalid event type 0x%04x", evttype}; context->throw_error(AL_INVALID_ENUM, "Invalid event type {:#04x}",
as_unsigned(evttype));
flags.set(al::to_underlying(*etype)); flags.set(al::to_underlying(*etype));
} }
@ -226,15 +224,22 @@ try {
std::lock_guard<std::mutex> eventlock{context->mEventCbLock}; std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
} }
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam) AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam)
FORCE_ALIGN void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context, FORCE_ALIGN void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context,
ALEVENTPROCSOFT callback, void *userParam) noexcept ALEVENTPROCSOFT callback, void *userParam) noexcept
{ try {
std::lock_guard<std::mutex> eventlock{context->mEventCbLock}; std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
context->mEventCb = callback; context->mEventCb = callback;
context->mEventParam = userParam; context->mEventParam = userParam;
} }
catch(al::base_exception&) {
}
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
}

View file

@ -21,13 +21,11 @@
#include "config.h" #include "config.h"
#include <string_view> #include <string_view>
#include <vector>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "alc/context.h" #include "alc/context.h"
#include "alc/inprogext.h"
#include "alstring.h" #include "alstring.h"
#include "direct_defs.h" #include "direct_defs.h"
#include "opthelpers.h" #include "opthelpers.h"

View file

@ -30,7 +30,6 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <numeric> #include <numeric>
#include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -41,12 +40,12 @@
#include "albit.h" #include "albit.h"
#include "alc/context.h" #include "alc/context.h"
#include "alc/device.h" #include "alc/device.h"
#include "alc/inprogext.h"
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alspan.h" #include "alspan.h"
#include "core/except.h"
#include "core/logging.h"
#include "direct_defs.h" #include "direct_defs.h"
#include "error.h"
#include "intrusive_ptr.h" #include "intrusive_ptr.h"
#include "opthelpers.h" #include "opthelpers.h"
@ -97,7 +96,8 @@ void InitFilterParams(ALfilter *filter, ALenum type)
filter->type = type; filter->type = type;
} }
auto EnsureFilters(ALCdevice *device, size_t needed) noexcept -> bool [[nodiscard]]
auto EnsureFilters(al::Device *device, size_t needed) noexcept -> bool
try { try {
size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), 0_uz, size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), 0_uz,
[](size_t cur, const FilterSubList &sublist) noexcept -> size_t [](size_t cur, const FilterSubList &sublist) noexcept -> size_t
@ -121,7 +121,8 @@ catch(...) {
} }
ALfilter *AllocFilter(ALCdevice *device) noexcept [[nodiscard]]
auto AllocFilter(al::Device *device) noexcept -> ALfilter*
{ {
auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(),
[](const FilterSubList &entry) noexcept -> bool [](const FilterSubList &entry) noexcept -> bool
@ -141,7 +142,7 @@ ALfilter *AllocFilter(ALCdevice *device) noexcept
return filter; return filter;
} }
void FreeFilter(ALCdevice *device, ALfilter *filter) void FreeFilter(al::Device *device, ALfilter *filter)
{ {
device->mFilterNames.erase(filter->id); device->mFilterNames.erase(filter->id);
@ -155,7 +156,8 @@ void FreeFilter(ALCdevice *device, ALfilter *filter)
} }
auto LookupFilter(ALCdevice *device, ALuint id) noexcept -> ALfilter* [[nodiscard]]
auto LookupFilter(al::Device *device, ALuint id) noexcept -> ALfilter*
{ {
const size_t lidx{(id-1) >> 6}; const size_t lidx{(id-1) >> 6};
const ALuint slidx{(id-1) & 0x3f}; const ALuint slidx{(id-1) & 0x3f};
@ -172,171 +174,176 @@ auto LookupFilter(ALCdevice *device, ALuint id) noexcept -> ALfilter*
/* Null filter parameter handlers */ /* Null filter parameter handlers */
template<> template<>
void FilterTable<NullFilterTable>::setParami(ALfilter*, ALenum param, int) void FilterTable<NullFilterTable>::setParami(ALCcontext *context, ALfilter*, ALenum param, int)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<NullFilterTable>::setParamiv(ALfilter*, ALenum param, const int*) void FilterTable<NullFilterTable>::setParamiv(ALCcontext *context, ALfilter*, ALenum param, const int*)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<NullFilterTable>::setParamf(ALfilter*, ALenum param, float) void FilterTable<NullFilterTable>::setParamf(ALCcontext *context, ALfilter*, ALenum param, float)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<NullFilterTable>::setParamfv(ALfilter*, ALenum param, const float*) void FilterTable<NullFilterTable>::setParamfv(ALCcontext *context, ALfilter*, ALenum param, const float*)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<NullFilterTable>::getParami(const ALfilter*, ALenum param, int*) void FilterTable<NullFilterTable>::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<NullFilterTable>::getParamiv(const ALfilter*, ALenum param, int*) void FilterTable<NullFilterTable>::getParamiv(ALCcontext *context, const ALfilter*, ALenum param, int*)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<NullFilterTable>::getParamf(const ALfilter*, ALenum param, float*) void FilterTable<NullFilterTable>::getParamf(ALCcontext *context, const ALfilter*, ALenum param, float*)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<NullFilterTable>::getParamfv(const ALfilter*, ALenum param, float*) void FilterTable<NullFilterTable>::getParamfv(ALCcontext *context, const ALfilter*, ALenum param, float*)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); }
/* Lowpass parameter handlers */ /* Lowpass parameter handlers */
template<> template<>
void FilterTable<LowpassFilterTable>::setParami(ALfilter*, ALenum param, int) void FilterTable<LowpassFilterTable>::setParami(ALCcontext *context, ALfilter*, ALenum param, int)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid low-pass integer property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<LowpassFilterTable>::setParamiv(ALfilter *filter, ALenum param, const int *values) void FilterTable<LowpassFilterTable>::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values)
{ setParami(filter, param, *values); } { setParami(context, filter, param, *values); }
template<> template<>
void FilterTable<LowpassFilterTable>::setParamf(ALfilter *filter, ALenum param, float val) void FilterTable<LowpassFilterTable>::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_LOWPASS_GAIN: case AL_LOWPASS_GAIN:
if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN))
throw al::context_error{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; context->throw_error(AL_INVALID_VALUE, "Low-pass gain {:f} out of range", val);
filter->Gain = val; filter->Gain = val;
return; return;
case AL_LOWPASS_GAINHF: case AL_LOWPASS_GAINHF:
if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF))
throw al::context_error{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val}; context->throw_error(AL_INVALID_VALUE, "Low-pass gainhf {:f} out of range", val);
filter->GainHF = val; filter->GainHF = val;
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid low-pass float property {:#04x}",
as_unsigned(param));
} }
template<> template<>
void FilterTable<LowpassFilterTable>::setParamfv(ALfilter *filter, ALenum param, const float *vals) void FilterTable<LowpassFilterTable>::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals)
{ setParamf(filter, param, *vals); } { setParamf(context, filter, param, *vals); }
template<> template<>
void FilterTable<LowpassFilterTable>::getParami(const ALfilter*, ALenum param, int*) void FilterTable<LowpassFilterTable>::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid low-pass integer property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<LowpassFilterTable>::getParamiv(const ALfilter *filter, ALenum param, int *values) void FilterTable<LowpassFilterTable>::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values)
{ getParami(filter, param, values); } { getParami(context, filter, param, values); }
template<> template<>
void FilterTable<LowpassFilterTable>::getParamf(const ALfilter *filter, ALenum param, float *val) void FilterTable<LowpassFilterTable>::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_LOWPASS_GAIN: *val = filter->Gain; return; case AL_LOWPASS_GAIN: *val = filter->Gain; return;
case AL_LOWPASS_GAINHF: *val = filter->GainHF; return; case AL_LOWPASS_GAINHF: *val = filter->GainHF; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid low-pass float property {:#04x}",
as_unsigned(param));
} }
template<> template<>
void FilterTable<LowpassFilterTable>::getParamfv(const ALfilter *filter, ALenum param, float *vals) void FilterTable<LowpassFilterTable>::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals)
{ getParamf(filter, param, vals); } { getParamf(context, filter, param, vals); }
/* Highpass parameter handlers */ /* Highpass parameter handlers */
template<> template<>
void FilterTable<HighpassFilterTable>::setParami(ALfilter*, ALenum param, int) void FilterTable<HighpassFilterTable>::setParami(ALCcontext *context, ALfilter*, ALenum param, int)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid high-pass integer property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<HighpassFilterTable>::setParamiv(ALfilter *filter, ALenum param, const int *values) void FilterTable<HighpassFilterTable>::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values)
{ setParami(filter, param, *values); } { setParami(context, filter, param, *values); }
template<> template<>
void FilterTable<HighpassFilterTable>::setParamf(ALfilter *filter, ALenum param, float val) void FilterTable<HighpassFilterTable>::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_HIGHPASS_GAIN: case AL_HIGHPASS_GAIN:
if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN))
throw al::context_error{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; context->throw_error(AL_INVALID_VALUE, "High-pass gain {:f} out of range", val);
filter->Gain = val; filter->Gain = val;
return; return;
case AL_HIGHPASS_GAINLF: case AL_HIGHPASS_GAINLF:
if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF))
throw al::context_error{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val}; context->throw_error(AL_INVALID_VALUE, "High-pass gainlf {:f} out of range", val);
filter->GainLF = val; filter->GainLF = val;
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid high-pass float property {:#04x}",
as_unsigned(param));
} }
template<> template<>
void FilterTable<HighpassFilterTable>::setParamfv(ALfilter *filter, ALenum param, const float *vals) void FilterTable<HighpassFilterTable>::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals)
{ setParamf(filter, param, *vals); } { setParamf(context, filter, param, *vals); }
template<> template<>
void FilterTable<HighpassFilterTable>::getParami(const ALfilter*, ALenum param, int*) void FilterTable<HighpassFilterTable>::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid high-pass integer property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<HighpassFilterTable>::getParamiv(const ALfilter *filter, ALenum param, int *values) void FilterTable<HighpassFilterTable>::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values)
{ getParami(filter, param, values); } { getParami(context, filter, param, values); }
template<> template<>
void FilterTable<HighpassFilterTable>::getParamf(const ALfilter *filter, ALenum param, float *val) void FilterTable<HighpassFilterTable>::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_HIGHPASS_GAIN: *val = filter->Gain; return; case AL_HIGHPASS_GAIN: *val = filter->Gain; return;
case AL_HIGHPASS_GAINLF: *val = filter->GainLF; return; case AL_HIGHPASS_GAINLF: *val = filter->GainLF; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid high-pass float property {:#04x}",
as_unsigned(param));
} }
template<> template<>
void FilterTable<HighpassFilterTable>::getParamfv(const ALfilter *filter, ALenum param, float *vals) void FilterTable<HighpassFilterTable>::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals)
{ getParamf(filter, param, vals); } { getParamf(context, filter, param, vals); }
/* Bandpass parameter handlers */ /* Bandpass parameter handlers */
template<> template<>
void FilterTable<BandpassFilterTable>::setParami(ALfilter*, ALenum param, int) void FilterTable<BandpassFilterTable>::setParami(ALCcontext *context, ALfilter*, ALenum param, int)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid band-pass integer property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<BandpassFilterTable>::setParamiv(ALfilter *filter, ALenum param, const int *values) void FilterTable<BandpassFilterTable>::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values)
{ setParami(filter, param, *values); } { setParami(context, filter, param, *values); }
template<> template<>
void FilterTable<BandpassFilterTable>::setParamf(ALfilter *filter, ALenum param, float val) void FilterTable<BandpassFilterTable>::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_BANDPASS_GAIN: case AL_BANDPASS_GAIN:
if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN))
throw al::context_error{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; context->throw_error(AL_INVALID_VALUE, "Band-pass gain {:f} out of range", val);
filter->Gain = val; filter->Gain = val;
return; return;
case AL_BANDPASS_GAINHF: case AL_BANDPASS_GAINHF:
if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF))
throw al::context_error{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val}; context->throw_error(AL_INVALID_VALUE, "Band-pass gainhf {:f} out of range", val);
filter->GainHF = val; filter->GainHF = val;
return; return;
case AL_BANDPASS_GAINLF: case AL_BANDPASS_GAINLF:
if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF))
throw al::context_error{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val}; context->throw_error(AL_INVALID_VALUE, "Band-pass gainlf {:f} out of range", val);
filter->GainLF = val; filter->GainLF = val;
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid band-pass float property {:#04x}",
as_unsigned(param));
} }
template<> template<>
void FilterTable<BandpassFilterTable>::setParamfv(ALfilter *filter, ALenum param, const float *vals) void FilterTable<BandpassFilterTable>::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals)
{ setParamf(filter, param, *vals); } { setParamf(context, filter, param, *vals); }
template<> template<>
void FilterTable<BandpassFilterTable>::getParami(const ALfilter*, ALenum param, int*) void FilterTable<BandpassFilterTable>::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*)
{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } { context->throw_error(AL_INVALID_ENUM, "Invalid band-pass integer property {:#04x}", as_unsigned(param)); }
template<> template<>
void FilterTable<BandpassFilterTable>::getParamiv(const ALfilter *filter, ALenum param, int *values) void FilterTable<BandpassFilterTable>::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values)
{ getParami(filter, param, values); } { getParami(context, filter, param, values); }
template<> template<>
void FilterTable<BandpassFilterTable>::getParamf(const ALfilter *filter, ALenum param, float *val) void FilterTable<BandpassFilterTable>::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
@ -344,32 +351,35 @@ void FilterTable<BandpassFilterTable>::getParamf(const ALfilter *filter, ALenum
case AL_BANDPASS_GAINHF: *val = filter->GainHF; return; case AL_BANDPASS_GAINHF: *val = filter->GainHF; return;
case AL_BANDPASS_GAINLF: *val = filter->GainLF; return; case AL_BANDPASS_GAINLF: *val = filter->GainLF; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid band-pass float property {:#04x}",
as_unsigned(param));
} }
template<> template<>
void FilterTable<BandpassFilterTable>::getParamfv(const ALfilter *filter, ALenum param, float *vals) void FilterTable<BandpassFilterTable>::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals)
{ getParamf(filter, param, vals); } { getParamf(context, filter, param, vals); }
AL_API DECL_FUNC2(void, alGenFilters, ALsizei,n, ALuint*,filters) AL_API DECL_FUNC2(void, alGenFilters, ALsizei,n, ALuint*,filters)
FORCE_ALIGN void AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) noexcept FORCE_ALIGN void AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) noexcept
try { try {
if(n < 0) if(n < 0)
throw al::context_error{AL_INVALID_VALUE, "Generating %d filters", n}; context->throw_error(AL_INVALID_VALUE, "Generating {} filters", n);
if(n <= 0) UNLIKELY return; if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
const al::span fids{filters, static_cast<ALuint>(n)}; const al::span fids{filters, static_cast<ALuint>(n)};
if(!EnsureFilters(device, fids.size())) if(!EnsureFilters(device, fids.size()))
throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} filter{}", n,
(n == 1) ? "" : "s"}; (n==1) ? "" : "s");
std::generate(fids.begin(), fids.end(), [device]{ return AllocFilter(device)->id; }); std::generate(fids.begin(), fids.end(), [device]{ return AllocFilter(device)->id; });
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC2(void, alDeleteFilters, ALsizei,n, const ALuint*,filters) AL_API DECL_FUNC2(void, alDeleteFilters, ALsizei,n, const ALuint*,filters)
@ -377,11 +387,11 @@ FORCE_ALIGN void AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei
const ALuint *filters) noexcept const ALuint *filters) noexcept
try { try {
if(n < 0) if(n < 0)
throw al::context_error{AL_INVALID_VALUE, "Deleting %d filters", n}; context->throw_error(AL_INVALID_VALUE, "Deleting {} filters", n);
if(n <= 0) UNLIKELY return; if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
/* First try to find any filters that are invalid. */ /* First try to find any filters that are invalid. */
auto validate_filter = [device](const ALuint fid) -> bool auto validate_filter = [device](const ALuint fid) -> bool
@ -390,7 +400,7 @@ try {
const al::span fids{filters, static_cast<ALuint>(n)}; const al::span fids{filters, static_cast<ALuint>(n)};
auto invflt = std::find_if_not(fids.begin(), fids.end(), validate_filter); auto invflt = std::find_if_not(fids.begin(), fids.end(), validate_filter);
if(invflt != fids.end()) if(invflt != fids.end())
throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", *invflt}; context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", *invflt);
/* All good. Delete non-0 filter IDs. */ /* All good. Delete non-0 filter IDs. */
auto delete_filter = [device](const ALuint fid) -> void auto delete_filter = [device](const ALuint fid) -> void
@ -400,15 +410,17 @@ try {
}; };
std::for_each(fids.begin(), fids.end(), delete_filter); std::for_each(fids.begin(), fids.end(), delete_filter);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC1(ALboolean, alIsFilter, ALuint,filter) AL_API DECL_FUNC1(ALboolean, alIsFilter, ALuint,filter)
FORCE_ALIGN ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) noexcept FORCE_ALIGN ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) noexcept
{ {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
if(!filter || LookupFilter(device, filter)) if(!filter || LookupFilter(device, filter))
return AL_TRUE; return AL_TRUE;
return AL_FALSE; return AL_FALSE;
@ -419,29 +431,32 @@ AL_API DECL_FUNC3(void, alFilteri, ALuint,filter, ALenum,param, ALint,value)
FORCE_ALIGN void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, FORCE_ALIGN void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param,
ALint value) noexcept ALint value) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)}; ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) if(!alfilt)
throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter);
switch(param) switch(param)
{ {
case AL_FILTER_TYPE: case AL_FILTER_TYPE:
if(!(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS if(!(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS
|| value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)) || value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS))
throw al::context_error{AL_INVALID_VALUE, "Invalid filter type 0x%04x", value}; context->throw_error(AL_INVALID_VALUE, "Invalid filter type {:#04x}",
as_unsigned(value));
InitFilterParams(alfilt, value); InitFilterParams(alfilt, value);
return; return;
} }
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([alfilt,param,value](auto&& thunk){thunk.setParami(alfilt, param, value);}, std::visit([context,alfilt,param,value](auto&& thunk)
alfilt->mTypeVariant); { thunk.setParami(context, alfilt, param, value); }, alfilt->mTypeVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alFilteriv, ALuint,filter, ALenum,param, const ALint*,values) AL_API DECL_FUNC3(void, alFilteriv, ALuint,filter, ALenum,param, const ALint*,values)
@ -455,83 +470,89 @@ try {
return; return;
} }
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)}; ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) if(!alfilt)
throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([alfilt,param,values](auto&& thunk){thunk.setParamiv(alfilt, param, values);}, std::visit([context,alfilt,param,values](auto&& thunk)
alfilt->mTypeVariant); { thunk.setParamiv(context, alfilt, param, values); }, alfilt->mTypeVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alFilterf, ALuint,filter, ALenum,param, ALfloat,value) AL_API DECL_FUNC3(void, alFilterf, ALuint,filter, ALenum,param, ALfloat,value)
FORCE_ALIGN void AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, FORCE_ALIGN void AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param,
ALfloat value) noexcept ALfloat value) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)}; ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) if(!alfilt)
throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([alfilt,param,value](auto&& thunk){thunk.setParamf(alfilt, param, value);}, std::visit([context,alfilt,param,value](auto&& thunk)
alfilt->mTypeVariant); { thunk.setParamf(context, alfilt, param, value); }, alfilt->mTypeVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alFilterfv, ALuint,filter, ALenum,param, const ALfloat*,values) AL_API DECL_FUNC3(void, alFilterfv, ALuint,filter, ALenum,param, const ALfloat*,values)
FORCE_ALIGN void AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, FORCE_ALIGN void AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param,
const ALfloat *values) noexcept const ALfloat *values) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)}; ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) if(!alfilt)
throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([alfilt,param,values](auto&& thunk){thunk.setParamfv(alfilt, param, values);}, std::visit([context,alfilt,param,values](auto&& thunk)
alfilt->mTypeVariant); { thunk.setParamfv(context, alfilt, param, values); }, alfilt->mTypeVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetFilteri, ALuint,filter, ALenum,param, ALint*,value) AL_API DECL_FUNC3(void, alGetFilteri, ALuint,filter, ALenum,param, ALint*,value)
FORCE_ALIGN void AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, FORCE_ALIGN void AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param,
ALint *value) noexcept ALint *value) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)}; const ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) if(!alfilt)
throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter);
switch(param) switch(param)
{ {
case AL_FILTER_TYPE: case AL_FILTER_TYPE: *value = alfilt->type; return;
*value = alfilt->type;
return;
} }
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([alfilt,param,value](auto&& thunk){thunk.getParami(alfilt, param, value);}, std::visit([context,alfilt,param,value](auto&& thunk)
alfilt->mTypeVariant); { thunk.getParami(context, alfilt, param, value); }, alfilt->mTypeVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetFilteriv, ALuint,filter, ALenum,param, ALint*,values) AL_API DECL_FUNC3(void, alGetFilteriv, ALuint,filter, ALenum,param, ALint*,values)
@ -545,68 +566,74 @@ try {
return; return;
} }
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)}; const ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) if(!alfilt)
throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([alfilt,param,values](auto&& thunk){thunk.getParamiv(alfilt, param, values);}, std::visit([context,alfilt,param,values](auto&& thunk)
alfilt->mTypeVariant); { thunk.getParamiv(context, alfilt, param, values); }, alfilt->mTypeVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetFilterf, ALuint,filter, ALenum,param, ALfloat*,value) AL_API DECL_FUNC3(void, alGetFilterf, ALuint,filter, ALenum,param, ALfloat*,value)
FORCE_ALIGN void AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, FORCE_ALIGN void AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param,
ALfloat *value) noexcept ALfloat *value) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)}; const ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) UNLIKELY if(!alfilt)
throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([alfilt,param,value](auto&& thunk){thunk.getParamf(alfilt, param, value);}, std::visit([context,alfilt,param,value](auto&& thunk)
alfilt->mTypeVariant); { thunk.getParamf(context, alfilt, param, value); }, alfilt->mTypeVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC3(void, alGetFilterfv, ALuint,filter, ALenum,param, ALfloat*,values) AL_API DECL_FUNC3(void, alGetFilterfv, ALuint,filter, ALenum,param, ALfloat*,values)
FORCE_ALIGN void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, FORCE_ALIGN void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param,
ALfloat *values) noexcept ALfloat *values) noexcept
try { try {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)}; const ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) UNLIKELY if(!alfilt)
throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter);
/* Call the appropriate handler */ /* Call the appropriate handler */
std::visit([alfilt,param,values](auto&& thunk){thunk.getParamfv(alfilt, param, values);}, std::visit([context,alfilt,param,values](auto&& thunk)
alfilt->mTypeVariant); { thunk.getParamfv(context, alfilt, param, values); }, alfilt->mTypeVariant);
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
void ALfilter::SetName(ALCcontext *context, ALuint id, std::string_view name) void ALfilter::SetName(ALCcontext *context, ALuint id, std::string_view name)
{ {
ALCdevice *device{context->mALDevice.get()}; auto *device = context->mALDevice.get();
std::lock_guard<std::mutex> filterlock{device->FilterLock}; auto filterlock = std::lock_guard{device->FilterLock};
auto filter = LookupFilter(device, id); auto filter = LookupFilter(device, id);
if(!filter) if(!filter)
throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", id}; context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", id);
device->mFilterNames.insert_or_assign(id, name); device->mFilterNames.insert_or_assign(id, name);
} }

View file

@ -14,21 +14,27 @@
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
struct ALfilter;
inline constexpr float LowPassFreqRef{5000.0f}; inline constexpr float LowPassFreqRef{5000.0f};
inline constexpr float HighPassFreqRef{250.0f}; inline constexpr float HighPassFreqRef{250.0f};
template<typename T> template<typename T>
struct FilterTable { struct FilterTable {
static void setParami(struct ALfilter*, ALenum, int); static void setParami(ALCcontext*, ALfilter*, ALenum, int);
static void setParamiv(struct ALfilter*, ALenum, const int*); static void setParamiv(ALCcontext*, ALfilter*, ALenum, const int*);
static void setParamf(struct ALfilter*, ALenum, float); static void setParamf(ALCcontext*, ALfilter*, ALenum, float);
static void setParamfv(struct ALfilter*, ALenum, const float*); static void setParamfv(ALCcontext*, ALfilter*, ALenum, const float*);
static void getParami(const struct ALfilter*, ALenum, int*); static void getParami(ALCcontext*, const ALfilter*, ALenum, int*);
static void getParamiv(const struct ALfilter*, ALenum, int*); static void getParamiv(ALCcontext*, const ALfilter*, ALenum, int*);
static void getParamf(const struct ALfilter*, ALenum, float*); static void getParamf(ALCcontext*, const ALfilter*, ALenum, float*);
static void getParamfv(const struct ALfilter*, ALenum, float*); static void getParamfv(ALCcontext*, const ALfilter*, ALenum, float*);
private:
FilterTable() = default;
friend T;
}; };
struct NullFilterTable : public FilterTable<NullFilterTable> { }; struct NullFilterTable : public FilterTable<NullFilterTable> { };

View file

@ -31,11 +31,11 @@
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/context.h" #include "alc/context.h"
#include "alc/inprogext.h" #include "alnumeric.h"
#include "alspan.h" #include "alspan.h"
#include "core/except.h"
#include "core/logging.h"
#include "direct_defs.h" #include "direct_defs.h"
#include "error.h"
#include "opthelpers.h"
namespace { namespace {
@ -54,7 +54,7 @@ inline void CommitAndUpdateProps(ALCcontext *context)
{ {
if(!context->mDeferUpdates) if(!context->mDeferUpdates)
{ {
#ifdef ALSOFT_EAX #if ALSOFT_EAX
if(context->eaxNeedsCommit()) if(context->eaxNeedsCommit())
{ {
context->mPropsDirty = true; context->mPropsDirty = true;
@ -79,22 +79,26 @@ try {
{ {
case AL_GAIN: case AL_GAIN:
if(!(value >= 0.0f && std::isfinite(value))) if(!(value >= 0.0f && std::isfinite(value)))
throw al::context_error{AL_INVALID_VALUE, "Listener gain out of range"}; context->throw_error(AL_INVALID_VALUE, "Listener gain {:f} out of range", value);
listener.Gain = value; listener.Gain = value;
UpdateProps(context); UpdateProps(context);
return; return;
case AL_METERS_PER_UNIT: case AL_METERS_PER_UNIT:
if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
throw al::context_error{AL_INVALID_VALUE, "Listener meters per unit out of range"}; context->throw_error(AL_INVALID_VALUE, "Listener meters per unit {:f} out of range",
value);
listener.mMetersPerUnit = value; listener.mMetersPerUnit = value;
UpdateProps(context); UpdateProps(context);
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid listener float property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3)
@ -107,7 +111,7 @@ try {
{ {
case AL_POSITION: case AL_POSITION:
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
throw al::context_error{AL_INVALID_VALUE, "Listener position out of range"}; context->throw_error(AL_INVALID_VALUE, "Listener position out of range");
listener.Position[0] = value1; listener.Position[0] = value1;
listener.Position[1] = value2; listener.Position[1] = value2;
listener.Position[2] = value3; listener.Position[2] = value3;
@ -116,17 +120,20 @@ try {
case AL_VELOCITY: case AL_VELOCITY:
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
throw al::context_error{AL_INVALID_VALUE, "Listener velocity out of range"}; context->throw_error(AL_INVALID_VALUE, "Listener velocity out of range");
listener.Velocity[0] = value1; listener.Velocity[0] = value1;
listener.Velocity[1] = value2; listener.Velocity[1] = value2;
listener.Velocity[2] = value3; listener.Velocity[2] = value3;
CommitAndUpdateProps(context); CommitAndUpdateProps(context);
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-float property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values) AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values)
@ -134,7 +141,7 @@ FORCE_ALIGN void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum para
const ALfloat *values) noexcept const ALfloat *values) noexcept
try { try {
if(!values) if(!values)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; context->throw_error(AL_INVALID_VALUE, "NULL pointer");
switch(param) switch(param)
{ {
@ -157,17 +164,20 @@ try {
case AL_ORIENTATION: case AL_ORIENTATION:
auto vals = al::span<const float,6>{values, 6_uz}; auto vals = al::span<const float,6>{values, 6_uz};
if(!std::all_of(vals.cbegin(), vals.cend(), [](float f) { return std::isfinite(f); })) if(!std::all_of(vals.cbegin(), vals.cend(), [](float f) { return std::isfinite(f); }))
return context->setError(AL_INVALID_VALUE, "Listener orientation out of range"); context->throw_error(AL_INVALID_VALUE, "Listener orientation out of range");
/* AT then UP */ /* AT then UP */
std::copy_n(vals.cbegin(), 3, listener.OrientAt.begin()); std::copy_n(vals.cbegin(), 3, listener.OrientAt.begin());
std::copy_n(vals.cbegin()+3, 3, listener.OrientUp.begin()); std::copy_n(vals.cbegin()+3, 3, listener.OrientUp.begin());
CommitAndUpdateProps(context); CommitAndUpdateProps(context);
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid listener float-vector property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
@ -175,10 +185,13 @@ AL_API DECL_FUNC2(void, alListeneri, ALenum,param, ALint,value)
FORCE_ALIGN void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint /*value*/) noexcept FORCE_ALIGN void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint /*value*/) noexcept
try { try {
std::lock_guard<std::mutex> proplock{context->mPropLock}; std::lock_guard<std::mutex> proplock{context->mPropLock};
throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid listener integer property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC4(void, alListener3i, ALenum,param, ALint,value1, ALint,value2, ALint,value3) AL_API DECL_FUNC4(void, alListener3i, ALenum,param, ALint,value1, ALint,value2, ALint,value3)
@ -195,10 +208,13 @@ try {
} }
std::lock_guard<std::mutex> proplock{context->mPropLock}; std::lock_guard<std::mutex> proplock{context->mPropLock};
throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-integer property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC2(void, alListeneriv, ALenum,param, const ALint*,values) AL_API DECL_FUNC2(void, alListeneriv, ALenum,param, const ALint*,values)
@ -206,7 +222,7 @@ FORCE_ALIGN void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum para
const ALint *values) noexcept const ALint *values) noexcept
try { try {
if(!values) if(!values)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; context->throw_error(AL_INVALID_VALUE, "NULL pointer");
al::span<const ALint> vals; al::span<const ALint> vals;
switch(param) switch(param)
@ -229,11 +245,13 @@ try {
} }
std::lock_guard<std::mutex> proplock{context->mPropLock}; std::lock_guard<std::mutex> proplock{context->mPropLock};
throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x", context->throw_error(AL_INVALID_ENUM, "Invalid listener integer-vector property {:#04x}",
param}; as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
@ -242,7 +260,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum pa
ALfloat *value) noexcept ALfloat *value) noexcept
try { try {
if(!value) if(!value)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; context->throw_error(AL_INVALID_VALUE, "NULL pointer");
ALlistener &listener = context->mListener; ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> proplock{context->mPropLock}; std::lock_guard<std::mutex> proplock{context->mPropLock};
@ -251,10 +269,13 @@ try {
case AL_GAIN: *value = listener.Gain; return; case AL_GAIN: *value = listener.Gain; return;
case AL_METERS_PER_UNIT: *value = listener.mMetersPerUnit; return; case AL_METERS_PER_UNIT: *value = listener.mMetersPerUnit; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid listener float property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC4(void, alGetListener3f, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) AL_API DECL_FUNC4(void, alGetListener3f, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3)
@ -262,7 +283,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum p
ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept
try { try {
if(!value1 || !value2 || !value3) if(!value1 || !value2 || !value3)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; context->throw_error(AL_INVALID_VALUE, "NULL pointer");
ALlistener &listener = context->mListener; ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> proplock{context->mPropLock}; std::lock_guard<std::mutex> proplock{context->mPropLock};
@ -280,10 +301,13 @@ try {
*value3 = listener.Velocity[2]; *value3 = listener.Velocity[2];
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-float property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC2(void, alGetListenerfv, ALenum,param, ALfloat*,values) AL_API DECL_FUNC2(void, alGetListenerfv, ALenum,param, ALfloat*,values)
@ -291,7 +315,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum p
ALfloat *values) noexcept ALfloat *values) noexcept
try { try {
if(!values) if(!values)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; context->throw_error(AL_INVALID_VALUE, "NULL pointer");
switch(param) switch(param)
{ {
@ -318,22 +342,28 @@ try {
std::copy_n(listener.OrientUp.cbegin(), 3, vals.begin()+3); std::copy_n(listener.OrientUp.cbegin(), 3, vals.begin()+3);
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid listener float-vector property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC2(void, alGetListeneri, ALenum,param, ALint*,value) AL_API DECL_FUNC2(void, alGetListeneri, ALenum,param, ALint*,value)
FORCE_ALIGN void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) noexcept FORCE_ALIGN void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) noexcept
try { try {
if(!value) throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer");
std::lock_guard<std::mutex> proplock{context->mPropLock}; std::lock_guard<std::mutex> proplock{context->mPropLock};
throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid listener integer property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC4(void, alGetListener3i, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) AL_API DECL_FUNC4(void, alGetListener3i, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3)
@ -341,7 +371,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum p
ALint *value1, ALint *value2, ALint *value3) noexcept ALint *value1, ALint *value2, ALint *value3) noexcept
try { try {
if(!value1 || !value2 || !value3) if(!value1 || !value2 || !value3)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; context->throw_error(AL_INVALID_VALUE, "NULL pointer");
ALlistener &listener = context->mListener; ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> proplock{context->mPropLock}; std::lock_guard<std::mutex> proplock{context->mPropLock};
@ -359,10 +389,13 @@ try {
*value3 = static_cast<ALint>(listener.Velocity[2]); *value3 = static_cast<ALint>(listener.Velocity[2]);
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param}; context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-integer property {:#04x}",
as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }
AL_API DECL_FUNC2(void, alGetListeneriv, ALenum,param, ALint*,values) AL_API DECL_FUNC2(void, alGetListeneriv, ALenum,param, ALint*,values)
@ -370,7 +403,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum p
ALint *values) noexcept ALint *values) noexcept
try { try {
if(!values) if(!values)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; context->throw_error(AL_INVALID_VALUE, "NULL pointer");
switch(param) switch(param)
{ {
@ -394,9 +427,11 @@ try {
std::transform(listener.OrientUp.cbegin(), listener.OrientUp.cend(), vals.begin()+3, f2i); std::transform(listener.OrientUp.cbegin(), listener.OrientUp.cend(), vals.begin()+3, f2i);
return; return;
} }
throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x", context->throw_error(AL_INVALID_ENUM, "Invalid listener integer-vector property {:#04x}",
param}; as_unsigned(param));
} }
catch(al::context_error& e) { catch(al::base_exception&) {
context->setError(e.errorCode(), "%s", e.what()); }
catch(std::exception &e) {
ERR("Caught exception: {}", e.what());
} }

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,8 @@
#ifndef AL_SOURCE_H #ifndef AL_SOURCE_H
#define AL_SOURCE_H #define AL_SOURCE_H
#include "config.h"
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
@ -20,7 +22,7 @@
#include "core/context.h" #include "core/context.h"
#include "core/voice.h" #include "core/voice.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "eax/api.h" #include "eax/api.h"
#include "eax/call.h" #include "eax/call.h"
#include "eax/exception.h" #include "eax/exception.h"
@ -45,12 +47,10 @@ inline bool sBufferSubDataCompat{false};
struct ALbufferQueueItem : public VoiceBufferItem { struct ALbufferQueueItem : public VoiceBufferItem {
ALbuffer *mBuffer{nullptr}; ALbuffer *mBuffer{nullptr};
DISABLE_ALLOC
}; };
#ifdef ALSOFT_EAX #if ALSOFT_EAX
class EaxSourceException : public EaxException { class EaxSourceException : public EaxException {
public: public:
explicit EaxSourceException(const char* message) explicit EaxSourceException(const char* message)
@ -71,7 +71,7 @@ struct ALsource {
float RefDistance{1.0f}; float RefDistance{1.0f};
float MaxDistance{std::numeric_limits<float>::max()}; float MaxDistance{std::numeric_limits<float>::max()};
float RolloffFactor{1.0f}; float RolloffFactor{1.0f};
#ifdef ALSOFT_EAX #if ALSOFT_EAX
// For EAXSOURCE_ROLLOFFFACTOR, which is distinct from and added to // For EAXSOURCE_ROLLOFFFACTOR, which is distinct from and added to
// AL_ROLLOFF_FACTOR // AL_ROLLOFF_FACTOR
float RolloffFactor2{0.0f}; float RolloffFactor2{0.0f};
@ -165,10 +165,10 @@ struct ALsource {
DISABLE_ALLOC DISABLE_ALLOC
#ifdef ALSOFT_EAX #if ALSOFT_EAX
public: public:
void eaxInitialize(ALCcontext *context) noexcept; void eaxInitialize(ALCcontext *context) noexcept;
void eaxDispatch(const EaxCall& call); void eaxDispatch(const EaxCall& call) { call.is_get() ? eax_get(call) : eax_set(call); }
void eaxCommit(); void eaxCommit();
void eaxMarkAsChanged() noexcept { mEaxChanged = true; } void eaxMarkAsChanged() noexcept { mEaxChanged = true; }
@ -199,26 +199,23 @@ private:
using EaxSpeakerLevels = std::array<EAXSPEAKERLEVELPROPERTIES, eax_max_speakers>; using EaxSpeakerLevels = std::array<EAXSPEAKERLEVELPROPERTIES, eax_max_speakers>;
using EaxSends = std::array<EAXSOURCEALLSENDPROPERTIES, EAX_MAX_FXSLOTS>; using EaxSends = std::array<EAXSOURCEALLSENDPROPERTIES, EAX_MAX_FXSLOTS>;
using Eax1Props = EAXBUFFER_REVERBPROPERTIES;
struct Eax1State { struct Eax1State {
Eax1Props i; // Immediate. EAXBUFFER_REVERBPROPERTIES i; // Immediate.
Eax1Props d; // Deferred. EAXBUFFER_REVERBPROPERTIES d; // Deferred.
}; };
using Eax2Props = EAX20BUFFERPROPERTIES;
struct Eax2State { struct Eax2State {
Eax2Props i; // Immediate. EAX20BUFFERPROPERTIES i; // Immediate.
Eax2Props d; // Deferred. EAX20BUFFERPROPERTIES d; // Deferred.
}; };
using Eax3Props = EAX30SOURCEPROPERTIES;
struct Eax3State { struct Eax3State {
Eax3Props i; // Immediate. EAX30SOURCEPROPERTIES i; // Immediate.
Eax3Props d; // Deferred. EAX30SOURCEPROPERTIES d; // Deferred.
}; };
struct Eax4Props { struct Eax4Props {
Eax3Props source; EAX30SOURCEPROPERTIES source;
EaxSends sends; EaxSends sends;
EAX40ACTIVEFXSLOTS active_fx_slots; EAX40ACTIVEFXSLOTS active_fx_slots;
}; };
@ -490,14 +487,14 @@ private:
}; };
struct Eax1SourceAllValidator { struct Eax1SourceAllValidator {
void operator()(const Eax1Props& props) const void operator()(const EAXBUFFER_REVERBPROPERTIES& props) const
{ {
Eax1SourceReverbMixValidator{}(props.fMix); Eax1SourceReverbMixValidator{}(props.fMix);
} }
}; };
struct Eax2SourceAllValidator { struct Eax2SourceAllValidator {
void operator()(const Eax2Props& props) const void operator()(const EAX20BUFFERPROPERTIES& props) const
{ {
Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectValidator{}(props.lDirect);
Eax2SourceDirectHfValidator{}(props.lDirectHF); Eax2SourceDirectHfValidator{}(props.lDirectHF);
@ -516,7 +513,7 @@ private:
}; };
struct Eax3SourceAllValidator { struct Eax3SourceAllValidator {
void operator()(const Eax3Props& props) const void operator()(const EAX30SOURCEPROPERTIES& props) const
{ {
Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectValidator{}(props.lDirect);
Eax2SourceDirectHfValidator{}(props.lDirectHF); Eax2SourceDirectHfValidator{}(props.lDirectHF);
@ -823,11 +820,11 @@ private:
[[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id(); [[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id();
static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept; static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept;
static void eax1_set_defaults(Eax1Props& props) noexcept; static void eax1_set_defaults(EAXBUFFER_REVERBPROPERTIES& props) noexcept;
void eax1_set_defaults() noexcept; void eax1_set_defaults() noexcept;
static void eax2_set_defaults(Eax2Props& props) noexcept; static void eax2_set_defaults(EAX20BUFFERPROPERTIES& props) noexcept;
void eax2_set_defaults() noexcept; void eax2_set_defaults() noexcept;
static void eax3_set_defaults(Eax3Props& props) noexcept; static void eax3_set_defaults(EAX30SOURCEPROPERTIES& props) noexcept;
void eax3_set_defaults() noexcept; void eax3_set_defaults() noexcept;
static void eax4_set_sends_defaults(EaxSends& sends) noexcept; static void eax4_set_sends_defaults(EaxSends& sends) noexcept;
static void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept; static void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept;
@ -840,9 +837,9 @@ private:
void eax5_set_defaults() noexcept; void eax5_set_defaults() noexcept;
void eax_set_defaults() noexcept; void eax_set_defaults() noexcept;
static void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept; static void eax1_translate(const EAXBUFFER_REVERBPROPERTIES& src, Eax5Props& dst) noexcept;
static void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept; static void eax2_translate(const EAX20BUFFERPROPERTIES& src, Eax5Props& dst) noexcept;
static void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept; static void eax3_translate(const EAX30SOURCEPROPERTIES& src, Eax5Props& dst) noexcept;
static void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept; static void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept;
static float eax_calculate_dst_occlusion_mb( static float eax_calculate_dst_occlusion_mb(
@ -907,12 +904,12 @@ private:
} }
static void eax_get_active_fx_slot_id(const EaxCall& call, const al::span<const GUID> src_ids); static void eax_get_active_fx_slot_id(const EaxCall& call, const al::span<const GUID> src_ids);
static void eax1_get(const EaxCall& call, const Eax1Props& props); static void eax1_get(const EaxCall& call, const EAXBUFFER_REVERBPROPERTIES& props);
static void eax2_get(const EaxCall& call, const Eax2Props& props); static void eax2_get(const EaxCall& call, const EAX20BUFFERPROPERTIES& props);
static void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); static void eax3_get_obstruction(const EaxCall& call, const EAX30SOURCEPROPERTIES& props);
static void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props); static void eax3_get_occlusion(const EaxCall& call, const EAX30SOURCEPROPERTIES& props);
static void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props); static void eax3_get_exclusion(const EaxCall& call, const EAX30SOURCEPROPERTIES& props);
static void eax3_get(const EaxCall& call, const Eax3Props& props); static void eax3_get(const EaxCall& call, const EAX30SOURCEPROPERTIES& props);
void eax4_get(const EaxCall& call, const Eax4Props& props); void eax4_get(const EaxCall& call, const Eax4Props& props);
static void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props); static void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props);
static void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props); static void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props);
@ -1034,9 +1031,9 @@ private:
void eax_set_efx_wet_gain_auto(); void eax_set_efx_wet_gain_auto();
void eax_set_efx_wet_gain_hf_auto(); void eax_set_efx_wet_gain_hf_auto();
static void eax1_set(const EaxCall& call, Eax1Props& props); static void eax1_set(const EaxCall& call, EAXBUFFER_REVERBPROPERTIES& props);
static void eax2_set(const EaxCall& call, Eax2Props& props); static void eax2_set(const EaxCall& call, EAX20BUFFERPROPERTIES& props);
void eax3_set(const EaxCall& call, Eax3Props& props); void eax3_set(const EaxCall& call, EAX30SOURCEPROPERTIES& props);
void eax4_set(const EaxCall& call, Eax4Props& props); void eax4_set(const EaxCall& call, Eax4Props& props);
static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props); static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props);
static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props); static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props);

View file

@ -40,6 +40,7 @@
#include "al/listener.h" #include "al/listener.h"
#include "alc/alu.h" #include "alc/alu.h"
#include "alc/context.h" #include "alc/context.h"
#include "alc/device.h"
#include "alc/inprogext.h" #include "alc/inprogext.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "atomic.h" #include "atomic.h"
@ -52,9 +53,7 @@
#include "opthelpers.h" #include "opthelpers.h"
#include "strutils.h" #include "strutils.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "alc/device.h"
#include "eax/globals.h" #include "eax/globals.h"
#include "eax/x_ram.h" #include "eax/x_ram.h"
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -62,6 +61,8 @@
namespace { namespace {
using ALvoidptr = ALvoid*;
[[nodiscard]] constexpr auto GetVendorString() noexcept { return "OpenAL Community"; } [[nodiscard]] constexpr auto GetVendorString() noexcept { return "OpenAL Community"; }
[[nodiscard]] constexpr auto GetVersionString() noexcept { return "1.1 ALSOFT " ALSOFT_VERSION; } [[nodiscard]] constexpr auto GetVersionString() noexcept { return "1.1 ALSOFT " ALSOFT_VERSION; }
[[nodiscard]] constexpr auto GetRendererString() noexcept { return "OpenAL Soft"; } [[nodiscard]] constexpr auto GetRendererString() noexcept { return "OpenAL Soft"; }
@ -94,6 +95,10 @@ template<> struct ResamplerName<Resampler::FastBSinc24>
{ static constexpr const ALchar *Get() noexcept { return "23rd order Sinc (fast)"; } }; { static constexpr const ALchar *Get() noexcept { return "23rd order Sinc (fast)"; } };
template<> struct ResamplerName<Resampler::BSinc24> template<> struct ResamplerName<Resampler::BSinc24>
{ static constexpr const ALchar *Get() noexcept { return "23rd order Sinc"; } }; { static constexpr const ALchar *Get() noexcept { return "23rd order Sinc"; } };
template<> struct ResamplerName<Resampler::FastBSinc48>
{ static constexpr const ALchar *Get() noexcept { return "47th order Sinc (fast)"; } };
template<> struct ResamplerName<Resampler::BSinc48>
{ static constexpr const ALchar *Get() noexcept { return "47th order Sinc"; } };
const ALchar *GetResamplerName(const Resampler rtype) const ALchar *GetResamplerName(const Resampler rtype)
{ {
@ -108,6 +113,8 @@ const ALchar *GetResamplerName(const Resampler rtype)
HANDLE_RESAMPLER(Resampler::BSinc12); HANDLE_RESAMPLER(Resampler::BSinc12);
HANDLE_RESAMPLER(Resampler::FastBSinc24); HANDLE_RESAMPLER(Resampler::FastBSinc24);
HANDLE_RESAMPLER(Resampler::BSinc24); HANDLE_RESAMPLER(Resampler::BSinc24);
HANDLE_RESAMPLER(Resampler::FastBSinc48);
HANDLE_RESAMPLER(Resampler::BSinc48);
} }
#undef HANDLE_RESAMPLER #undef HANDLE_RESAMPLER
/* Should never get here. */ /* Should never get here. */
@ -144,24 +151,24 @@ constexpr auto ALenumFromDistanceModel(DistanceModel model) -> ALenum
} }
enum PropertyValue : ALenum { enum PropertyValue : ALenum {
DopplerFactor = AL_DOPPLER_FACTOR, DopplerFactorProp = AL_DOPPLER_FACTOR,
DopplerVelocity = AL_DOPPLER_VELOCITY, DopplerVelocityProp = AL_DOPPLER_VELOCITY,
DistanceModel = AL_DISTANCE_MODEL, DistanceModelProp = AL_DISTANCE_MODEL,
SpeedOfSound = AL_SPEED_OF_SOUND, SpeedOfSoundProp = AL_SPEED_OF_SOUND,
DeferredUpdates = AL_DEFERRED_UPDATES_SOFT, DeferredUpdatesProp = AL_DEFERRED_UPDATES_SOFT,
GainLimit = AL_GAIN_LIMIT_SOFT, GainLimitProp = AL_GAIN_LIMIT_SOFT,
NumResamplers = AL_NUM_RESAMPLERS_SOFT, NumResamplersProp = AL_NUM_RESAMPLERS_SOFT,
DefaultResampler = AL_DEFAULT_RESAMPLER_SOFT, DefaultResamplerProp = AL_DEFAULT_RESAMPLER_SOFT,
DebugLoggedMessages = AL_DEBUG_LOGGED_MESSAGES_EXT, DebugLoggedMessagesProp = AL_DEBUG_LOGGED_MESSAGES_EXT,
DebugNextLoggedMessageLength = AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT, DebugNextLoggedMessageLengthProp = AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT,
MaxDebugMessageLength = AL_MAX_DEBUG_MESSAGE_LENGTH_EXT, MaxDebugMessageLengthProp = AL_MAX_DEBUG_MESSAGE_LENGTH_EXT,
MaxDebugLoggedMessages = AL_MAX_DEBUG_LOGGED_MESSAGES_EXT, MaxDebugLoggedMessagesProp = AL_MAX_DEBUG_LOGGED_MESSAGES_EXT,
MaxDebugGroupDepth = AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT, MaxDebugGroupDepthProp = AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT,
MaxLabelLength = AL_MAX_LABEL_LENGTH_EXT, MaxLabelLengthProp = AL_MAX_LABEL_LENGTH_EXT,
ContextFlags = AL_CONTEXT_FLAGS_EXT, ContextFlagsProp = AL_CONTEXT_FLAGS_EXT,
#ifdef ALSOFT_EAX #if ALSOFT_EAX
EaxRamSize = AL_EAX_RAM_SIZE, EaxRamSizeProp = AL_EAX_RAM_SIZE,
EaxRamFree = AL_EAX_RAM_FREE, EaxRamFreeProp = AL_EAX_RAM_FREE,
#endif #endif
}; };
@ -259,7 +266,7 @@ void GetValue(ALCcontext *context, ALenum pname, T *values)
*values = cast_value(context->mContextFlags.to_ulong()); *values = cast_value(context->mContextFlags.to_ulong());
return; return;
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#define EAX_ERROR "[alGetInteger] EAX not enabled" #define EAX_ERROR "[alGetInteger] EAX not enabled"
case AL_EAX_RAM_SIZE: case AL_EAX_RAM_SIZE:
@ -268,7 +275,7 @@ void GetValue(ALCcontext *context, ALenum pname, T *values)
*values = cast_value(eax_x_ram_max_size); *values = cast_value(eax_x_ram_max_size);
return; return;
} }
ERR(EAX_ERROR "\n"); ERR(EAX_ERROR);
break; break;
case AL_EAX_RAM_FREE: case AL_EAX_RAM_FREE:
@ -279,13 +286,13 @@ void GetValue(ALCcontext *context, ALenum pname, T *values)
*values = cast_value(device->eax_x_ram_free_size); *values = cast_value(device->eax_x_ram_free_size);
return; return;
} }
ERR(EAX_ERROR "\n"); ERR(EAX_ERROR);
break; break;
#undef EAX_ERROR #undef EAX_ERROR
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
} }
context->setError(AL_INVALID_ENUM, "Invalid context property 0x%04x", pname); context->setError(AL_INVALID_ENUM, "Invalid context property {:#04x}", as_unsigned(pname));
} }
@ -331,7 +338,8 @@ FORCE_ALIGN void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capabili
context->setError(AL_INVALID_OPERATION, "Re-enabling AL_STOP_SOURCES_ON_DISCONNECT_SOFT not yet supported"); context->setError(AL_INVALID_OPERATION, "Re-enabling AL_STOP_SOURCES_ON_DISCONNECT_SOFT not yet supported");
return; return;
} }
context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability); context->setError(AL_INVALID_VALUE, "Invalid enable property {:#04x}",
as_unsigned(capability));
} }
AL_API DECL_FUNC1(void, alDisable, ALenum,capability) AL_API DECL_FUNC1(void, alDisable, ALenum,capability)
@ -355,7 +363,8 @@ FORCE_ALIGN void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capabil
context->mStopVoicesOnDisconnect.store(false); context->mStopVoicesOnDisconnect.store(false);
return; return;
} }
context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability); context->setError(AL_INVALID_VALUE, "Invalid disable property {:#04x}",
as_unsigned(capability));
} }
AL_API DECL_FUNC1(ALboolean, alIsEnabled, ALenum,capability) AL_API DECL_FUNC1(ALboolean, alIsEnabled, ALenum,capability)
@ -369,14 +378,15 @@ FORCE_ALIGN ALboolean AL_APIENTRY alIsEnabledDirect(ALCcontext *context, ALenum
case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: case AL_STOP_SOURCES_ON_DISCONNECT_SOFT:
return context->mStopVoicesOnDisconnect.load() ? AL_TRUE : AL_FALSE; return context->mStopVoicesOnDisconnect.load() ? AL_TRUE : AL_FALSE;
} }
context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability); context->setError(AL_INVALID_VALUE, "Invalid is enabled property {:#04x}",
as_unsigned(capability));
return AL_FALSE; return AL_FALSE;
} }
#define DECL_GETFUNC(R, Name, Ext) \ #define DECL_GETFUNC(R, Name, Ext) \
AL_API auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \
{ \ { \
R value{}; \ auto value = R{}; \
auto context = GetContextRef(); \ auto context = GetContextRef(); \
if(!context) UNLIKELY return value; \ if(!context) UNLIKELY return value; \
Name##vDirect##Ext(GetContextRef().get(), pname, &value); \ Name##vDirect##Ext(GetContextRef().get(), pname, &value); \
@ -384,18 +394,19 @@ AL_API auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \
} \ } \
FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, ALenum pname) noexcept -> R \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, ALenum pname) noexcept -> R \
{ \ { \
R value{}; \ auto value = R{}; \
Name##vDirect##Ext(context, pname, &value); \ Name##vDirect##Ext(context, pname, &value); \
return value; \ return value; \
} }
DECL_GETFUNC(ALboolean, alGetBoolean,) AL_API DECL_GETFUNC(ALboolean, alGetBoolean,)
DECL_GETFUNC(ALdouble, alGetDouble,) AL_API DECL_GETFUNC(ALdouble, alGetDouble,)
DECL_GETFUNC(ALfloat, alGetFloat,) AL_API DECL_GETFUNC(ALfloat, alGetFloat,)
DECL_GETFUNC(ALint, alGetInteger,) AL_API DECL_GETFUNC(ALint, alGetInteger,)
DECL_GETFUNC(ALint64SOFT, alGetInteger64,SOFT) DECL_GETFUNC(ALvoidptr, alGetPointer,EXT)
DECL_GETFUNC(ALvoid*, alGetPointer,SOFT) AL_API DECL_GETFUNC(ALint64SOFT, alGetInteger64,SOFT)
AL_API DECL_GETFUNC(ALvoidptr, alGetPointer,SOFT)
#undef DECL_GETFUNC #undef DECL_GETFUNC
@ -442,6 +453,10 @@ FORCE_ALIGN void AL_APIENTRY alGetInteger64vDirectSOFT(ALCcontext *context, ALen
AL_API DECL_FUNCEXT2(void, alGetPointerv,SOFT, ALenum,pname, ALvoid**,values) AL_API DECL_FUNCEXT2(void, alGetPointerv,SOFT, ALenum,pname, ALvoid**,values)
FORCE_ALIGN void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept FORCE_ALIGN void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept
{ return alGetPointervDirectEXT(context, pname, values); }
FORCE_ALIGN DECL_FUNCEXT2(void, alGetPointerv,EXT, ALenum,pname, ALvoid**,values)
FORCE_ALIGN void AL_APIENTRY alGetPointervDirectEXT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept
{ {
if(!values) UNLIKELY if(!values) UNLIKELY
return context->setError(AL_INVALID_VALUE, "NULL pointer"); return context->setError(AL_INVALID_VALUE, "NULL pointer");
@ -464,7 +479,8 @@ FORCE_ALIGN void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum
*values = context->mDebugParam; *values = context->mDebugParam;
return; return;
} }
context->setError(AL_INVALID_ENUM, "Invalid context pointer property 0x%04x", pname); context->setError(AL_INVALID_ENUM, "Invalid context pointer property {:#04x}",
as_unsigned(pname));
} }
AL_API DECL_FUNC1(const ALchar*, alGetString, ALenum,pname) AL_API DECL_FUNC1(const ALchar*, alGetString, ALenum,pname)
@ -472,9 +488,18 @@ FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALe
{ {
switch(pname) switch(pname)
{ {
case AL_VENDOR: return GetVendorString(); case AL_VENDOR:
case AL_VERSION: return GetVersionString(); if(auto device = context->mALDevice.get(); !device->mVendorOverride.empty())
case AL_RENDERER: return GetRendererString(); return device->mVendorOverride.c_str();
return GetVendorString();
case AL_VERSION:
if(auto device = context->mALDevice.get(); !device->mVersionOverride.empty())
return device->mVersionOverride.c_str();
return GetVersionString();
case AL_RENDERER:
if(auto device = context->mALDevice.get(); !device->mRendererOverride.empty())
return device->mRendererOverride.c_str();
return GetRendererString();
case AL_EXTENSIONS: return context->mExtensionsString.c_str(); case AL_EXTENSIONS: return context->mExtensionsString.c_str();
case AL_NO_ERROR: return GetNoErrorString(); case AL_NO_ERROR: return GetNoErrorString();
case AL_INVALID_NAME: return GetInvalidNameString(); case AL_INVALID_NAME: return GetInvalidNameString();
@ -485,7 +510,7 @@ FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALe
case AL_STACK_OVERFLOW_EXT: return GetStackOverflowString(); case AL_STACK_OVERFLOW_EXT: return GetStackOverflowString();
case AL_STACK_UNDERFLOW_EXT: return GetStackUnderflowString(); case AL_STACK_UNDERFLOW_EXT: return GetStackUnderflowString();
} }
context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname); context->setError(AL_INVALID_VALUE, "Invalid string property {:#04x}", as_unsigned(pname));
return nullptr; return nullptr;
} }
@ -493,7 +518,7 @@ AL_API DECL_FUNC1(void, alDopplerFactor, ALfloat,value)
FORCE_ALIGN void AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) noexcept FORCE_ALIGN void AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) noexcept
{ {
if(!(value >= 0.0f && std::isfinite(value))) if(!(value >= 0.0f && std::isfinite(value)))
context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value); context->setError(AL_INVALID_VALUE, "Doppler factor {:f} out of range", value);
else else
{ {
std::lock_guard<std::mutex> proplock{context->mPropLock}; std::lock_guard<std::mutex> proplock{context->mPropLock};
@ -506,7 +531,7 @@ AL_API DECL_FUNC1(void, alSpeedOfSound, ALfloat,value)
FORCE_ALIGN void AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) noexcept FORCE_ALIGN void AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) noexcept
{ {
if(!(value > 0.0f && std::isfinite(value))) if(!(value > 0.0f && std::isfinite(value)))
context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value); context->setError(AL_INVALID_VALUE, "Speed of sound {:f} out of range", value);
else else
{ {
std::lock_guard<std::mutex> proplock{context->mPropLock}; std::lock_guard<std::mutex> proplock{context->mPropLock};
@ -526,7 +551,8 @@ FORCE_ALIGN void AL_APIENTRY alDistanceModelDirect(ALCcontext *context, ALenum v
UpdateProps(context); UpdateProps(context);
} }
else else
context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value); context->setError(AL_INVALID_VALUE, "Distance model {:#04x} out of range",
as_unsigned(value));
} }
@ -551,12 +577,13 @@ FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringiDirectSOFT(ALCcontext *context
switch(pname) switch(pname)
{ {
case AL_RESAMPLER_NAME_SOFT: case AL_RESAMPLER_NAME_SOFT:
if(index >= 0 && index <= static_cast<ALint>(Resampler::Max)) if(index >= 0 && index <= al::to_underlying(Resampler::Max))
return GetResamplerName(static_cast<Resampler>(index)); return GetResamplerName(static_cast<Resampler>(index));
context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index); context->setError(AL_INVALID_VALUE, "Resampler name index {} out of range", index);
return nullptr; return nullptr;
} }
context->setError(AL_INVALID_VALUE, "Invalid string indexed property"); context->setError(AL_INVALID_VALUE, "Invalid string indexed property {:#04x}",
as_unsigned(pname));
return nullptr; return nullptr;
} }
@ -567,13 +594,13 @@ AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) noexcept
if(!context) UNLIKELY return; if(!context) UNLIKELY return;
if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY
context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 0, context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 1,
DebugSeverity::Medium, DebugSeverity::Medium,
"alDopplerVelocity is deprecated in AL 1.1, use alSpeedOfSound; " "alDopplerVelocity is deprecated in AL 1.1, use alSpeedOfSound; "
"alDopplerVelocity(x) -> alSpeedOfSound(343.3f * x)"); "alDopplerVelocity(x) -> alSpeedOfSound(343.3f * x)");
if(!(value >= 0.0f && std::isfinite(value))) if(!(value >= 0.0f && std::isfinite(value)))
context->setError(AL_INVALID_VALUE, "Doppler velocity %f out of range", value); context->setError(AL_INVALID_VALUE, "Doppler velocity {:f} out of range", value);
else else
{ {
std::lock_guard<std::mutex> proplock{context->mPropLock}; std::lock_guard<std::mutex> proplock{context->mPropLock};
@ -611,6 +638,9 @@ void UpdateContextProps(ALCcontext *context)
props->DopplerFactor = context->mDopplerFactor; props->DopplerFactor = context->mDopplerFactor;
props->DopplerVelocity = context->mDopplerVelocity; props->DopplerVelocity = context->mDopplerVelocity;
props->SpeedOfSound = context->mSpeedOfSound; props->SpeedOfSound = context->mSpeedOfSound;
#if ALSOFT_EAX
props->DistanceFactor = context->eaxGetDistanceFactor();
#endif
props->SourceDistanceModel = context->mSourceDistanceModel; props->SourceDistanceModel = context->mSourceDistanceModel;
props->mDistanceModel = context->mDistanceModel; props->mDistanceModel = context->mDistanceModel;

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,6 @@
#include <array> #include <array>
#include <cctype> #include <cctype>
#include <cstdlib> #include <cstdlib>
#include <filesystem>
#include <fstream> #include <fstream>
#include <istream> #include <istream>
#include <limits> #include <limits>
@ -47,9 +46,10 @@
#include "alstring.h" #include "alstring.h"
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "filesystem.h"
#include "strutils.h" #include "strutils.h"
#if defined(ALSOFT_UWP) #if ALSOFT_UWP
#include <winrt/Windows.Media.Core.h> // !!This is important!! #include <winrt/Windows.Media.Core.h> // !!This is important!!
#include <winrt/Windows.Storage.h> #include <winrt/Windows.Storage.h>
#include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.h>
@ -61,7 +61,7 @@ namespace {
using namespace std::string_view_literals; using namespace std::string_view_literals;
#if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP) #if defined(_WIN32) && !defined(_GAMING_XBOX) && !ALSOFT_UWP
struct CoTaskMemDeleter { struct CoTaskMemDeleter {
void operator()(void *mem) const { CoTaskMemFree(mem); } void operator()(void *mem) const { CoTaskMemFree(mem); }
}; };
@ -153,7 +153,7 @@ void LoadConfigFromFile(std::istream &f)
auto endpos = buffer.find(']', 1); auto endpos = buffer.find(']', 1);
if(endpos == 1 || endpos == std::string::npos) if(endpos == 1 || endpos == std::string::npos)
{ {
ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); ERR(" config parse error: bad line \"{}\"", buffer);
continue; continue;
} }
if(buffer[endpos+1] != '\0') if(buffer[endpos+1] != '\0')
@ -164,7 +164,7 @@ void LoadConfigFromFile(std::istream &f)
if(last < buffer.size() && buffer[last] != '#') if(last < buffer.size() && buffer[last] != '#')
{ {
ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); ERR(" config parse error: bad line \"{}\"", buffer);
continue; continue;
} }
} }
@ -234,7 +234,7 @@ void LoadConfigFromFile(std::istream &f)
auto sep = buffer.find('='); auto sep = buffer.find('=');
if(sep == std::string::npos) if(sep == std::string::npos)
{ {
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); ERR(" config parse error: malformed option line: \"{}\"", buffer);
continue; continue;
} }
auto keypart = std::string_view{buffer}.substr(0, sep++); auto keypart = std::string_view{buffer}.substr(0, sep++);
@ -242,7 +242,7 @@ void LoadConfigFromFile(std::istream &f)
keypart.remove_suffix(1); keypart.remove_suffix(1);
if(keypart.empty()) if(keypart.empty())
{ {
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); ERR(" config parse error: malformed option line: \"{}\"", buffer);
continue; continue;
} }
auto valpart = std::string_view{buffer}.substr(sep); auto valpart = std::string_view{buffer}.substr(sep);
@ -259,7 +259,7 @@ void LoadConfigFromFile(std::istream &f)
if(valpart.size() > size_t{std::numeric_limits<int>::max()}) if(valpart.size() > size_t{std::numeric_limits<int>::max()})
{ {
ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str()); ERR(" config parse error: value too long in line \"{}\"", buffer);
continue; continue;
} }
if(valpart.size() > 1) if(valpart.size() > 1)
@ -272,7 +272,7 @@ void LoadConfigFromFile(std::istream &f)
} }
} }
TRACE(" setting '%s' = '%.*s'\n", fullKey.c_str(), al::sizei(valpart), valpart.data()); TRACE(" setting '{}' = '{}'", fullKey, valpart);
/* Check if we already have this option set */ /* Check if we already have this option set */
auto find_key = [&fullKey](const ConfigEntry &entry) -> bool auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
@ -291,11 +291,12 @@ void LoadConfigFromFile(std::istream &f)
ConfOpts.shrink_to_fit(); ConfOpts.shrink_to_fit();
} }
const char *GetConfigValue(const std::string_view devName, const std::string_view blockName, auto GetConfigValue(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName) const std::string_view keyName) -> const std::string&
{ {
static const auto emptyString = std::string{};
if(keyName.empty()) if(keyName.empty())
return nullptr; return emptyString;
std::string key; std::string key;
if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0) if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0)
@ -314,14 +315,14 @@ const char *GetConfigValue(const std::string_view devName, const std::string_vie
[&key](const ConfigEntry &entry) -> bool { return entry.key == key; }); [&key](const ConfigEntry &entry) -> bool { return entry.key == key; });
if(iter != ConfOpts.cend()) if(iter != ConfOpts.cend())
{ {
TRACE("Found option %s = \"%s\"\n", key.c_str(), iter->value.c_str()); TRACE("Found option {} = \"{}\"", key, iter->value);
if(!iter->value.empty()) if(!iter->value.empty())
return iter->value.c_str(); return iter->value;
return nullptr; return emptyString;
} }
if(devName.empty()) if(devName.empty())
return nullptr; return emptyString;
return GetConfigValue({}, blockName, keyName); return GetConfigValue({}, blockName, keyName);
} }
@ -331,12 +332,11 @@ const char *GetConfigValue(const std::string_view devName, const std::string_vie
#ifdef _WIN32 #ifdef _WIN32
void ReadALConfig() void ReadALConfig()
{ {
namespace fs = std::filesystem;
fs::path path; fs::path path;
#if !defined(_GAMING_XBOX) #if !defined(_GAMING_XBOX)
{ {
#if !defined(ALSOFT_UWP) #if !ALSOFT_UWP
std::unique_ptr<WCHAR,CoTaskMemDeleter> bufstore; std::unique_ptr<WCHAR,CoTaskMemDeleter> bufstore;
const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND,
nullptr, al::out_ptr(bufstore))}; nullptr, al::out_ptr(bufstore))};
@ -352,8 +352,8 @@ void ReadALConfig()
path = fs::path{buffer}; path = fs::path{buffer};
path /= L"alsoft.ini"; path /= L"alsoft.ini";
TRACE("Loading config %s...\n", path.u8string().c_str()); TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open()) if(fs::ifstream f{path}; f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
} }
@ -362,17 +362,17 @@ void ReadALConfig()
path = fs::u8path(GetProcBinary().path); path = fs::u8path(GetProcBinary().path);
if(!path.empty()) if(!path.empty())
{ {
path /= "alsoft.ini"; path /= L"alsoft.ini";
TRACE("Loading config %s...\n", path.u8string().c_str()); TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open()) if(fs::ifstream f{path}; f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
if(auto confpath = al::getenv(L"ALSOFT_CONF")) if(auto confpath = al::getenv(L"ALSOFT_CONF"))
{ {
path = *confpath; path = *confpath;
TRACE("Loading config %s...\n", path.u8string().c_str()); TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open()) if(fs::ifstream f{path}; f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
} }
@ -381,11 +381,10 @@ void ReadALConfig()
void ReadALConfig() void ReadALConfig()
{ {
namespace fs = std::filesystem;
fs::path path{"/etc/openal/alsoft.conf"}; fs::path path{"/etc/openal/alsoft.conf"};
TRACE("Loading config %s...\n", path.u8string().c_str()); TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open()) if(fs::ifstream f{path}; f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")}; std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
@ -409,13 +408,13 @@ void ReadALConfig()
} }
if(!path.is_absolute()) if(!path.is_absolute())
WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str()); WARN("Ignoring XDG config dir: {}", al::u8_as_char(path.u8string()));
else else
{ {
path /= "alsoft.conf"; path /= "alsoft.conf";
TRACE("Loading config %s...\n", path.u8string().c_str()); TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open()) if(fs::ifstream f{path}; f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
} }
@ -441,7 +440,7 @@ void ReadALConfig()
path = *homedir; path = *homedir;
path /= ".alsoftrc"; path /= ".alsoftrc";
TRACE("Loading config %s...\n", path.u8string().c_str()); TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open()) if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
@ -462,7 +461,7 @@ void ReadALConfig()
} }
if(!path.empty()) if(!path.empty())
{ {
TRACE("Loading config %s...\n", path.u8string().c_str()); TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open()) if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
@ -472,66 +471,99 @@ void ReadALConfig()
{ {
path /= "alsoft.conf"; path /= "alsoft.conf";
TRACE("Loading config %s...\n", path.u8string().c_str()); TRACE("Loading config {}...", al::u8_as_char(path.u8string()));
if(std::ifstream f{path}; f.is_open()) if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
if(auto confname = al::getenv("ALSOFT_CONF")) if(auto confname = al::getenv("ALSOFT_CONF"))
{ {
TRACE("Loading config %s...\n", confname->c_str()); TRACE("Loading config {}...", *confname);
if(std::ifstream f{*confname}; f.is_open()) if(std::ifstream f{*confname}; f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
} }
#endif #endif
std::optional<std::string> ConfigValueStr(const std::string_view devName, auto ConfigValueStr(const std::string_view devName, const std::string_view blockName,
const std::string_view blockName, const std::string_view keyName) const std::string_view keyName) -> std::optional<std::string>
{ {
if(const char *val{GetConfigValue(devName, blockName, keyName)}) if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty())
return val; return val;
return std::nullopt; return std::nullopt;
} }
std::optional<int> ConfigValueInt(const std::string_view devName, const std::string_view blockName, auto ConfigValueInt(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName) const std::string_view keyName) -> std::optional<int>
{ {
if(const char *val{GetConfigValue(devName, blockName, keyName)}) if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
return static_cast<int>(std::strtol(val, nullptr, 0)); return static_cast<int>(std::stol(val, nullptr, 0));
}
catch(std::exception&) {
WARN("Option is not an int: {} = {}", keyName, val);
}
return std::nullopt; return std::nullopt;
} }
std::optional<unsigned int> ConfigValueUInt(const std::string_view devName, auto ConfigValueUInt(const std::string_view devName, const std::string_view blockName,
const std::string_view blockName, const std::string_view keyName) const std::string_view keyName) -> std::optional<unsigned int>
{ {
if(const char *val{GetConfigValue(devName, blockName, keyName)}) if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
return static_cast<unsigned int>(std::strtoul(val, nullptr, 0)); return static_cast<unsigned int>(std::stoul(val, nullptr, 0));
}
catch(std::exception&) {
WARN("Option is not an unsigned int: {} = {}", keyName, val);
}
return std::nullopt; return std::nullopt;
} }
std::optional<float> ConfigValueFloat(const std::string_view devName, auto ConfigValueFloat(const std::string_view devName, const std::string_view blockName,
const std::string_view blockName, const std::string_view keyName) const std::string_view keyName) -> std::optional<float>
{ {
if(const char *val{GetConfigValue(devName, blockName, keyName)}) if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
return std::strtof(val, nullptr); return std::stof(val);
}
catch(std::exception&) {
WARN("Option is not a float: {} = {}", keyName, val);
}
return std::nullopt; return std::nullopt;
} }
std::optional<bool> ConfigValueBool(const std::string_view devName, auto ConfigValueBool(const std::string_view devName, const std::string_view blockName,
const std::string_view blockName, const std::string_view keyName) const std::string_view keyName) -> std::optional<bool>
{ {
if(const char *val{GetConfigValue(devName, blockName, keyName)}) if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 return al::case_compare(val, "on"sv) == 0 || al::case_compare(val, "yes"sv) == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0; || al::case_compare(val, "true"sv) == 0 || std::stoll(val) != 0;
}
catch(std::out_of_range&) {
/* If out of range, the value is some non-0 (true) value and it doesn't
* matter that it's too big or small.
*/
return true;
}
catch(std::exception&) {
/* If stoll fails to convert for any other reason, it's some other word
* that's treated as false.
*/
return false;
}
return std::nullopt; return std::nullopt;
} }
bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName, auto GetConfigValueBool(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName, bool def) const std::string_view keyName, bool def) -> bool
{ {
if(const char *val{GetConfigValue(devName, blockName, keyName)}) if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 return al::case_compare(val, "on"sv) == 0 || al::case_compare(val, "yes"sv) == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0; || al::case_compare(val, "true"sv) == 0 || std::stoll(val) != 0;
}
catch(std::out_of_range&) {
return true;
}
catch(std::exception&) {
return false;
}
return def; return def;
} }

View file

@ -19,6 +19,7 @@
*/ */
#include "config.h" #include "config.h"
#include "config_simd.h"
#include "alu.h" #include "alu.h"
@ -26,23 +27,25 @@
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <cassert> #include <cassert>
#include <chrono> #include <cmath>
#include <climits>
#include <cstdarg> #include <cstdarg>
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <functional>
#include <iterator> #include <iterator>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <new>
#include <optional> #include <optional>
#include <string>
#include <string_view>
#include <utility> #include <utility>
#include <variant>
#include "almalloc.h" #include "almalloc.h"
#include "alnumbers.h" #include "alnumbers.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alsem.h"
#include "alspan.h" #include "alspan.h"
#include "alstring.h" #include "alstring.h"
#include "atomic.h" #include "atomic.h"
@ -70,6 +73,7 @@
#include "core/mixer/defs.h" #include "core/mixer/defs.h"
#include "core/mixer/hrtfdefs.h" #include "core/mixer/hrtfdefs.h"
#include "core/resampler_limits.h" #include "core/resampler_limits.h"
#include "core/storage_formats.h"
#include "core/uhjfilter.h" #include "core/uhjfilter.h"
#include "core/voice.h" #include "core/voice.h"
#include "core/voice_change.h" #include "core/voice_change.h"
@ -78,19 +82,18 @@
#include "ringbuffer.h" #include "ringbuffer.h"
#include "strutils.h" #include "strutils.h"
#include "vecmat.h" #include "vecmat.h"
#include "vector.h"
struct CTag; struct CTag;
#ifdef HAVE_SSE #if HAVE_SSE
struct SSETag; struct SSETag;
#endif #endif
#ifdef HAVE_SSE2 #if HAVE_SSE2
struct SSE2Tag; struct SSE2Tag;
#endif #endif
#ifdef HAVE_SSE4_1 #if HAVE_SSE4_1
struct SSE4Tag; struct SSE4Tag;
#endif #endif
#ifdef HAVE_NEON #if HAVE_NEON
struct NEONTag; struct NEONTag;
#endif #endif
struct PointTag; struct PointTag;
@ -143,11 +146,11 @@ HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>};
inline HrtfDirectMixerFunc SelectHrtfMixer() inline HrtfDirectMixerFunc SelectHrtfMixer()
{ {
#ifdef HAVE_NEON #if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON)) if((CPUCapFlags&CPU_CAP_NEON))
return MixDirectHrtf_<NEONTag>; return MixDirectHrtf_<NEONTag>;
#endif #endif
#ifdef HAVE_SSE #if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE)) if((CPUCapFlags&CPU_CAP_SSE))
return MixDirectHrtf_<SSETag>; return MixDirectHrtf_<SSETag>;
#endif #endif
@ -186,60 +189,62 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment)
case Resampler::Point: case Resampler::Point:
return Resample_<PointTag,CTag>; return Resample_<PointTag,CTag>;
case Resampler::Linear: case Resampler::Linear:
#ifdef HAVE_NEON #if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON)) if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<LerpTag,NEONTag>; return Resample_<LerpTag,NEONTag>;
#endif #endif
#ifdef HAVE_SSE4_1 #if HAVE_SSE4_1
if((CPUCapFlags&CPU_CAP_SSE4_1)) if((CPUCapFlags&CPU_CAP_SSE4_1))
return Resample_<LerpTag,SSE4Tag>; return Resample_<LerpTag,SSE4Tag>;
#endif #endif
#ifdef HAVE_SSE2 #if HAVE_SSE2
if((CPUCapFlags&CPU_CAP_SSE2)) if((CPUCapFlags&CPU_CAP_SSE2))
return Resample_<LerpTag,SSE2Tag>; return Resample_<LerpTag,SSE2Tag>;
#endif #endif
return Resample_<LerpTag,CTag>; return Resample_<LerpTag,CTag>;
case Resampler::Spline: case Resampler::Spline:
case Resampler::Gaussian: case Resampler::Gaussian:
#ifdef HAVE_NEON #if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON)) if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<CubicTag,NEONTag>; return Resample_<CubicTag,NEONTag>;
#endif #endif
#ifdef HAVE_SSE4_1 #if HAVE_SSE4_1
if((CPUCapFlags&CPU_CAP_SSE4_1)) if((CPUCapFlags&CPU_CAP_SSE4_1))
return Resample_<CubicTag,SSE4Tag>; return Resample_<CubicTag,SSE4Tag>;
#endif #endif
#ifdef HAVE_SSE2 #if HAVE_SSE2
if((CPUCapFlags&CPU_CAP_SSE2)) if((CPUCapFlags&CPU_CAP_SSE2))
return Resample_<CubicTag,SSE2Tag>; return Resample_<CubicTag,SSE2Tag>;
#endif #endif
#ifdef HAVE_SSE #if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE)) if((CPUCapFlags&CPU_CAP_SSE))
return Resample_<CubicTag,SSETag>; return Resample_<CubicTag,SSETag>;
#endif #endif
return Resample_<CubicTag,CTag>; return Resample_<CubicTag,CTag>;
case Resampler::BSinc12: case Resampler::BSinc12:
case Resampler::BSinc24: case Resampler::BSinc24:
case Resampler::BSinc48:
if(increment > MixerFracOne) if(increment > MixerFracOne)
{ {
#ifdef HAVE_NEON #if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON)) if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<BSincTag,NEONTag>; return Resample_<BSincTag,NEONTag>;
#endif #endif
#ifdef HAVE_SSE #if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE)) if((CPUCapFlags&CPU_CAP_SSE))
return Resample_<BSincTag,SSETag>; return Resample_<BSincTag,SSETag>;
#endif #endif
return Resample_<BSincTag,CTag>; return Resample_<BSincTag,CTag>;
} }
/* fall-through */ [[fallthrough]];
case Resampler::FastBSinc12: case Resampler::FastBSinc12:
case Resampler::FastBSinc24: case Resampler::FastBSinc24:
#ifdef HAVE_NEON case Resampler::FastBSinc48:
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON)) if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<FastBSincTag,NEONTag>; return Resample_<FastBSincTag,NEONTag>;
#endif #endif
#ifdef HAVE_SSE #if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE)) if((CPUCapFlags&CPU_CAP_SSE))
return Resample_<FastBSincTag,SSETag>; return Resample_<FastBSincTag,SSETag>;
#endif #endif
@ -283,6 +288,10 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState
case Resampler::BSinc24: case Resampler::BSinc24:
BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc24); BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc24);
break; break;
case Resampler::FastBSinc48:
case Resampler::BSinc48:
BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc48);
break;
} }
return SelectResampler(resampler, increment); return SelectResampler(resampler, increment);
} }
@ -334,7 +343,8 @@ void DeviceBase::ProcessBs2b(const size_t SamplesToDo)
const size_t ridx{RealOut.ChannelIndex[FrontRight]}; const size_t ridx{RealOut.ChannelIndex[FrontRight]};
/* Now apply the BS2B binaural/crossfeed filter. */ /* Now apply the BS2B binaural/crossfeed filter. */
Bs2b->cross_feed(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), SamplesToDo); Bs2b->cross_feed(al::span{RealOut.Buffer[lidx]}.first(SamplesToDo),
al::span{RealOut.Buffer[ridx]}.first(SamplesToDo));
} }
@ -434,11 +444,19 @@ bool CalcContextParams(ContextBase *ctx)
ctx->mParams.Velocity = rot * vel; ctx->mParams.Velocity = rot * vel;
ctx->mParams.Gain = props->Gain * ctx->mGainBoost; ctx->mParams.Gain = props->Gain * ctx->mGainBoost;
ctx->mParams.MetersPerUnit = props->MetersPerUnit; ctx->mParams.MetersPerUnit = props->MetersPerUnit
#if ALSOFT_EAX
* props->DistanceFactor
#endif
;
ctx->mParams.AirAbsorptionGainHF = props->AirAbsorptionGainHF; ctx->mParams.AirAbsorptionGainHF = props->AirAbsorptionGainHF;
ctx->mParams.DopplerFactor = props->DopplerFactor; ctx->mParams.DopplerFactor = props->DopplerFactor;
ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity; ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity
#if ALSOFT_EAX
/ props->DistanceFactor
#endif
;
ctx->mParams.SourceDistanceModel = props->SourceDistanceModel; ctx->mParams.SourceDistanceModel = props->SourceDistanceModel;
ctx->mParams.mDistanceModel = props->mDistanceModel; ctx->mParams.mDistanceModel = props->mDistanceModel;
@ -462,23 +480,27 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa
slot->Target = props->Target; slot->Target = props->Target;
slot->EffectType = props->Type; slot->EffectType = props->Type;
slot->mEffectProps = props->Props; slot->mEffectProps = props->Props;
slot->RoomRolloff = 0.0f;
slot->DecayTime = 0.0f;
slot->DecayLFRatio = 0.0f;
slot->DecayHFRatio = 0.0f;
slot->DecayHFLimit = false;
slot->AirAbsorptionGainHF = 1.0f;
if(auto *reverbprops = std::get_if<ReverbProps>(&props->Props)) if(auto *reverbprops = std::get_if<ReverbProps>(&props->Props))
{ {
slot->RoomRolloff = reverbprops->RoomRolloffFactor; slot->RoomRolloff = reverbprops->RoomRolloffFactor;
slot->DecayTime = reverbprops->DecayTime;
slot->DecayLFRatio = reverbprops->DecayLFRatio;
slot->DecayHFRatio = reverbprops->DecayHFRatio;
slot->DecayHFLimit = reverbprops->DecayHFLimit;
slot->AirAbsorptionGainHF = reverbprops->AirAbsorptionGainHF; slot->AirAbsorptionGainHF = reverbprops->AirAbsorptionGainHF;
} /* If this effect slot's Auxiliary Send Auto is off, don't apply the
else * automatic send adjustments based on source distance.
{ */
slot->RoomRolloff = 0.0f; if(slot->AuxSendAuto)
slot->DecayTime = 0.0f; {
slot->DecayLFRatio = 0.0f; slot->DecayTime = reverbprops->DecayTime;
slot->DecayHFRatio = 0.0f; slot->DecayLFRatio = reverbprops->DecayLFRatio;
slot->DecayHFLimit = false; slot->DecayHFRatio = reverbprops->DecayHFRatio;
slot->AirAbsorptionGainHF = 1.0f; slot->DecayHFLimit = reverbprops->DecayHFLimit;
}
} }
EffectState *state{props->State.release()}; EffectState *state{props->State.release()};
@ -493,9 +515,9 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa
/* Otherwise, if it would be deleted send it off with a release event. */ /* Otherwise, if it would be deleted send it off with a release event. */
RingBuffer *ring{context->mAsyncEvents.get()}; RingBuffer *ring{context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector(); auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len > 0) LIKELY if(evt_vec[0].len > 0) LIKELY
{ {
auto &evt = InitAsyncEvent<AsyncEffectReleaseEvent>(evt_vec.first.buf); auto &evt = InitAsyncEvent<AsyncEffectReleaseEvent>(evt_vec[0].buf);
evt.mEffectState = oldstate; evt.mEffectState = oldstate;
ring->writeAdvance(1); ring->writeAdvance(1);
} }
@ -686,8 +708,8 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
/* Don't do anything for < 2nd order. */ /* Don't do anything for < 2nd order. */
if(order < 2) return; if(order < 2) return;
auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, static constexpr auto P = [](const int i, const int l, const int a, const int n,
const AmbiRotateMatrix &R) const size_t last_band, const AmbiRotateMatrix &R)
{ {
const float ri1{ R[ 1+2][static_cast<size_t>(i+2_z)]}; const float ri1{ R[ 1+2][static_cast<size_t>(i+2_z)]};
const float rim1{R[-1+2][static_cast<size_t>(i+2_z)]}; const float rim1{R[-1+2][static_cast<size_t>(i+2_z)]};
@ -701,12 +723,12 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
return ri0*R[last_band + static_cast<size_t>(l-1_z+n)][y]; return ri0*R[last_band + static_cast<size_t>(l-1_z+n)][y];
}; };
auto U = [P](const int l, const int m, const int n, const size_t last_band, static constexpr auto U = [](const int l, const int m, const int n, const size_t last_band,
const AmbiRotateMatrix &R) const AmbiRotateMatrix &R)
{ {
return P(0, l, m, n, last_band, R); return P(0, l, m, n, last_band, R);
}; };
auto V = [P](const int l, const int m, const int n, const size_t last_band, static constexpr auto V = [](const int l, const int m, const int n, const size_t last_band,
const AmbiRotateMatrix &R) const AmbiRotateMatrix &R)
{ {
using namespace al::numbers; using namespace al::numbers;
@ -722,7 +744,7 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
const float p1{P(-1, l, -m-1, n, last_band, R)}; const float p1{P(-1, l, -m-1, n, last_band, R)};
return d ? p1*sqrt2_v<float> : (p0 + p1); return d ? p1*sqrt2_v<float> : (p0 + p1);
}; };
auto W = [P](const int l, const int m, const int n, const size_t last_band, static constexpr auto W = [](const int l, const int m, const int n, const size_t last_band,
const AmbiRotateMatrix &R) const AmbiRotateMatrix &R)
{ {
assert(m != 0); assert(m != 0);
@ -836,7 +858,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
}; };
const auto Frequency = static_cast<float>(Device->Frequency); const auto Frequency = static_cast<float>(Device->mSampleRate);
const uint NumSends{Device->NumAuxSends}; const uint NumSends{Device->NumAuxSends};
const size_t num_channels{voice->mChans.size()}; const size_t num_channels{voice->mChans.size()};
@ -1183,8 +1205,6 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
} }
else for(size_t c{0};c < num_channels;c++) else for(size_t c{0};c < num_channels;c++)
{ {
using namespace al::numbers;
/* Skip LFE */ /* Skip LFE */
if(chans[c].channel == LFE) continue; if(chans[c].channel == LFE) continue;
const float pangain{SelectChannelGain(chans[c].channel)}; const float pangain{SelectChannelGain(chans[c].channel)};
@ -1194,7 +1214,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
* the source position, at full spread (pi*2), each channel is * the source position, at full spread (pi*2), each channel is
* left unchanged. * left unchanged.
*/ */
const float a{1.0f - (inv_pi_v<float>/2.0f)*Spread}; const float a{1.0f - (al::numbers::inv_pi_v<float>/2.0f)*Spread};
std::array pos{ std::array pos{
lerpf(chans[c].pos[0], xpos, a), lerpf(chans[c].pos[0], xpos, a),
lerpf(chans[c].pos[1], ypos, a), lerpf(chans[c].pos[1], ypos, a),
@ -1310,57 +1330,51 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
voice->mChans[0].mWetParams[i].Gains.Target); voice->mChans[0].mWetParams[i].Gains.Target);
} }
} }
else else for(size_t c{0};c < num_channels;c++)
{ {
using namespace al::numbers; const auto pangain = SelectChannelGain(chans[c].channel);
for(size_t c{0};c < num_channels;c++) /* Special-case LFE */
if(chans[c].channel == LFE)
{ {
const float pangain{SelectChannelGain(chans[c].channel)}; if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
/* Special-case LFE */
if(chans[c].channel == LFE)
{ {
if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) const auto idx = uint{Device->channelIdxByName(chans[c].channel)};
{ if(idx != InvalidChannelIndex)
const uint idx{Device->channelIdxByName(chans[c].channel)}; voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain;
if(idx != InvalidChannelIndex)
voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base
* pangain;
}
continue;
} }
continue;
}
/* Warp the channel position toward the source position as /* Warp the channel position toward the source position as the
* the spread decreases. With no spread, all channels are * spread decreases. With no spread, all channels are at the
* at the source position, at full spread (pi*2), each * source position, at full spread (pi*2), each channel
* channel position is left unchanged. * position is left unchanged.
*/ */
const float a{1.0f - (inv_pi_v<float>/2.0f)*Spread}; const auto a = 1.0f - (al::numbers::inv_pi_v<float>/2.0f)*Spread;
std::array pos{ auto pos = std::array{
lerpf(chans[c].pos[0], xpos, a), lerpf(chans[c].pos[0], xpos, a),
lerpf(chans[c].pos[1], ypos, a), lerpf(chans[c].pos[1], ypos, a),
lerpf(chans[c].pos[2], zpos, a)}; lerpf(chans[c].pos[2], zpos, a)};
const float len{std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2])}; const auto len = std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2]);
if(len < 1.0f) if(len < 1.0f)
{ {
pos[0] /= len; pos[0] /= len;
pos[1] /= len; pos[1] /= len;
pos[2] /= len; pos[2] /= len;
} }
if(Device->mRenderMode == RenderMode::Pairwise) if(Device->mRenderMode == RenderMode::Pairwise)
pos = ScaleAzimuthFront3(pos); pos = ScaleAzimuthFront3(pos);
const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); const auto coeffs = CalcDirectionCoeffs(pos, 0.0f);
ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain,
voice->mChans[c].mDryParams.Gains.Target); voice->mChans[c].mDryParams.Gains.Target);
for(uint i{0};i < NumSends;i++) for(uint i{0};i < NumSends;i++)
{ {
if(const EffectSlot *Slot{SendSlots[i]}) if(const EffectSlot *Slot{SendSlots[i]})
ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain,
voice->mChans[c].mWetParams[i].Gains.Target); voice->mChans[c].mWetParams[i].Gains.Target);
}
} }
} }
} }
@ -1473,7 +1487,7 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex
/* Calculate the stepping value */ /* Calculate the stepping value */
const auto Pitch = static_cast<float>(voice->mFrequency) / const auto Pitch = static_cast<float>(voice->mFrequency) /
static_cast<float>(Device->Frequency) * props->Pitch; static_cast<float>(Device->mSampleRate) * props->Pitch;
if(Pitch > float{MaxPitch}) if(Pitch > float{MaxPitch})
voice->mStep = MaxPitch<<MixerFracBits; voice->mStep = MaxPitch<<MixerFracBits;
else else
@ -1509,33 +1523,24 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
voice->mDirect.Buffer = Device->Dry.Buffer; voice->mDirect.Buffer = Device->Dry.Buffer;
std::array<EffectSlot*,MaxSendCount> SendSlots{}; std::array<EffectSlot*,MaxSendCount> SendSlots{};
std::array<float,MaxSendCount> RoomRolloff{}; std::array<float,MaxSendCount> RoomRolloff{};
std::bitset<MaxSendCount> UseDryAttnForRoom{0};
for(uint i{0};i < NumSends;i++) for(uint i{0};i < NumSends;i++)
{ {
SendSlots[i] = props->Send[i].Slot; SendSlots[i] = props->Send[i].Slot;
if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None)
{
SendSlots[i] = nullptr; SendSlots[i] = nullptr;
else if(SendSlots[i]->AuxSendAuto) voice->mSend[i].Buffer = {};
}
else
{ {
/* NOTE: Contrary to the EFX docs, the effect's room rolloff factor /* NOTE: Contrary to the EFX docs, the effect's room rolloff factor
* applies to the selected distance model along with the source's * applies to the selected distance model along with the source's
* room rolloff factor, not necessarily the inverse distance model. * room rolloff factor, not necessarily the inverse distance model.
*
* Generic Software also applies these rolloff factors regardless
* of any setting. It doesn't seem to use the effect slot's send
* auto for anything, though as far as I understand, it's supposed
* to control whether the send gets the same gain/gainhf as the
* direct path (excluding the filter).
*/ */
RoomRolloff[i] = props->RoomRolloffFactor + SendSlots[i]->RoomRolloff; RoomRolloff[i] = props->RoomRolloffFactor + SendSlots[i]->RoomRolloff;
}
else
UseDryAttnForRoom.set(i);
if(!SendSlots[i])
voice->mSend[i].Buffer = {};
else
voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer; voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
}
} }
/* Transform source to listener space (convert to head relative) */ /* Transform source to listener space (convert to head relative) */
@ -1670,28 +1675,34 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
std::array<GainTriplet,MaxSendCount> WetGain{}; std::array<GainTriplet,MaxSendCount> WetGain{};
for(uint i{0};i < NumSends;i++) for(uint i{0};i < NumSends;i++)
{ {
WetGainBase[i] = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) * const auto gain = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) *
context->mParams.Gain; context->mParams.Gain;
/* If this effect slot's Auxiliary Send Auto is off, then use the dry
* path distance and cone attenuation, otherwise use the wet (room)
* path distance and cone attenuation. The send filter is used instead
* of the direct filter, regardless.
*/
const bool use_room{!UseDryAttnForRoom.test(i)};
const float gain{use_room ? WetGainBase[i] : DryGainBase};
WetGain[i].Base = std::min(gain * props->Send[i].Gain, GainMixMax); WetGain[i].Base = std::min(gain * props->Send[i].Gain, GainMixMax);
WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF; WetGain[i].HF = WetConeHF * props->Send[i].GainHF;
WetGain[i].LF = props->Send[i].GainLF; WetGain[i].LF = props->Send[i].GainLF;
} }
/* Distance-based air absorption and initial send decay. */ /* Distance-based air absorption and initial send decay. */
if(Distance > props->RefDistance) LIKELY if(Distance > props->RefDistance) LIKELY
{ {
const float distance_base{(Distance-props->RefDistance) * props->RolloffFactor}; /* FIXME: In keeping with EAX, the base air absorption gain should be
const float distance_meters{distance_base * context->mParams.MetersPerUnit}; * taken from the reverb property in the "primary fx slot" when it has
const float dryabsorb{distance_meters * props->AirAbsorptionFactor}; * a reverb effect and the environment flag set, and be applied to the
if(dryabsorb > std::numeric_limits<float>::epsilon()) * direct path and all environment sends, rather than each path using
DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, dryabsorb); * the air absorption gain associated with the given slot's effect. At
* this point in the mixer, and even in EFX itself, there's no concept
* of a "primary fx slot" so it's unclear which effect slot should be
* checked.
*
* The HF reference is also intended to be handled the same way, but
* again, there's no concept of a "primary fx slot" here and no way to
* know which effect slot to look at for the reference frequency.
*/
const auto distance_units = float{(Distance-props->RefDistance) * props->RolloffFactor};
const auto distance_meters = float{distance_units * context->mParams.MetersPerUnit};
const auto absorb = float{distance_meters * props->AirAbsorptionFactor};
if(absorb > std::numeric_limits<float>::epsilon())
DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, absorb);
/* If the source's Auxiliary Send Filter Gain Auto is off, no extra /* If the source's Auxiliary Send Filter Gain Auto is off, no extra
* adjustment is applied to the send gains. * adjustment is applied to the send gains.
@ -1701,18 +1712,9 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f)) if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f))
continue; continue;
if(distance_meters > std::numeric_limits<float>::epsilon()) if(SendSlots[i]->AirAbsorptionGainHF < 1.0f
WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, distance_meters); && absorb > std::numeric_limits<float>::epsilon())
WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, absorb);
/* If this effect slot's Auxiliary Send Auto is off, don't apply
* the automatic initial reverb decay.
*
* NOTE: Generic Software applies the initial decay regardless of
* this setting. It doesn't seem to use it for anything, only the
* source's send filter gain auto flag affects this.
*/
if(!SendSlots[i]->AuxSendAuto)
continue;
const float DecayDistance{SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec}; const float DecayDistance{SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec};
@ -1726,7 +1728,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
* with the reverb and source rolloff parameters. * with the reverb and source rolloff parameters.
*/ */
const float baseAttn{DryAttnBase}; const float baseAttn{DryAttnBase};
const float fact{distance_base / DecayDistance}; const float fact{distance_meters / DecayDistance};
const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn}; const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn};
WetGain[i].Base *= gain; WetGain[i].Base *= gain;
} }
@ -1771,7 +1773,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
/* Adjust pitch based on the buffer and output frequencies, and calculate /* Adjust pitch based on the buffer and output frequencies, and calculate
* fixed-point stepping value. * fixed-point stepping value.
*/ */
Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->Frequency); Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->mSampleRate);
if(Pitch > float{MaxPitch}) if(Pitch > float{MaxPitch})
voice->mStep = MaxPitch<<MixerFracBits; voice->mStep = MaxPitch<<MixerFracBits;
else else
@ -1814,9 +1816,9 @@ void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state)
{ {
RingBuffer *ring{context->mAsyncEvents.get()}; RingBuffer *ring{context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector(); auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len < 1) return; if(evt_vec[0].len < 1) return;
auto &evt = InitAsyncEvent<AsyncSourceStateEvent>(evt_vec.first.buf); auto &evt = InitAsyncEvent<AsyncSourceStateEvent>(evt_vec[0].buf);
evt.mId = id; evt.mId = id;
switch(state) switch(state)
{ {
@ -1962,34 +1964,35 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo)
{ {
ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo > 0);
const nanoseconds curtime{device->mClockBase.load(std::memory_order_relaxed) + const auto curtime = device->getClockTime();
nanoseconds{seconds{device->mSamplesDone.load(std::memory_order_relaxed)}}/
device->Frequency};
for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire)) auto proc_context = [SamplesToDo,curtime](ContextBase *ctx)
{ {
const auto auxslotspan = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)}; const auto auxslotspan = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)};
const auto auxslots = auxslotspan.first(auxslotspan.size()>>1); const auto auxslots = auxslotspan.first(auxslotspan.size()>>1);
const auto sorted_slots = auxslotspan.last(auxslotspan.size()>>1); const auto sorted_slots = auxslotspan.last(auxslotspan.size()>>1);
const al::span<Voice*> voices{ctx->getVoicesSpanAcquired()}; const auto voices = ctx->getVoicesSpanAcquired();
/* Process pending property updates for objects on the context. */ /* Process pending property updates for objects on the context. */
ProcessParamUpdates(ctx, auxslots, sorted_slots, voices); ProcessParamUpdates(ctx, auxslots, sorted_slots, voices);
/* Clear auxiliary effect slot mixing buffers. */ /* Clear auxiliary effect slot mixing buffers. */
for(EffectSlot *slot : auxslots) auto clear_wetbuffers = [](EffectSlot *slot)
{ {
for(auto &buffer : slot->Wet.Buffer) auto clear_buffer = [](const FloatBufferSpan buffer)
buffer.fill(0.0f); { std::fill(buffer.begin(), buffer.end(), 0.0f); };
} std::for_each(slot->Wet.Buffer.begin(), slot->Wet.Buffer.end(), clear_buffer);
};
std::for_each(auxslots.begin(), auxslots.end(), clear_wetbuffers);
/* Process voices that have a playing source. */ /* Process voices that have a playing source. */
for(Voice *voice : voices) auto proc_voice = [ctx,curtime,SamplesToDo](Voice *voice)
{ {
const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)}; const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)};
if(vstate != Voice::Stopped && vstate != Voice::Pending) if(vstate != Voice::Stopped && vstate != Voice::Pending)
voice->mix(vstate, ctx, curtime, SamplesToDo); voice->mix(vstate, ctx, curtime, SamplesToDo);
} };
std::for_each(voices.begin(), voices.end(), proc_voice);
/* Process effects. */ /* Process effects. */
if(!auxslots.empty()) if(!auxslots.empty())
@ -2000,60 +2003,54 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo)
*/ */
if(!sorted_slots[0]) if(!sorted_slots[0])
{ {
/* First, copy the slots to the sorted list, then partition the /* First, copy the slots to the sorted list and partition them,
* sorted list so that all slots without a target slot go to * so that all slots without a target slot go to the end.
* the end.
*/ */
std::copy(auxslots.begin(), auxslots.end(), sorted_slots.begin()); auto has_target = [](const EffectSlot *slot) noexcept -> bool
auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(), { return slot->Target != nullptr; };
[](const EffectSlot *slot) noexcept -> bool auto split_point = std::partition_copy(auxslots.rbegin(), auxslots.rend(),
{ return slot->Target != nullptr; }); sorted_slots.begin(), sorted_slots.rbegin(), has_target).first;
/* There must be at least one slot without a slot target. */ /* There must be at least one slot without a slot target. */
assert(split_point != sorted_slots.end()); assert(split_point != sorted_slots.end());
/* Simple case: no more than 1 slot has a target slot. Either /* Starting from the back of the sorted list, continue
* all slots go right to the output, or the remaining one must * partitioning the front of the list given each target until
* target an already-partitioned slot. * all targets are accounted for. This ensures all slots
* without a target go last, all slots directly targeting those
* last slots go second-to-last, all slots directly targeting
* those second-last slots go third-to-last, etc.
*/ */
if(split_point - sorted_slots.begin() > 1) auto next_target = sorted_slots.end();
while(std::distance(sorted_slots.begin(), split_point) > 1)
{ {
/* At least two slots target other slots. Starting from the /* This shouldn't happen, but if there's unsorted slots
* back of the sorted list, continue partitioning the front * left that don't target any sorted slots, they can't
* of the list given each target until all targets are * contribute to the output, so leave them.
* accounted for. This ensures all slots without a target
* go last, all slots directly targeting those last slots
* go second-to-last, all slots directly targeting those
* second-last slots go third-to-last, etc.
*/ */
auto next_target = sorted_slots.end(); if(next_target == split_point) UNLIKELY
do { break;
/* This shouldn't happen, but if there's unsorted slots
* left that don't target any sorted slots, they can't
* contribute to the output, so leave them.
*/
if(next_target == split_point) UNLIKELY
break;
--next_target; --next_target;
split_point = std::partition(sorted_slots.begin(), split_point, auto not_next = [next_target](const EffectSlot *slot) noexcept -> bool
[next_target](const EffectSlot *slot) noexcept -> bool { return slot->Target != *next_target; };
{ return slot->Target != *next_target; }); split_point = std::partition(sorted_slots.begin(), split_point, not_next);
} while(split_point - sorted_slots.begin() > 1);
} }
} }
for(const EffectSlot *slot : sorted_slots) auto proc_slot = [SamplesToDo](const EffectSlot *slot)
{ {
EffectState *state{slot->mEffectState.get()}; EffectState *state{slot->mEffectState.get()};
state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget); state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
} };
std::for_each(sorted_slots.begin(), sorted_slots.end(), proc_slot);
} }
/* Signal the event handler if there are any events to read. */ /* Signal the event handler if there are any events to read. */
RingBuffer *ring{ctx->mAsyncEvents.get()}; if(RingBuffer *ring{ctx->mAsyncEvents.get()}; ring->readSpace() > 0)
if(ring->readSpace() > 0)
ctx->mEventSem.post(); ctx->mEventSem.post();
} };
const auto contexts = al::span{*device->mContexts.load(std::memory_order_acquire)};
std::for_each(contexts.begin(), contexts.end(), proc_context);
} }
@ -2152,25 +2149,47 @@ void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, cons
ASSUME(FrameStep > 0); ASSUME(FrameStep > 0);
ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo > 0);
const auto output = al::span{static_cast<T*>(OutBuffer), (Offset+SamplesToDo)*FrameStep} /* Some Clang versions don't like calling subspan on an rvalue here. */
.subspan(Offset*FrameStep); const auto output_ = al::span{static_cast<T*>(OutBuffer), (Offset+SamplesToDo)*FrameStep};
size_t c{0}; const auto output = output_.subspan(Offset*FrameStep);
for(const FloatBufferLine &inbuf : InBuffer)
/* If there's extra channels in the interleaved output buffer to skip,
* clear the whole output buffer. This is simpler to ensure the extra
* channels are silent than trying to clear just the extra channels.
*/
if(FrameStep > InBuffer.size())
std::fill(output.begin(), output.end(), SampleConv<T>(0.0f));
auto outbase = output.begin();
for(const auto &srcbuf : InBuffer)
{ {
auto out = output.begin(); const auto src = al::span{srcbuf}.first(SamplesToDo);
auto conv_sample = [FrameStep,c,&out](const float s) noexcept auto out = outbase++;
*out = SampleConv<T>(src.front());
std::for_each(src.begin()+1, src.end(), [FrameStep,&out](const float s) noexcept
{ {
out[c] = SampleConv<T>(s);
out += ptrdiff_t(FrameStep); out += ptrdiff_t(FrameStep);
}; *out = SampleConv<T>(s);
std::for_each_n(inbuf.cbegin(), SamplesToDo, conv_sample); });
++c;
} }
if(const size_t extra{FrameStep - c}) }
template<typename T>
void Write(const al::span<const FloatBufferLine> InBuffer, al::span<void*> OutBuffers,
const size_t Offset, const size_t SamplesToDo)
{
ASSUME(SamplesToDo > 0);
auto srcbuf = InBuffer.cbegin();
for(auto *dstbuf : OutBuffers)
{ {
const auto silence = SampleConv<T>(0.0f); const auto src = al::span{*srcbuf}.first(SamplesToDo);
for(size_t i{0};i < SamplesToDo;++i) /* Some Clang versions don't like calling subspan on an rvalue here. */
std::fill_n(&output[i*FrameStep + c], extra, silence); const auto dst_ = al::span{static_cast<T*>(dstbuf), Offset+SamplesToDo};
const auto dst = dst_.subspan(Offset);
std::transform(src.begin(), src.end(), dst.begin(), SampleConv<T>);
++srcbuf;
} }
} }
@ -2195,10 +2214,10 @@ uint DeviceBase::renderSamples(const uint numSamples)
* also guarantees a stable conversion. * also guarantees a stable conversion.
*/ */
auto samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo; auto samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo;
auto clockBase = mClockBase.load(std::memory_order_relaxed) + auto clockBaseSec = mClockBaseSec.load(std::memory_order_relaxed) +
std::chrono::seconds{samplesDone/Frequency}; seconds32{samplesDone/mSampleRate};
mSamplesDone.store(samplesDone%Frequency, std::memory_order_relaxed); mSamplesDone.store(samplesDone%mSampleRate, std::memory_order_relaxed);
mClockBase.store(clockBase, std::memory_order_relaxed); mClockBaseSec.store(clockBaseSec, std::memory_order_relaxed);
} }
/* Apply any needed post-process for finalizing the Dry mix to the RealOut /* Apply any needed post-process for finalizing the Dry mix to the RealOut
@ -2207,7 +2226,7 @@ uint DeviceBase::renderSamples(const uint numSamples)
postProcess(samplesToDo); postProcess(samplesToDo);
/* Apply compression, limiting sample amplitude if needed or desired. */ /* Apply compression, limiting sample amplitude if needed or desired. */
if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data()); if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer);
/* Apply delays and attenuation for mismatched speaker distances. */ /* Apply delays and attenuation for mismatched speaker distances. */
if(ChannelDelays) if(ChannelDelays)
@ -2222,7 +2241,7 @@ uint DeviceBase::renderSamples(const uint numSamples)
return samplesToDo; return samplesToDo;
} }
void DeviceBase::renderSamples(const al::span<float*> outBuffers, const uint numSamples) void DeviceBase::renderSamples(const al::span<void*> outBuffers, const uint numSamples)
{ {
FPUCtl mixer_mode{}; FPUCtl mixer_mode{};
uint total{0}; uint total{0};
@ -2230,13 +2249,19 @@ void DeviceBase::renderSamples(const al::span<float*> outBuffers, const uint num
{ {
const uint samplesToDo{renderSamples(todo)}; const uint samplesToDo{renderSamples(todo)};
auto srcbuf = RealOut.Buffer.cbegin(); switch(FmtType)
for(auto *dstbuf : outBuffers)
{ {
const auto dst = al::span{dstbuf, numSamples}.subspan(total); #define HANDLE_WRITE(T) case T: \
std::copy_n(srcbuf->cbegin(), samplesToDo, dst.begin()); Write<DevFmtType_t<T>>(RealOut.Buffer, outBuffers, total, samplesToDo); break;
++srcbuf; HANDLE_WRITE(DevFmtByte)
HANDLE_WRITE(DevFmtUByte)
HANDLE_WRITE(DevFmtShort)
HANDLE_WRITE(DevFmtUShort)
HANDLE_WRITE(DevFmtInt)
HANDLE_WRITE(DevFmtUInt)
HANDLE_WRITE(DevFmtFloat)
} }
#undef HANDLE_WRITE
total += samplesToDo; total += samplesToDo;
} }
@ -2274,7 +2299,7 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz
} }
} }
void DeviceBase::handleDisconnect(const char *msg, ...) void DeviceBase::doDisconnect(std::string msg)
{ {
const auto mixLock = getWriteMixLock(); const auto mixLock = getWriteMixLock();
@ -2282,29 +2307,12 @@ void DeviceBase::handleDisconnect(const char *msg, ...)
{ {
AsyncEvent evt{std::in_place_type<AsyncDisconnectEvent>}; AsyncEvent evt{std::in_place_type<AsyncDisconnectEvent>};
auto &disconnect = std::get<AsyncDisconnectEvent>(evt); auto &disconnect = std::get<AsyncDisconnectEvent>(evt);
disconnect.msg = std::move(msg);
/* NOLINTBEGIN(*-array-to-pointer-decay) */
va_list args, args2;
va_start(args, msg);
va_copy(args2, args);
if(int msglen{vsnprintf(nullptr, 0, msg, args)}; msglen > 0)
{
disconnect.msg.resize(static_cast<uint>(msglen)+1_uz);
vsnprintf(disconnect.msg.data(), disconnect.msg.size(), msg, args2);
}
else
disconnect.msg = "<failed constructing message>";
va_end(args2);
va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
while(!disconnect.msg.empty() && disconnect.msg.back() == '\0')
disconnect.msg.pop_back();
for(ContextBase *ctx : *mContexts.load()) for(ContextBase *ctx : *mContexts.load())
{ {
RingBuffer *ring{ctx->mAsyncEvents.get()}; RingBuffer *ring{ctx->mAsyncEvents.get()};
auto evt_data = ring->getWriteVector().first; auto evt_data = ring->getWriteVector()[0];
if(evt_data.len > 0) if(evt_data.len > 0)
{ {
al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), evt); al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), evt);

View file

@ -11,6 +11,9 @@ struct EffectSlot;
enum class StereoEncoding : std::uint8_t; enum class StereoEncoding : std::uint8_t;
namespace al {
struct Device;
} // namespace al
constexpr float GainMixMax{1000.0f}; /* +60dB */ constexpr float GainMixMax{1000.0f}; /* +60dB */
@ -31,7 +34,7 @@ void aluInit(CompatFlagBitset flags, const float nfcscale);
* Set up the appropriate panning method and mixing method given the device * Set up the appropriate panning method and mixing method given the device
* properties. * properties.
*/ */
void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncoding> stereomode); void aluInitRenderer(al::Device *device, int hrtf_id, std::optional<StereoEncoding> stereomode);
void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context);

View file

@ -29,7 +29,6 @@
#include <chrono> #include <chrono>
#include <cstring> #include <cstring>
#include <exception> #include <exception>
#include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
@ -38,16 +37,15 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "albit.h"
#include "alc/alconfig.h" #include "alc/alconfig.h"
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alstring.h"
#include "althrd_setname.h" #include "althrd_setname.h"
#include "core/device.h" #include "core/device.h"
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "fmt/core.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
@ -60,7 +58,7 @@ using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; } [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; }
#ifdef HAVE_DYNLOAD #if HAVE_DYNLOAD
#define ALSA_FUNCS(MAGIC) \ #define ALSA_FUNCS(MAGIC) \
MAGIC(snd_strerror); \ MAGIC(snd_strerror); \
MAGIC(snd_pcm_open); \ MAGIC(snd_pcm_open); \
@ -272,7 +270,7 @@ struct SndCtlCardInfo {
SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete; SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete;
[[nodiscard]] [[nodiscard]]
operator snd_ctl_card_info_t*() const noexcept { return mInfo; } operator snd_ctl_card_info_t*() const noexcept { return mInfo; } /* NOLINT(google-explicit-constructor) */
}; };
struct SndPcmInfo { struct SndPcmInfo {
@ -284,7 +282,7 @@ struct SndPcmInfo {
SndPcmInfo& operator=(const SndPcmInfo&) = delete; SndPcmInfo& operator=(const SndPcmInfo&) = delete;
[[nodiscard]] [[nodiscard]]
operator snd_pcm_info_t*() const noexcept { return mInfo; } operator snd_pcm_info_t*() const noexcept { return mInfo; } /* NOLINT(google-explicit-constructor) */
}; };
struct SndCtl { struct SndCtl {
@ -299,7 +297,7 @@ struct SndCtl {
auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); } auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); }
[[nodiscard]] [[nodiscard]]
operator snd_ctl_t*() const noexcept { return mHandle; } operator snd_ctl_t*() const noexcept { return mHandle; } /* NOLINT(google-explicit-constructor) */
}; };
@ -324,15 +322,15 @@ std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
const size_t seppos{customdevs->find('=', curpos)}; const size_t seppos{customdevs->find('=', curpos)};
if(seppos == curpos || seppos >= nextpos) if(seppos == curpos || seppos >= nextpos)
{ {
const std::string spec{customdevs->substr(curpos, nextpos-curpos)}; const auto spec = std::string_view{*customdevs}.substr(curpos, nextpos-curpos);
ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); ERR("Invalid ALSA device specification \"{}\"", spec);
} }
else else
{ {
const std::string_view strview{*customdevs}; const std::string_view strview{*customdevs};
const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos), const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos),
strview.substr(seppos+1, nextpos-seppos-1)); strview.substr(seppos+1, nextpos-seppos-1));
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name);
} }
if(nextpos < customdevs->length()) if(nextpos < customdevs->length())
@ -354,13 +352,13 @@ std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
err = handle.open(name.c_str(), 0); err = handle.open(name.c_str(), 0);
if(err < 0) if(err < 0)
{ {
ERR("control open (hw:%d): %s\n", card, snd_strerror(err)); ERR("control open (hw:{}): {}", card, snd_strerror(err));
continue; continue;
} }
err = snd_ctl_card_info(handle, info); err = snd_ctl_card_info(handle, info);
if(err < 0) if(err < 0)
{ {
ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err)); ERR("control hardware info (hw:{}): {}", card, snd_strerror(err));
continue; continue;
} }
@ -375,7 +373,7 @@ std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
while(true) while(true)
{ {
if(snd_ctl_pcm_next_device(handle, &dev) < 0) if(snd_ctl_pcm_next_device(handle, &dev) < 0)
ERR("snd_ctl_pcm_next_device failed\n"); ERR("snd_ctl_pcm_next_device failed");
if(dev < 0) break; if(dev < 0) break;
snd_pcm_info_set_device(pcminfo, static_cast<uint>(dev)); snd_pcm_info_set_device(pcminfo, static_cast<uint>(dev));
@ -385,42 +383,28 @@ std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
if(err < 0) if(err < 0)
{ {
if(err != -ENOENT) if(err != -ENOENT)
ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err)); ERR("control digital audio info (hw:{}): {}", card, snd_strerror(err));
continue; continue;
} }
/* "prefix-cardid-dev" */ /* "prefix-cardid-dev" */
name = prefix_name(stream); name = fmt::format("{}-{}-{}", prefix_name(stream), cardid, dev);
name += '-'; const auto device_prefix = std::string{ConfigValueStr({}, "alsa"sv, name)
name += cardid;
name += '-';
name += std::to_string(dev);
const std::string device_prefix{ConfigValueStr({}, "alsa"sv, name)
.value_or(card_prefix)}; .value_or(card_prefix)};
/* "CardName, PcmName (CARD=cardid,DEV=dev)" */ /* "CardName, PcmName (CARD=cardid,DEV=dev)" */
name = cardname; name = fmt::format("{}, {} (CARD={},DEV={})", cardname, snd_pcm_info_get_name(pcminfo),
name += ", "; cardid, dev);
name += snd_pcm_info_get_name(pcminfo);
name += " (CARD=";
name += cardid;
name += ",DEV=";
name += std::to_string(dev);
name += ')';
/* "devprefixCARD=cardid,DEV=dev" */ /* "devprefixCARD=cardid,DEV=dev" */
std::string device{device_prefix}; auto device = fmt::format("{}CARD={},DEV={}", device_prefix, cardid, dev);
device += "CARD=";
device += cardid;
device += ",DEV=";
device += std::to_string(dev);
const auto &entry = devlist.emplace_back(std::move(name), std::move(device)); const auto &entry = devlist.emplace_back(std::move(name), std::move(device));
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name);
} }
} }
if(err < 0) if(err < 0)
ERR("snd_card_next failed: %s\n", snd_strerror(err)); ERR("snd_card_next failed: {}", snd_strerror(err));
return devlist; return devlist;
} }
@ -470,7 +454,7 @@ int verify_state(snd_pcm_t *handle)
struct AlsaPlayback final : public BackendBase { struct AlsaPlayback final : public BackendBase {
AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaPlayback() override; ~AlsaPlayback() override;
int mixerProc(); int mixerProc();
@ -507,29 +491,29 @@ int AlsaPlayback::mixerProc()
SetRTPriority(); SetRTPriority();
althrd_setname(GetMixerThreadName()); althrd_setname(GetMixerThreadName());
const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t update_size{mDevice->mUpdateSize};
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; const snd_pcm_uframes_t buffer_size{mDevice->mBufferSize};
while(!mKillNow.load(std::memory_order_acquire)) while(!mKillNow.load(std::memory_order_acquire))
{ {
int state{verify_state(mPcmHandle)}; int state{verify_state(mPcmHandle)};
if(state < 0) if(state < 0)
{ {
ERR("Invalid state detected: %s\n", snd_strerror(state)); ERR("Invalid state detected: {}", snd_strerror(state));
mDevice->handleDisconnect("Bad state: %s", snd_strerror(state)); mDevice->handleDisconnect("Bad state: {}", snd_strerror(state));
break; break;
} }
snd_pcm_sframes_t avails{snd_pcm_avail_update(mPcmHandle)}; snd_pcm_sframes_t avails{snd_pcm_avail_update(mPcmHandle)};
if(avails < 0) if(avails < 0)
{ {
ERR("available update failed: %s\n", snd_strerror(static_cast<int>(avails))); ERR("available update failed: {}", snd_strerror(static_cast<int>(avails)));
continue; continue;
} }
snd_pcm_uframes_t avail{static_cast<snd_pcm_uframes_t>(avails)}; snd_pcm_uframes_t avail{static_cast<snd_pcm_uframes_t>(avails)};
if(avail > buffer_size) if(avail > buffer_size)
{ {
WARN("available samples exceeds the buffer size\n"); WARN("available samples exceeds the buffer size");
snd_pcm_reset(mPcmHandle); snd_pcm_reset(mPcmHandle);
continue; continue;
} }
@ -542,12 +526,12 @@ int AlsaPlayback::mixerProc()
int err{snd_pcm_start(mPcmHandle)}; int err{snd_pcm_start(mPcmHandle)};
if(err < 0) if(err < 0)
{ {
ERR("start failed: %s\n", snd_strerror(err)); ERR("start failed: {}", snd_strerror(err));
continue; continue;
} }
} }
if(snd_pcm_wait(mPcmHandle, 1000) == 0) if(snd_pcm_wait(mPcmHandle, 1000) == 0)
ERR("Wait timeout... buffer size too low?\n"); ERR("Wait timeout... buffer size too low?");
continue; continue;
} }
avail -= avail%update_size; avail -= avail%update_size;
@ -563,7 +547,7 @@ int AlsaPlayback::mixerProc()
int err{snd_pcm_mmap_begin(mPcmHandle, &areas, &offset, &frames)}; int err{snd_pcm_mmap_begin(mPcmHandle, &areas, &offset, &frames)};
if(err < 0) if(err < 0)
{ {
ERR("mmap begin error: %s\n", snd_strerror(err)); ERR("mmap begin error: {}", snd_strerror(err));
break; break;
} }
@ -574,7 +558,7 @@ int AlsaPlayback::mixerProc()
snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)}; snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)};
if(commitres < 0 || static_cast<snd_pcm_uframes_t>(commitres) != frames) if(commitres < 0 || static_cast<snd_pcm_uframes_t>(commitres) != frames)
{ {
ERR("mmap commit error: %s\n", ERR("mmap commit error: {}",
snd_strerror(commitres >= 0 ? -EPIPE : static_cast<int>(commitres))); snd_strerror(commitres >= 0 ? -EPIPE : static_cast<int>(commitres)));
break; break;
} }
@ -591,28 +575,28 @@ int AlsaPlayback::mixerNoMMapProc()
SetRTPriority(); SetRTPriority();
althrd_setname(GetMixerThreadName()); althrd_setname(GetMixerThreadName());
const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t update_size{mDevice->mUpdateSize};
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; const snd_pcm_uframes_t buffer_size{mDevice->mBufferSize};
while(!mKillNow.load(std::memory_order_acquire)) while(!mKillNow.load(std::memory_order_acquire))
{ {
int state{verify_state(mPcmHandle)}; int state{verify_state(mPcmHandle)};
if(state < 0) if(state < 0)
{ {
ERR("Invalid state detected: %s\n", snd_strerror(state)); ERR("Invalid state detected: {}", snd_strerror(state));
mDevice->handleDisconnect("Bad state: %s", snd_strerror(state)); mDevice->handleDisconnect("Bad state: {}", snd_strerror(state));
break; break;
} }
snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)}; snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)};
if(avail < 0) if(avail < 0)
{ {
ERR("available update failed: %s\n", snd_strerror(static_cast<int>(avail))); ERR("available update failed: {}", snd_strerror(static_cast<int>(avail)));
continue; continue;
} }
if(static_cast<snd_pcm_uframes_t>(avail) > buffer_size) if(static_cast<snd_pcm_uframes_t>(avail) > buffer_size)
{ {
WARN("available samples exceeds the buffer size\n"); WARN("available samples exceeds the buffer size");
snd_pcm_reset(mPcmHandle); snd_pcm_reset(mPcmHandle);
continue; continue;
} }
@ -624,12 +608,12 @@ int AlsaPlayback::mixerNoMMapProc()
int err{snd_pcm_start(mPcmHandle)}; int err{snd_pcm_start(mPcmHandle)};
if(err < 0) if(err < 0)
{ {
ERR("start failed: %s\n", snd_strerror(err)); ERR("start failed: {}", snd_strerror(err));
continue; continue;
} }
} }
if(snd_pcm_wait(mPcmHandle, 1000) == 0) if(snd_pcm_wait(mPcmHandle, 1000) == 0)
ERR("Wait timeout... buffer size too low?\n"); ERR("Wait timeout... buffer size too low?");
continue; continue;
} }
@ -686,7 +670,7 @@ void AlsaPlayback::open(std::string_view name)
[name](const DevMap &entry) -> bool { return entry.name == name; }); [name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == PlaybackDevices.cend()) if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
driver = iter->device_name; driver = iter->device_name;
} }
else else
@ -695,13 +679,13 @@ void AlsaPlayback::open(std::string_view name)
if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv)) if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv))
driver = std::move(driveropt).value(); driver = std::move(driveropt).value();
} }
TRACE("Opening device \"%s\"\n", driver.c_str()); TRACE("Opening device \"{}\"", driver);
snd_pcm_t *pcmHandle{}; snd_pcm_t *pcmHandle{};
int err{snd_pcm_open(&pcmHandle, driver.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; int err{snd_pcm_open(&pcmHandle, driver.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)};
if(err < 0) if(err < 0)
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Could not open ALSA device \"%s\"", driver.c_str()}; "Could not open ALSA device \"{}\"", driver};
if(mPcmHandle) if(mPcmHandle)
snd_pcm_close(mPcmHandle); snd_pcm_close(mPcmHandle);
mPcmHandle = pcmHandle; mPcmHandle = pcmHandle;
@ -709,7 +693,7 @@ void AlsaPlayback::open(std::string_view name)
/* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
snd_config_update_free_global(); snd_config_update_free_global();
mDevice->DeviceName = name; mDeviceName = name;
} }
bool AlsaPlayback::reset() bool AlsaPlayback::reset()
@ -740,15 +724,15 @@ bool AlsaPlayback::reset()
break; break;
} }
bool allowmmap{GetConfigValueBool(mDevice->DeviceName, "alsa"sv, "mmap"sv, true)}; bool allowmmap{GetConfigValueBool(mDevice->mDeviceName, "alsa"sv, "mmap"sv, true)};
uint periodLen{static_cast<uint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)}; uint periodLen{static_cast<uint>(mDevice->mUpdateSize * 1000000_u64 / mDevice->mSampleRate)};
uint bufferLen{static_cast<uint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)}; uint bufferLen{static_cast<uint>(mDevice->mBufferSize * 1000000_u64 / mDevice->mSampleRate)};
uint rate{mDevice->Frequency}; uint rate{mDevice->mSampleRate};
HwParamsPtr hp{CreateHwParams()}; HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \ #define CHECK(x) do { \
if(int err{x}; err < 0) \ if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \
snd_strerror(err)}; \ snd_strerror(err)}; \
} while(0) } while(0)
CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get()));
@ -798,21 +782,21 @@ bool AlsaPlayback::reset()
else mDevice->FmtChans = DevFmtStereo; else mDevice->FmtChans = DevFmtStereo;
} }
/* set rate (implicitly constrains period/buffer parameters) */ /* set rate (implicitly constrains period/buffer parameters) */
if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false) if(!GetConfigValueBool(mDevice->mDeviceName, "alsa", "allow-resampler", false)
|| !mDevice->Flags.test(FrequencyRequest)) || !mDevice->Flags.test(FrequencyRequest))
{ {
if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
WARN("Failed to disable ALSA resampler\n"); WARN("Failed to disable ALSA resampler");
} }
else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0) else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0)
WARN("Failed to enable ALSA resampler\n"); WARN("Failed to enable ALSA resampler");
CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr)); CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr));
/* set period time (implicitly constrains period/buffer parameters) */ /* set period time (implicitly constrains period/buffer parameters) */
if(int err{snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)}; err < 0) if(int err{snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)}; err < 0)
ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err)); ERR("snd_pcm_hw_params_set_period_time_near failed: {}", snd_strerror(err));
/* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */ /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */
if(int err{snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)}; err < 0) if(int err{snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)}; err < 0)
ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err)); ERR("snd_pcm_hw_params_set_buffer_time_near failed: {}", snd_strerror(err));
/* install and prepare hardware configuration */ /* install and prepare hardware configuration */
CHECK(snd_pcm_hw_params(mPcmHandle, hp.get())); CHECK(snd_pcm_hw_params(mPcmHandle, hp.get()));
@ -835,9 +819,9 @@ bool AlsaPlayback::reset()
#undef CHECK #undef CHECK
sp = nullptr; sp = nullptr;
mDevice->BufferSize = static_cast<uint>(bufferSizeInFrames); mDevice->mBufferSize = static_cast<uint>(bufferSizeInFrames);
mDevice->UpdateSize = static_cast<uint>(periodSizeInFrames); mDevice->mUpdateSize = static_cast<uint>(periodSizeInFrames);
mDevice->Frequency = rate; mDevice->mSampleRate = rate;
setDefaultChannelOrder(); setDefaultChannelOrder();
@ -850,7 +834,7 @@ void AlsaPlayback::start()
HwParamsPtr hp{CreateHwParams()}; HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \ #define CHECK(x) do { \
if(int err{x}; err < 0) \ if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \
snd_strerror(err)}; \ snd_strerror(err)}; \
} while(0) } while(0)
CHECK(snd_pcm_hw_params_current(mPcmHandle, hp.get())); CHECK(snd_pcm_hw_params_current(mPcmHandle, hp.get()));
@ -861,7 +845,7 @@ void AlsaPlayback::start()
int (AlsaPlayback::*thread_func)(){}; int (AlsaPlayback::*thread_func)(){};
if(access == SND_PCM_ACCESS_RW_INTERLEAVED) if(access == SND_PCM_ACCESS_RW_INTERLEAVED)
{ {
auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize); auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->mUpdateSize);
mBuffer.resize(static_cast<size_t>(datalen)); mBuffer.resize(static_cast<size_t>(datalen));
thread_func = &AlsaPlayback::mixerNoMMapProc; thread_func = &AlsaPlayback::mixerNoMMapProc;
} }
@ -874,11 +858,11 @@ void AlsaPlayback::start()
try { try {
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(thread_func), this}; mThread = std::thread{thread_func, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()}; "Failed to start mixing thread: {}", e.what()};
} }
} }
@ -891,7 +875,7 @@ void AlsaPlayback::stop()
mBuffer.clear(); mBuffer.clear();
int err{snd_pcm_drop(mPcmHandle)}; int err{snd_pcm_drop(mPcmHandle)};
if(err < 0) if(err < 0)
ERR("snd_pcm_drop failed: %s\n", snd_strerror(err)); ERR("snd_pcm_drop failed: {}", snd_strerror(err));
} }
ClockLatency AlsaPlayback::getClockLatency() ClockLatency AlsaPlayback::getClockLatency()
@ -903,18 +887,18 @@ ClockLatency AlsaPlayback::getClockLatency()
int err{snd_pcm_delay(mPcmHandle, &delay)}; int err{snd_pcm_delay(mPcmHandle, &delay)};
if(err < 0) if(err < 0)
{ {
ERR("Failed to get pcm delay: %s\n", snd_strerror(err)); ERR("Failed to get pcm delay: {}", snd_strerror(err));
delay = 0; delay = 0;
} }
ret.Latency = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)}; ret.Latency = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)};
ret.Latency /= mDevice->Frequency; ret.Latency /= mDevice->mSampleRate;
return ret; return ret;
} }
struct AlsaCapture final : public BackendBase { struct AlsaCapture final : public BackendBase {
AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } explicit AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaCapture() override; ~AlsaCapture() override;
void open(std::string_view name) override; void open(std::string_view name) override;
@ -954,7 +938,7 @@ void AlsaCapture::open(std::string_view name)
[name](const DevMap &entry) -> bool { return entry.name == name; }); [name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == CaptureDevices.cend()) if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
driver = iter->device_name; driver = iter->device_name;
} }
else else
@ -964,10 +948,10 @@ void AlsaCapture::open(std::string_view name)
driver = std::move(driveropt).value(); driver = std::move(driveropt).value();
} }
TRACE("Opening device \"%s\"\n", driver.c_str()); TRACE("Opening device \"{}\"", driver);
if(int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; err < 0) if(int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; err < 0)
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Could not open ALSA device \"%s\"", driver.c_str()}; "Could not open ALSA device \"{}\"", driver};
/* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
snd_config_update_free_global(); snd_config_update_free_global();
@ -998,16 +982,16 @@ void AlsaCapture::open(std::string_view name)
break; break;
} }
snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->BufferSize, snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->mBufferSize,
100u*mDevice->Frequency/1000u)}; 100u*mDevice->mSampleRate/1000u)};
snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->BufferSize, snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->mBufferSize,
25u*mDevice->Frequency/1000u)}; 25u*mDevice->mSampleRate/1000u)};
bool needring{false}; bool needring{false};
HwParamsPtr hp{CreateHwParams()}; HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \ #define CHECK(x) do { \
if(int err{x}; err < 0) \ if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \
snd_strerror(err)}; \ snd_strerror(err)}; \
} while(0) } while(0)
CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get()));
@ -1018,11 +1002,11 @@ void AlsaCapture::open(std::string_view name)
/* set channels (implicitly sets frame bits) */ /* set channels (implicitly sets frame bits) */
CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt())); CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()));
/* set rate (implicitly constrains period/buffer parameters) */ /* set rate (implicitly constrains period/buffer parameters) */
CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp.get(), mDevice->Frequency, 0)); CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp.get(), mDevice->mSampleRate, 0));
/* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */
if(snd_pcm_hw_params_set_buffer_size_min(mPcmHandle, hp.get(), &bufferSizeInFrames) < 0) if(snd_pcm_hw_params_set_buffer_size_min(mPcmHandle, hp.get(), &bufferSizeInFrames) < 0)
{ {
TRACE("Buffer too large, using intermediate ring buffer\n"); TRACE("Buffer too large, using intermediate ring buffer");
needring = true; needring = true;
CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp.get(), &bufferSizeInFrames)); CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp.get(), &bufferSizeInFrames));
} }
@ -1036,20 +1020,20 @@ void AlsaCapture::open(std::string_view name)
hp = nullptr; hp = nullptr;
if(needring) if(needring)
mRing = RingBuffer::Create(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false); mRing = RingBuffer::Create(mDevice->mBufferSize, mDevice->frameSizeFromFmt(), false);
mDevice->DeviceName = name; mDeviceName = name;
} }
void AlsaCapture::start() void AlsaCapture::start()
{ {
if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0) if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0)
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s", throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: {}",
snd_strerror(err)}; snd_strerror(err)};
if(int err{snd_pcm_start(mPcmHandle)}; err < 0) if(int err{snd_pcm_start(mPcmHandle)}; err < 0)
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s", throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: {}",
snd_strerror(err)}; snd_strerror(err)};
mDoCapture = true; mDoCapture = true;
@ -1073,7 +1057,7 @@ void AlsaCapture::stop()
mBuffer = std::move(temp); mBuffer = std::move(temp);
} }
if(int err{snd_pcm_drop(mPcmHandle)}; err < 0) if(int err{snd_pcm_drop(mPcmHandle)}; err < 0)
ERR("drop failed: %s\n", snd_strerror(err)); ERR("snd_pcm_drop failed: {}", snd_strerror(err));
mDoCapture = false; mDoCapture = false;
} }
@ -1109,7 +1093,7 @@ void AlsaCapture::captureSamples(std::byte *buffer, uint samples)
amt = snd_pcm_readi(mPcmHandle, al::to_address(outiter), samples); amt = snd_pcm_readi(mPcmHandle, al::to_address(outiter), samples);
if(amt < 0) if(amt < 0)
{ {
ERR("read error: %s\n", snd_strerror(static_cast<int>(amt))); ERR("read error: {}", snd_strerror(static_cast<int>(amt)));
if(amt == -EAGAIN) if(amt == -EAGAIN)
continue; continue;
@ -1123,8 +1107,8 @@ void AlsaCapture::captureSamples(std::byte *buffer, uint samples)
if(amt < 0) if(amt < 0)
{ {
const char *err{snd_strerror(static_cast<int>(amt))}; const char *err{snd_strerror(static_cast<int>(amt))};
ERR("restore error: %s\n", err); ERR("restore error: {}", err);
mDevice->handleDisconnect("Capture recovery failure: %s", err); mDevice->handleDisconnect("Capture recovery failure: {}", err);
break; break;
} }
/* If the amount available is less than what's asked, we lost it /* If the amount available is less than what's asked, we lost it
@ -1149,7 +1133,7 @@ uint AlsaCapture::availableSamples()
avail = snd_pcm_avail_update(mPcmHandle); avail = snd_pcm_avail_update(mPcmHandle);
if(avail < 0) if(avail < 0)
{ {
ERR("avail update failed: %s\n", snd_strerror(static_cast<int>(avail))); ERR("snd_pcm_avail_update failed: {}", snd_strerror(static_cast<int>(avail)));
avail = snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1); avail = snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1);
if(avail >= 0) if(avail >= 0)
@ -1162,29 +1146,29 @@ uint AlsaCapture::availableSamples()
if(avail < 0) if(avail < 0)
{ {
const char *err{snd_strerror(static_cast<int>(avail))}; const char *err{snd_strerror(static_cast<int>(avail))};
ERR("restore error: %s\n", err); ERR("restore error: {}", err);
mDevice->handleDisconnect("Capture recovery failure: %s", err); mDevice->handleDisconnect("Capture recovery failure: {}", err);
} }
} }
if(!mRing) if(!mRing)
{ {
if(avail < 0) avail = 0; avail = std::max<snd_pcm_sframes_t>(avail, 0);
avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size())); avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
if(avail > mLastAvail) mLastAvail = avail; mLastAvail = std::max(mLastAvail, avail);
return static_cast<uint>(mLastAvail); return static_cast<uint>(mLastAvail);
} }
while(avail > 0) while(avail > 0)
{ {
auto vec = mRing->getWriteVector(); auto vec = mRing->getWriteVector();
if(vec.first.len == 0) break; if(vec[0].len == 0) break;
snd_pcm_sframes_t amt{std::min(static_cast<snd_pcm_sframes_t>(vec.first.len), avail)}; snd_pcm_sframes_t amt{std::min(static_cast<snd_pcm_sframes_t>(vec[0].len), avail)};
amt = snd_pcm_readi(mPcmHandle, vec.first.buf, static_cast<snd_pcm_uframes_t>(amt)); amt = snd_pcm_readi(mPcmHandle, vec[0].buf, static_cast<snd_pcm_uframes_t>(amt));
if(amt < 0) if(amt < 0)
{ {
ERR("read error: %s\n", snd_strerror(static_cast<int>(amt))); ERR("read error: {}", snd_strerror(static_cast<int>(amt)));
if(amt == -EAGAIN) if(amt == -EAGAIN)
continue; continue;
@ -1199,8 +1183,8 @@ uint AlsaCapture::availableSamples()
if(amt < 0) if(amt < 0)
{ {
const char *err{snd_strerror(static_cast<int>(amt))}; const char *err{snd_strerror(static_cast<int>(amt))};
ERR("restore error: %s\n", err); ERR("restore error: {}", err);
mDevice->handleDisconnect("Capture recovery failure: %s", err); mDevice->handleDisconnect("Capture recovery failure: {}", err);
break; break;
} }
avail = amt; avail = amt;
@ -1222,11 +1206,11 @@ ClockLatency AlsaCapture::getClockLatency()
int err{snd_pcm_delay(mPcmHandle, &delay)}; int err{snd_pcm_delay(mPcmHandle, &delay)};
if(err < 0) if(err < 0)
{ {
ERR("Failed to get pcm delay: %s\n", snd_strerror(err)); ERR("Failed to get pcm delay: {}", snd_strerror(err));
delay = 0; delay = 0;
} }
ret.Latency = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)}; ret.Latency = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)};
ret.Latency /= mDevice->Frequency; ret.Latency /= mDevice->mSampleRate;
return ret; return ret;
} }
@ -1236,13 +1220,13 @@ ClockLatency AlsaCapture::getClockLatency()
bool AlsaBackendFactory::init() bool AlsaBackendFactory::init()
{ {
#ifdef HAVE_DYNLOAD #if HAVE_DYNLOAD
if(!alsa_handle) if(!alsa_handle)
{ {
alsa_handle = LoadLib("libasound.so.2"); alsa_handle = LoadLib("libasound.so.2");
if(!alsa_handle) if(!alsa_handle)
{ {
WARN("Failed to load %s\n", "libasound.so.2"); WARN("Failed to load {}", "libasound.so.2");
return false; return false;
} }
@ -1256,7 +1240,7 @@ bool AlsaBackendFactory::init()
if(!missing_funcs.empty()) if(!missing_funcs.empty())
{ {
WARN("Missing expected functions:%s\n", missing_funcs.c_str()); WARN("Missing expected functions:{}", missing_funcs);
CloseLib(alsa_handle); CloseLib(alsa_handle);
alsa_handle = nullptr; alsa_handle = nullptr;
return false; return false;

View file

@ -3,26 +3,18 @@
#include "base.h" #include "base.h"
#include <algorithm>
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <utility>
#include "core/devformat.h" #include "core/devformat.h"
namespace al { namespace al {
auto backend_exception::make_string(fmt::string_view fmt, fmt::format_args args) -> std::string
{ return fmt::vformat(fmt, std::move(args)); }
backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{
/* NOLINTBEGIN(*-array-to-pointer-decay) */
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
}
backend_exception::~backend_exception() = default; backend_exception::~backend_exception() = default;
} // namespace al } // namespace al
@ -50,8 +42,8 @@ ClockLatency BackendBase::getClockLatency()
* any given time during playback. Without a more accurate measurement from * any given time during playback. Without a more accurate measurement from
* the output, this is an okay approximation. * the output, this is an okay approximation.
*/ */
ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize}; ret.Latency = std::chrono::seconds{mDevice->mBufferSize - mDevice->mUpdateSize};
ret.Latency /= mDevice->Frequency; ret.Latency /= mDevice->mSampleRate;
return ret; return ret;
} }

View file

@ -5,14 +5,14 @@
#include <cstdarg> #include <cstdarg>
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
#include <ratio>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include "alc/events.h"
#include "core/device.h" #include "core/device.h"
#include "core/except.h" #include "core/except.h"
#include "alc/events.h" #include "fmt/core.h"
using uint = unsigned int; using uint = unsigned int;
@ -35,11 +35,12 @@ struct BackendBase {
virtual ClockLatency getClockLatency(); virtual ClockLatency getClockLatency();
DeviceBase *const mDevice; DeviceBase *const mDevice;
std::string mDeviceName;
BackendBase() = delete; BackendBase() = delete;
BackendBase(const BackendBase&) = delete; BackendBase(const BackendBase&) = delete;
BackendBase(BackendBase&&) = delete; BackendBase(BackendBase&&) = delete;
BackendBase(DeviceBase *device) noexcept : mDevice{device} { } explicit BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
virtual ~BackendBase() = default; virtual ~BackendBase() = default;
void operator=(const BackendBase&) = delete; void operator=(const BackendBase&) = delete;
@ -102,15 +103,20 @@ enum class backend_error {
class backend_exception final : public base_exception { class backend_exception final : public base_exception {
backend_error mErrorCode; backend_error mErrorCode;
static auto make_string(fmt::string_view fmt, fmt::format_args args) -> std::string;
public: public:
#ifdef __MINGW32__ template<typename ...Args>
[[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] backend_exception(backend_error code, fmt::format_string<Args...> fmt, Args&& ...args)
#else : base_exception{make_string(fmt, fmt::make_format_args(args...))}, mErrorCode{code}
[[gnu::format(printf, 3, 4)]] { }
#endif backend_exception(const backend_exception&) = default;
backend_exception(backend_error code, const char *msg, ...); backend_exception(backend_exception&&) = default;
~backend_exception() override; ~backend_exception() override;
backend_exception& operator=(const backend_exception&) = default;
backend_exception& operator=(backend_exception&&) = default;
[[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; } [[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; }
}; };

View file

@ -24,7 +24,9 @@
#include <cinttypes> #include <cinttypes>
#include <cmath> #include <cmath>
#include <functional>
#include <memory> #include <memory>
#include <optional>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -32,13 +34,13 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
#include <optional>
#include "alnumeric.h" #include "alnumeric.h"
#include "alstring.h" #include "alstring.h"
#include "core/converter.h" #include "core/converter.h"
#include "core/device.h" #include "core/device.h"
#include "core/logging.h" #include "core/logging.h"
#include "fmt/core.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include <AudioUnit/AudioUnit.h> #include <AudioUnit/AudioUnit.h>
@ -56,10 +58,40 @@ namespace {
constexpr auto OutputElement = 0; constexpr auto OutputElement = 0;
constexpr auto InputElement = 1; constexpr auto InputElement = 1;
// These following arrays should always be defined in ascending AudioChannelLabel value order
constexpr std::array<AudioChannelLabel, 1> MonoChanMap { kAudioChannelLabel_Mono };
constexpr std::array<AudioChannelLabel, 2> StereoChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right};
constexpr std::array<AudioChannelLabel, 4> QuadChanMap {
kAudioChannelLabel_Left, kAudioChannelLabel_Right,
kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround
};
constexpr std::array<AudioChannelLabel, 6> X51ChanMap {
kAudioChannelLabel_Left, kAudioChannelLabel_Right,
kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround
};
constexpr std::array<AudioChannelLabel, 6> X51RearChanMap {
kAudioChannelLabel_Left, kAudioChannelLabel_Right,
kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft
};
constexpr std::array<AudioChannelLabel, 7> X61ChanMap {
kAudioChannelLabel_Left, kAudioChannelLabel_Right,
kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
kAudioChannelLabel_CenterSurround,
kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft
};
constexpr std::array<AudioChannelLabel, 8> X71ChanMap {
kAudioChannelLabel_Left, kAudioChannelLabel_Right,
kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround,
kAudioChannelLabel_LeftCenter, kAudioChannelLabel_RightCenter
};
struct FourCCPrinter { struct FourCCPrinter {
char mString[sizeof(UInt32) + 1]{}; char mString[sizeof(UInt32) + 1]{};
constexpr FourCCPrinter(UInt32 code) noexcept explicit constexpr FourCCPrinter(UInt32 code) noexcept
{ {
for(size_t i{0};i < sizeof(UInt32);++i) for(size_t i{0};i < sizeof(UInt32);++i)
{ {
@ -73,7 +105,7 @@ struct FourCCPrinter {
code >>= 8; code >>= 8;
} }
} }
constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { } explicit constexpr FourCCPrinter(OSStatus code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { }
constexpr const char *c_str() const noexcept { return mString; } constexpr const char *c_str() const noexcept { return mString; }
}; };
@ -159,19 +191,19 @@ std::string GetDeviceName(AudioDeviceID devId)
/* Clear extraneous nul chars that may have been written with the name /* Clear extraneous nul chars that may have been written with the name
* string, and return it. * string, and return it.
*/ */
while(!devname.back()) while(!devname.empty() && !devname.back())
devname.pop_back(); devname.pop_back();
return devname; return devname;
} }
UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) auto GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) -> UInt32
{ {
UInt32 propSize{}; auto propSize = UInt32{};
auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
&propSize); &propSize);
if(err) if(err)
{ {
ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n", ERR("kAudioDevicePropertyStreamConfiguration size query failed: '{}' ({})",
FourCCPrinter{err}.c_str(), err); FourCCPrinter{err}.c_str(), err);
return 0; return 0;
} }
@ -183,15 +215,14 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
buflist); buflist);
if(err) if(err)
{ {
ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n", ERR("kAudioDevicePropertyStreamConfiguration query failed: '{}' ({})",
FourCCPrinter{err}.c_str(), err); FourCCPrinter{err}.c_str(), err);
return 0; return 0;
} }
UInt32 numChannels{0}; auto numChannels = UInt32{0};
for(size_t i{0};i < buflist->mNumberBuffers;++i) for(size_t i{0};i < buflist->mNumberBuffers;++i)
numChannels += buflist->mBuffers[i].mNumberChannels; numChannels += buflist->mBuffers[i].mNumberChannels;
return numChannels; return numChannels;
} }
@ -201,14 +232,14 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
UInt32 propSize{}; UInt32 propSize{};
if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize)) if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
{ {
ERR("Failed to get device list size: %u\n", err); ERR("Failed to get device list size: {}", err);
return; return;
} }
auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown); auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data())) if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
{ {
ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); ERR("Failed to get device list: '{}' ({})", FourCCPrinter{err}.c_str(), err);
return; return;
} }
@ -223,7 +254,7 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
{ {
newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)}); newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
const auto &entry = newdevs.back(); const auto &entry = newdevs.back();
TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId); TRACE("Got device: {} = ID {}", entry.mName, entry.mId);
} }
for(const AudioDeviceID devId : devIds) for(const AudioDeviceID devId : devIds)
{ {
@ -240,7 +271,7 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
{ {
newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)}); newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
const auto &entry = newdevs.back(); const auto &entry = newdevs.back();
TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId); TRACE("Got device: {} = ID {}", entry.mName, entry.mId);
} }
} }
@ -255,14 +286,12 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
{ return entry.mName == curitem->mName; }; { return entry.mName == curitem->mName; };
if(std::find_if(newdevs.begin(), curitem, check_match) != curitem) if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
{ {
std::string name{curitem->mName}; auto name = std::string{curitem->mName};
size_t count{1}; auto count = 1_uz;
auto check_name = [&name](const DeviceEntry &entry) -> bool auto check_name = [&name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; }; { return entry.mName == name; };
do { do {
name = curitem->mName; name = fmt::format("{} #{}", curitem->mName, ++count);
name += " #";
name += std::to_string(++count);
} while(std::find_if(newdevs.begin(), curitem, check_name) != curitem); } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
curitem->mName = std::move(name); curitem->mName = std::move(name);
} }
@ -277,18 +306,18 @@ struct DeviceHelper {
DeviceHelper() DeviceHelper()
{ {
AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
if (status != noErr) if (status != noErr)
ERR("AudioObjectAddPropertyListener fail: %d", status); ERR("AudioObjectAddPropertyListener fail: {}", status);
} }
~DeviceHelper() ~DeviceHelper()
{ {
AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
if (status != noErr) if (status != noErr)
ERR("AudioObjectRemovePropertyListener fail: %d", status); ERR("AudioObjectRemovePropertyListener fail: {}", status);
} }
static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses, static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses,
@ -322,7 +351,7 @@ static constexpr char ca_device[] = "CoreAudio Default";
struct CoreAudioPlayback final : public BackendBase { struct CoreAudioPlayback final : public BackendBase {
CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~CoreAudioPlayback() override; ~CoreAudioPlayback() override;
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
@ -377,7 +406,7 @@ void CoreAudioPlayback::open(std::string_view name)
auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
if(devmatch == PlaybackList.cend()) if(devmatch == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
audioDevice = devmatch->mId; audioDevice = devmatch->mId;
} }
@ -385,8 +414,8 @@ void CoreAudioPlayback::open(std::string_view name)
if(name.empty()) if(name.empty())
name = ca_device; name = ca_device;
else if(name != ca_device) else if(name != ca_device)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
#endif #endif
/* open the default output unit */ /* open the default output unit */
@ -410,7 +439,7 @@ void CoreAudioPlayback::open(std::string_view name)
OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)}; OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; "Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err};
#if CAN_ENUMERATE #if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown) if(audioDevice != kAudioDeviceUnknown)
@ -421,7 +450,7 @@ void CoreAudioPlayback::open(std::string_view name)
err = AudioUnitInitialize(audioUnit); err = AudioUnitInitialize(audioUnit);
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; "Could not initialize audio unit: '{}' ({})", FourCCPrinter{err}.c_str(), err};
/* WARNING: I don't know if "valid" audio unit values are guaranteed to be /* WARNING: I don't know if "valid" audio unit values are guaranteed to be
* non-0. If not, this logic is broken. * non-0. If not, this logic is broken.
@ -435,7 +464,7 @@ void CoreAudioPlayback::open(std::string_view name)
#if CAN_ENUMERATE #if CAN_ENUMERATE
if(!name.empty()) if(!name.empty())
mDevice->DeviceName = name; mDeviceName = name;
else else
{ {
UInt32 propSize{sizeof(audioDevice)}; UInt32 propSize{sizeof(audioDevice)};
@ -444,8 +473,8 @@ void CoreAudioPlayback::open(std::string_view name)
kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize); kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize);
std::string devname{GetDeviceName(audioDevice)}; std::string devname{GetDeviceName(audioDevice)};
if(!devname.empty()) mDevice->DeviceName = std::move(devname); if(!devname.empty()) mDeviceName = std::move(devname);
else mDevice->DeviceName = "Unknown Device Name"; else mDeviceName = "Unknown Device Name";
} }
if(audioDevice != kAudioDeviceUnknown) if(audioDevice != kAudioDeviceUnknown)
@ -454,16 +483,16 @@ void CoreAudioPlayback::open(std::string_view name)
err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false, err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false,
kAudioObjectPropertyElementMaster, sizeof(type), &type); kAudioObjectPropertyElementMaster, sizeof(type), &type);
if(err != noErr) if(err != noErr)
ERR("Failed to get audio device type: %u\n", err); WARN("Failed to get audio device type: '{}' ({})", FourCCPrinter{err}.c_str(), err);
else else
{ {
TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str()); TRACE("Got device type '{}'", FourCCPrinter{type}.c_str());
mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones)); mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones));
} }
} }
#else #else
mDevice->DeviceName = name; mDeviceName = name;
#endif #endif
} }
@ -471,7 +500,7 @@ bool CoreAudioPlayback::reset()
{ {
OSStatus err{AudioUnitUninitialize(mAudioUnit)}; OSStatus err{AudioUnitUninitialize(mAudioUnit)};
if(err != noErr) if(err != noErr)
ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); ERR("AudioUnitUninitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
/* retrieve default output unit's properties (output side) */ /* retrieve default output unit's properties (output side) */
AudioStreamBasicDescription streamFormat{}; AudioStreamBasicDescription streamFormat{};
@ -480,33 +509,75 @@ bool CoreAudioPlayback::reset()
OutputElement, &streamFormat, &size); OutputElement, &streamFormat, &size);
if(err != noErr || size != sizeof(streamFormat)) if(err != noErr || size != sizeof(streamFormat))
{ {
ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), ERR("AudioUnitGetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(),
err); err);
return false; return false;
} }
#if 0
TRACE("Output streamFormat of default output unit -\n");
TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
#endif
/* Use the sample rate from the output unit's current parameters, but reset /* Use the sample rate from the output unit's current parameters, but reset
* everything else. * everything else.
*/ */
if(mDevice->Frequency != streamFormat.mSampleRate) if(mDevice->mSampleRate != streamFormat.mSampleRate)
{ {
mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/ mDevice->mBufferSize = static_cast<uint>(mDevice->mBufferSize*streamFormat.mSampleRate/
mDevice->Frequency + 0.5); mDevice->mSampleRate + 0.5);
mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate); mDevice->mSampleRate = static_cast<uint>(streamFormat.mSampleRate);
} }
/* FIXME: How to tell what channels are what in the output device, and how struct ChannelMap {
* to specify what we're giving? e.g. 6.0 vs 5.1 DevFmtChannels fmt;
al::span<const AudioChannelLabel> map;
bool is_51rear;
};
static constexpr std::array<ChannelMap,7> chanmaps{{
{ DevFmtX71, X71ChanMap, false },
{ DevFmtX61, X61ChanMap, false },
{ DevFmtX51, X51ChanMap, false },
{ DevFmtX51, X51RearChanMap, true },
{ DevFmtQuad, QuadChanMap, false },
{ DevFmtStereo, StereoChanMap, false },
{ DevFmtMono, MonoChanMap, false }
}};
if(!mDevice->Flags.test(ChannelsRequest))
{
auto propSize = UInt32{};
auto writable = Boolean{};
err = AudioUnitGetPropertyInfo(mAudioUnit, kAudioUnitProperty_AudioChannelLayout,
kAudioUnitScope_Output, OutputElement, &propSize, &writable);
if(err == noErr)
{
auto layout_data = std::make_unique<char[]>(propSize);
auto *layout = reinterpret_cast<AudioChannelLayout*>(layout_data.get());
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_AudioChannelLayout,
kAudioUnitScope_Output, OutputElement, layout, &propSize);
if(err == noErr)
{
auto descs = al::span{std::data(layout->mChannelDescriptions),
layout->mNumberChannelDescriptions};
auto labels = std::vector<AudioChannelLayoutTag>(descs.size());
std::transform(descs.begin(), descs.end(), labels.begin(),
std::mem_fn(&AudioChannelDescription::mChannelLabel));
sort(labels.begin(), labels.end());
auto check_labels = [&labels](const ChannelMap &chanmap) -> bool
{
return std::includes(labels.begin(), labels.end(), chanmap.map.begin(),
chanmap.map.end());
};
auto chaniter = std::find_if(chanmaps.cbegin(), chanmaps.cend(), check_labels);
if(chaniter != chanmaps.cend())
mDevice->FmtChans = chaniter->fmt;
}
}
}
/* TODO: Also set kAudioUnitProperty_AudioChannelLayout according to the AL
* device's channel configuration.
*/ */
streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt(); streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
@ -548,7 +619,7 @@ bool CoreAudioPlayback::reset()
OutputElement, &streamFormat, sizeof(streamFormat)); OutputElement, &streamFormat, sizeof(streamFormat));
if(err != noErr) if(err != noErr)
{ {
ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), ERR("AudioUnitSetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(),
err); err);
return false; return false;
} }
@ -566,7 +637,7 @@ bool CoreAudioPlayback::reset()
kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct)); kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr) if(err != noErr)
{ {
ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n", ERR("AudioUnitSetProperty(SetRenderCallback) failed: '{}' ({})",
FourCCPrinter{err}.c_str(), err); FourCCPrinter{err}.c_str(), err);
return false; return false;
} }
@ -575,7 +646,7 @@ bool CoreAudioPlayback::reset()
err = AudioUnitInitialize(mAudioUnit); err = AudioUnitInitialize(mAudioUnit);
if(err != noErr) if(err != noErr)
{ {
ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); ERR("AudioUnitInitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
return false; return false;
} }
@ -587,19 +658,19 @@ void CoreAudioPlayback::start()
const OSStatus err{AudioOutputUnitStart(mAudioUnit)}; const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; "AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err};
} }
void CoreAudioPlayback::stop() void CoreAudioPlayback::stop()
{ {
OSStatus err{AudioOutputUnitStop(mAudioUnit)}; OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr) if(err != noErr)
ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
} }
struct CoreAudioCapture final : public BackendBase { struct CoreAudioCapture final : public BackendBase {
CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { } explicit CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~CoreAudioCapture() override; ~CoreAudioCapture() override;
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
@ -650,7 +721,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
inNumberFrames, &audiobuf.list)}; inNumberFrames, &audiobuf.list)};
if(err != noErr) if(err != noErr)
{ {
ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); ERR("AudioUnitRender capture error: '{}' ({})", FourCCPrinter{err}.c_str(), err);
return err; return err;
} }
@ -676,7 +747,7 @@ void CoreAudioCapture::open(std::string_view name)
auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name); auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
if(devmatch == CaptureList.cend()) if(devmatch == CaptureList.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
audioDevice = devmatch->mId; audioDevice = devmatch->mId;
} }
@ -684,8 +755,8 @@ void CoreAudioCapture::open(std::string_view name)
if(name.empty()) if(name.empty())
name = ca_device; name = ca_device;
else if(name != ca_device) else if(name != ca_device)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
#endif #endif
AudioComponentDescription desc{}; AudioComponentDescription desc{};
@ -709,7 +780,7 @@ void CoreAudioCapture::open(std::string_view name)
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; "Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err};
// Turn off AudioUnit output // Turn off AudioUnit output
UInt32 enableIO{0}; UInt32 enableIO{0};
@ -717,7 +788,7 @@ void CoreAudioCapture::open(std::string_view name)
kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO)); kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(), "Could not disable audio unit output property: '{}' ({})", FourCCPrinter{err}.c_str(),
err}; err};
// Turn on AudioUnit input // Turn on AudioUnit input
@ -726,7 +797,7 @@ void CoreAudioCapture::open(std::string_view name)
kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO)); kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(), "Could not enable audio unit input property: '{}' ({})", FourCCPrinter{err}.c_str(),
err}; err};
#if CAN_ENUMERATE #if CAN_ENUMERATE
@ -745,7 +816,7 @@ void CoreAudioCapture::open(std::string_view name)
kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct)); kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; "Could not set capture callback: '{}' ({})", FourCCPrinter{err}.c_str(), err};
// Disable buffer allocation for capture // Disable buffer allocation for capture
UInt32 flag{0}; UInt32 flag{0};
@ -753,14 +824,14 @@ void CoreAudioCapture::open(std::string_view name)
kAudioUnitScope_Output, InputElement, &flag, sizeof(flag)); kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(), "Could not disable buffer allocation property: '{}' ({})", FourCCPrinter{err}.c_str(),
err}; err};
// Initialize the device // Initialize the device
err = AudioUnitInitialize(mAudioUnit); err = AudioUnitInitialize(mAudioUnit);
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; "Could not initialize audio unit: '{}' ({})", FourCCPrinter{err}.c_str(), err};
// Get the hardware format // Get the hardware format
AudioStreamBasicDescription hardwareFormat{}; AudioStreamBasicDescription hardwareFormat{};
@ -769,7 +840,7 @@ void CoreAudioCapture::open(std::string_view name)
InputElement, &hardwareFormat, &propertySize); InputElement, &hardwareFormat, &propertySize);
if(err != noErr || propertySize != sizeof(hardwareFormat)) if(err != noErr || propertySize != sizeof(hardwareFormat))
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; "Could not get input format: '{}' ({})", FourCCPrinter{err}.c_str(), err};
// Set up the requested format description // Set up the requested format description
AudioStreamBasicDescription requestedFormat{}; AudioStreamBasicDescription requestedFormat{};
@ -825,13 +896,13 @@ void CoreAudioCapture::open(std::string_view name)
case DevFmtX7144: case DevFmtX7144:
case DevFmtX3D71: case DevFmtX3D71:
case DevFmtAmbi3D: case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s not supported", throw al::backend_exception{al::backend_error::DeviceError, "{} not supported",
DevFmtChannelsString(mDevice->FmtChans)}; DevFmtChannelsString(mDevice->FmtChans)};
} }
requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8; requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame; requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
requestedFormat.mSampleRate = mDevice->Frequency; requestedFormat.mSampleRate = mDevice->mSampleRate;
requestedFormat.mFormatID = kAudioFormatLinearPCM; requestedFormat.mFormatID = kAudioFormatLinearPCM;
requestedFormat.mReserved = 0; requestedFormat.mReserved = 0;
requestedFormat.mFramesPerPacket = 1; requestedFormat.mFramesPerPacket = 1;
@ -851,18 +922,18 @@ void CoreAudioCapture::open(std::string_view name)
InputElement, &outputFormat, sizeof(outputFormat)); InputElement, &outputFormat, sizeof(outputFormat));
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; "Could not set input format: '{}' ({})", FourCCPrinter{err}.c_str(), err};
/* Calculate the minimum AudioUnit output format frame count for the pre- /* Calculate the minimum AudioUnit output format frame count for the pre-
* conversion ring buffer. Ensure at least 100ms for the total buffer. * conversion ring buffer. Ensure at least 100ms for the total buffer.
*/ */
double srateScale{outputFormat.mSampleRate / mDevice->Frequency}; double srateScale{outputFormat.mSampleRate / mDevice->mSampleRate};
auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)), auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->mBufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10_u64); static_cast<UInt32>(outputFormat.mSampleRate)/10_u64);
FrameCount64 += MaxResamplerPadding; FrameCount64 += MaxResamplerPadding;
if(FrameCount64 > std::numeric_limits<int32_t>::max()) if(FrameCount64 > std::numeric_limits<int32_t>::max())
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Calculated frame count is too large: %" PRIu64, FrameCount64}; "Calculated frame count is too large: {}", FrameCount64};
UInt32 outputFrameCount{}; UInt32 outputFrameCount{};
propertySize = sizeof(outputFrameCount); propertySize = sizeof(outputFrameCount);
@ -870,7 +941,7 @@ void CoreAudioCapture::open(std::string_view name)
kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize); kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
if(err != noErr || propertySize != sizeof(outputFrameCount)) if(err != noErr || propertySize != sizeof(outputFrameCount))
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; "Could not get input frame count: '{}' ({})", FourCCPrinter{err}.c_str(), err};
mCaptureData.resize(outputFrameCount * mFrameSize); mCaptureData.resize(outputFrameCount * mFrameSize);
@ -878,14 +949,14 @@ void CoreAudioCapture::open(std::string_view name)
mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false); mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
/* Set up sample converter if needed */ /* Set up sample converter if needed */
if(outputFormat.mSampleRate != mDevice->Frequency) if(outputFormat.mSampleRate != mDevice->mSampleRate)
mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate), mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
mDevice->Frequency, Resampler::FastBSinc24); mDevice->mSampleRate, Resampler::FastBSinc24);
#if CAN_ENUMERATE #if CAN_ENUMERATE
if(!name.empty()) if(!name.empty())
mDevice->DeviceName = name; mDeviceName = name;
else else
{ {
UInt32 propSize{sizeof(audioDevice)}; UInt32 propSize{sizeof(audioDevice)};
@ -894,11 +965,11 @@ void CoreAudioCapture::open(std::string_view name)
kAudioUnitScope_Global, InputElement, &audioDevice, &propSize); kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
std::string devname{GetDeviceName(audioDevice)}; std::string devname{GetDeviceName(audioDevice)};
if(!devname.empty()) mDevice->DeviceName = std::move(devname); if(!devname.empty()) mDeviceName = std::move(devname);
else mDevice->DeviceName = "Unknown Device Name"; else mDeviceName = "Unknown Device Name";
} }
#else #else
mDevice->DeviceName = name; mDeviceName = name;
#endif #endif
} }
@ -908,14 +979,14 @@ void CoreAudioCapture::start()
OSStatus err{AudioOutputUnitStart(mAudioUnit)}; OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; "AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err};
} }
void CoreAudioCapture::stop() void CoreAudioCapture::stop()
{ {
OSStatus err{AudioOutputUnitStop(mAudioUnit)}; OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr) if(err != noErr)
ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
} }
void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples) void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
@ -927,16 +998,16 @@ void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
} }
auto rec_vec = mRing->getReadVector(); auto rec_vec = mRing->getReadVector();
const void *src0{rec_vec.first.buf}; const void *src0{rec_vec[0].buf};
auto src0len = static_cast<uint>(rec_vec.first.len); auto src0len = static_cast<uint>(rec_vec[0].len);
uint got{mConverter->convert(&src0, &src0len, buffer, samples)}; uint got{mConverter->convert(&src0, &src0len, buffer, samples)};
size_t total_read{rec_vec.first.len - src0len}; size_t total_read{rec_vec[0].len - src0len};
if(got < samples && !src0len && rec_vec.second.len > 0) if(got < samples && !src0len && rec_vec[1].len > 0)
{ {
const void *src1{rec_vec.second.buf}; const void *src1{rec_vec[1].buf};
auto src1len = static_cast<uint>(rec_vec.second.len); auto src1len = static_cast<uint>(rec_vec[1].len);
got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got); got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
total_read += rec_vec.second.len - src1len; total_read += rec_vec[1].len - src1len;
} }
mRing->readAdvance(total_read); mRing->readAdvance(total_read);

View file

@ -37,21 +37,20 @@
#include <cassert> #include <cassert>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <functional>
#include <memory.h> #include <memory.h>
#include <mutex>
#include <string> #include <string>
#include <thread> #include <thread>
#include <vector> #include <vector>
#include "alnumeric.h"
#include "alspan.h" #include "alspan.h"
#include "alstring.h"
#include "althrd_setname.h" #include "althrd_setname.h"
#include "comptr.h" #include "comptr.h"
#include "core/device.h" #include "core/device.h"
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "fmt/core.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include "strutils.h" #include "strutils.h"
@ -90,10 +89,7 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0
namespace { namespace {
#define DEVNAME_HEAD "OpenAL Soft on " #if HAVE_DYNLOAD
#ifdef HAVE_DYNLOAD
void *ds_handle; void *ds_handle;
HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter); HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext); HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
@ -146,24 +142,19 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
return TRUE; return TRUE;
auto& devices = *static_cast<std::vector<DevMap>*>(data); auto& devices = *static_cast<std::vector<DevMap>*>(data);
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)}; const auto basename = wstr_to_utf8(desc);
int count{1}; auto count = 1;
std::string newname{basename}; auto newname = basename;
while(checkName(devices, newname)) while(checkName(devices, newname))
{ newname = fmt::format("{} #{}", basename, ++count);
newname = basename; const DevMap &newentry = devices.emplace_back(std::move(newname), *guid);
newname += " #";
newname += std::to_string(++count);
}
devices.emplace_back(std::move(newname), *guid);
const DevMap &newentry = devices.back();
OLECHAR *guidstr{nullptr}; OLECHAR *guidstr{nullptr};
HRESULT hr{StringFromCLSID(*guid, &guidstr)}; HRESULT hr{StringFromCLSID(*guid, &guidstr)};
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
{ {
TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr); TRACE("Got device \"{}\", GUID \"{}\"", newentry.name, wstr_to_utf8(guidstr));
CoTaskMemFree(guidstr); CoTaskMemFree(guidstr);
} }
@ -172,7 +163,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
struct DSoundPlayback final : public BackendBase { struct DSoundPlayback final : public BackendBase {
DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundPlayback() override; ~DSoundPlayback() override;
int mixerProc(); int mixerProc();
@ -215,14 +206,15 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
HRESULT err{mBuffer->GetCaps(&DSBCaps)}; HRESULT err{mBuffer->GetCaps(&DSBCaps)};
if(FAILED(err)) if(FAILED(err))
{ {
ERR("Failed to get buffer caps: 0x%lx\n", err); ERR("Failed to get buffer caps: {:#x}", as_unsigned(err));
mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err); mDevice->handleDisconnect("Failure retrieving playback buffer info: {:#x}",
as_unsigned(err));
return 1; return 1;
} }
const size_t FrameStep{mDevice->channelsFromFmt()}; const size_t FrameStep{mDevice->channelsFromFmt()};
uint FrameSize{mDevice->frameSizeFromFmt()}; uint FrameSize{mDevice->frameSizeFromFmt()};
DWORD FragSize{mDevice->UpdateSize * FrameSize}; DWORD FragSize{mDevice->mUpdateSize * FrameSize};
bool Playing{false}; bool Playing{false};
DWORD LastCursor{0u}; DWORD LastCursor{0u};
@ -242,8 +234,9 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
err = mBuffer->Play(0, 0, DSBPLAY_LOOPING); err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
if(FAILED(err)) if(FAILED(err))
{ {
ERR("Failed to play buffer: 0x%lx\n", err); ERR("Failed to play buffer: {:#x}", as_unsigned(err));
mDevice->handleDisconnect("Failure starting playback: 0x%lx", err); mDevice->handleDisconnect("Failure starting playback: {:#x}",
as_unsigned(err));
return 1; return 1;
} }
Playing = true; Playing = true;
@ -251,7 +244,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE); avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
if(avail != WAIT_OBJECT_0) if(avail != WAIT_OBJECT_0)
ERR("WaitForSingleObjectEx error: 0x%lx\n", avail); ERR("WaitForSingleObjectEx error: {:#x}", avail);
continue; continue;
} }
avail -= avail%FragSize; avail -= avail%FragSize;
@ -264,7 +257,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
// If the buffer is lost, restore it and lock // If the buffer is lost, restore it and lock
if(err == DSERR_BUFFERLOST) if(err == DSERR_BUFFERLOST)
{ {
WARN("Buffer lost, restoring...\n"); WARN("Buffer lost, restoring...");
err = mBuffer->Restore(); err = mBuffer->Restore();
if(SUCCEEDED(err)) if(SUCCEEDED(err))
{ {
@ -274,22 +267,19 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
&WritePtr2, &WriteCnt2, 0); &WritePtr2, &WriteCnt2, 0);
} }
} }
if(FAILED(err))
if(SUCCEEDED(err))
{ {
mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep); ERR("Buffer lock error: {:#x}", as_unsigned(err));
if(WriteCnt2 > 0) mDevice->handleDisconnect("Failed to lock output buffer: {:#x}", as_unsigned(err));
mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
}
else
{
ERR("Buffer lock error: %#lx\n", err);
mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
return 1; return 1;
} }
mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
if(WriteCnt2 > 0)
mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
// Update old write cursor location // Update old write cursor location
LastCursor += WriteCnt1+WriteCnt2; LastCursor += WriteCnt1+WriteCnt2;
LastCursor %= DSBCaps.dwBufferBytes; LastCursor %= DSBCaps.dwBufferBytes;
@ -307,7 +297,7 @@ void DSoundPlayback::open(std::string_view name)
ComWrapper com{}; ComWrapper com{};
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr)) if(FAILED(hr))
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr));
} }
const GUID *guid{nullptr}; const GUID *guid{nullptr};
@ -329,7 +319,7 @@ void DSoundPlayback::open(std::string_view name)
[&id](const DevMap &entry) -> bool { return entry.guid == id; }); [&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == PlaybackDevices.cend()) if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
} }
guid = &iter->guid; guid = &iter->guid;
} }
@ -348,15 +338,15 @@ void DSoundPlayback::open(std::string_view name)
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
if(FAILED(hr)) if(FAILED(hr))
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}",
hr}; as_unsigned(hr)};
mNotifies = nullptr; mNotifies = nullptr;
mBuffer = nullptr; mBuffer = nullptr;
mPrimaryBuffer = nullptr; mPrimaryBuffer = nullptr;
mDS = std::move(ds); mDS = std::move(ds);
mDevice->DeviceName = name; mDeviceName = name;
} }
bool DSoundPlayback::reset() bool DSoundPlayback::reset()
@ -391,7 +381,7 @@ bool DSoundPlayback::reset()
HRESULT hr{mDS->GetSpeakerConfig(&speakers)}; HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
if(FAILED(hr)) if(FAILED(hr))
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get speaker config: 0x%08lx", hr}; "Failed to get speaker config: {:#x}", as_unsigned(hr)};
speakers = DSSPEAKER_CONFIG(speakers); speakers = DSSPEAKER_CONFIG(speakers);
if(!mDevice->Flags.test(ChannelsRequest)) if(!mDevice->Flags.test(ChannelsRequest))
@ -407,7 +397,7 @@ bool DSoundPlayback::reset()
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
mDevice->FmtChans = DevFmtX71; mDevice->FmtChans = DevFmtX71;
else else
ERR("Unknown system speaker config: 0x%lx\n", speakers); ERR("Unknown system speaker config: {:#x}", speakers);
} }
mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE)); mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK}; const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
@ -435,7 +425,7 @@ bool DSoundPlayback::reset()
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8); OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels * OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8); OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency; OutputType.Format.nSamplesPerSec = mDevice->mSampleRate;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign; OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0; OutputType.Format.cbSize = 0;
@ -469,16 +459,16 @@ bool DSoundPlayback::reset()
if(FAILED(hr)) if(FAILED(hr))
break; break;
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize};
if(num_updates > MAX_UPDATES) if(num_updates > MAX_UPDATES)
num_updates = MAX_UPDATES; num_updates = MAX_UPDATES;
mDevice->BufferSize = mDevice->UpdateSize * num_updates; mDevice->mBufferSize = mDevice->mUpdateSize * num_updates;
DSBUFFERDESC DSBDescription{}; DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2
| DSBCAPS_GLOBALFOCUS; | DSBCAPS_GLOBALFOCUS;
DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; DSBDescription.dwBufferBytes = mDevice->mBufferSize * OutputType.Format.nBlockAlign;
DSBDescription.lpwfxFormat = &OutputType.Format; DSBDescription.lpwfxFormat = &OutputType.Format;
hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr); hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr);
@ -492,13 +482,13 @@ bool DSoundPlayback::reset()
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies)); hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies));
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
{ {
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize};
assert(num_updates <= MAX_UPDATES); assert(num_updates <= MAX_UPDATES);
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots{}; std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots{};
for(uint i{0};i < num_updates;++i) for(uint i{0};i < num_updates;++i)
{ {
nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign; nots[i].dwOffset = i * mDevice->mUpdateSize * OutputType.Format.nBlockAlign;
nots[i].hEventNotify = mNotifyEvent; nots[i].hEventNotify = mNotifyEvent;
} }
if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK) if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
@ -524,11 +514,11 @@ void DSoundPlayback::start()
{ {
try { try {
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this}; mThread = std::thread{&DSoundPlayback::mixerProc, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()}; "Failed to start mixing thread: {}", e.what()};
} }
} }
@ -543,7 +533,7 @@ void DSoundPlayback::stop()
struct DSoundCapture final : public BackendBase { struct DSoundCapture final : public BackendBase {
DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } explicit DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundCapture() override; ~DSoundCapture() override;
void open(std::string_view name) override; void open(std::string_view name) override;
@ -580,7 +570,7 @@ void DSoundCapture::open(std::string_view name)
ComWrapper com{}; ComWrapper com{};
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr)) if(FAILED(hr))
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr));
} }
const GUID *guid{nullptr}; const GUID *guid{nullptr};
@ -602,7 +592,7 @@ void DSoundCapture::open(std::string_view name)
[&id](const DevMap &entry) -> bool { return entry.guid == id; }); [&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == CaptureDevices.cend()) if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
} }
guid = &iter->guid; guid = &iter->guid;
} }
@ -612,9 +602,9 @@ void DSoundCapture::open(std::string_view name)
case DevFmtByte: case DevFmtByte:
case DevFmtUShort: case DevFmtUShort:
case DevFmtUInt: case DevFmtUInt:
WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); WARN("{} capture samples not supported", DevFmtTypeString(mDevice->FmtType));
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
case DevFmtUByte: case DevFmtUByte:
case DevFmtShort: case DevFmtShort:
@ -636,8 +626,8 @@ void DSoundCapture::open(std::string_view name)
case DevFmtX7144: case DevFmtX7144:
case DevFmtX3D71: case DevFmtX3D71:
case DevFmtAmbi3D: case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); WARN("{} capture not supported", DevFmtChannelsString(mDevice->FmtChans));
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported",
DevFmtChannelsString(mDevice->FmtChans)}; DevFmtChannelsString(mDevice->FmtChans)};
} }
@ -646,7 +636,7 @@ void DSoundCapture::open(std::string_view name)
InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8); InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels * InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
InputType.Format.wBitsPerSample / 8); InputType.Format.wBitsPerSample / 8);
InputType.Format.nSamplesPerSec = mDevice->Frequency; InputType.Format.nSamplesPerSec = mDevice->mSampleRate;
InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec * InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
InputType.Format.nBlockAlign; InputType.Format.nBlockAlign;
InputType.Format.cbSize = 0; InputType.Format.cbSize = 0;
@ -663,7 +653,7 @@ void DSoundCapture::open(std::string_view name)
InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
} }
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)};
DSCBUFFERDESC DSCBDescription{}; DSCBUFFERDESC DSCBDescription{};
DSCBDescription.dwSize = sizeof(DSCBDescription); DSCBDescription.dwSize = sizeof(DSCBDescription);
@ -676,7 +666,7 @@ void DSoundCapture::open(std::string_view name)
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr); mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr);
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); mRing = RingBuffer::Create(mDevice->mBufferSize, InputType.Format.nBlockAlign, false);
if(FAILED(hr)) if(FAILED(hr))
{ {
@ -684,14 +674,14 @@ void DSoundCapture::open(std::string_view name)
mDSCbuffer = nullptr; mDSCbuffer = nullptr;
mDSC = nullptr; mDSC = nullptr;
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}",
hr}; as_unsigned(hr)};
} }
mBufferBytes = DSCBDescription.dwBufferBytes; mBufferBytes = DSCBDescription.dwBufferBytes;
setDefaultWFXChannelOrder(); setDefaultWFXChannelOrder();
mDevice->DeviceName = name; mDeviceName = name;
} }
void DSoundCapture::start() void DSoundCapture::start()
@ -699,7 +689,7 @@ void DSoundCapture::start()
const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)}; const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
if(FAILED(hr)) if(FAILED(hr))
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failure starting capture: 0x%lx", hr}; "Failure starting capture: {:#x}", as_unsigned(hr)};
} }
void DSoundCapture::stop() void DSoundCapture::stop()
@ -707,8 +697,8 @@ void DSoundCapture::stop()
HRESULT hr{mDSCbuffer->Stop()}; HRESULT hr{mDSCbuffer->Stop()};
if(FAILED(hr)) if(FAILED(hr))
{ {
ERR("stop failed: 0x%08lx\n", hr); ERR("stop failed: {:#x}", as_unsigned(hr));
mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr); mDevice->handleDisconnect("Failure stopping capture: {:#x}", as_unsigned(hr));
} }
} }
@ -745,8 +735,8 @@ uint DSoundCapture::availableSamples()
if(FAILED(hr)) if(FAILED(hr))
{ {
ERR("update failed: 0x%08lx\n", hr); ERR("update failed: {:#x}", as_unsigned(hr));
mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr); mDevice->handleDisconnect("Failure retrieving capture data: {:#x}", as_unsigned(hr));
} }
return static_cast<uint>(mRing->readSpace()); return static_cast<uint>(mRing->readSpace());
@ -763,13 +753,13 @@ BackendFactory &DSoundBackendFactory::getFactory()
bool DSoundBackendFactory::init() bool DSoundBackendFactory::init()
{ {
#ifdef HAVE_DYNLOAD #if HAVE_DYNLOAD
if(!ds_handle) if(!ds_handle)
{ {
ds_handle = LoadLib("dsound.dll"); ds_handle = LoadLib("dsound.dll");
if(!ds_handle) if(!ds_handle)
{ {
ERR("Failed to load dsound.dll\n"); ERR("Failed to load dsound.dll");
return false; return false;
} }
@ -808,7 +798,7 @@ auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector<std::strin
case BackendType::Playback: case BackendType::Playback:
PlaybackDevices.clear(); PlaybackDevices.clear();
if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr)) if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr))
ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr); ERR("Error enumerating DirectSound playback devices: {:#x}", as_unsigned(hr));
outnames.reserve(PlaybackDevices.size()); outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break; break;
@ -816,7 +806,7 @@ auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector<std::strin
case BackendType::Capture: case BackendType::Capture:
CaptureDevices.clear(); CaptureDevices.clear();
if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr)) if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr))
ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr); ERR("Error enumerating DirectSound capture devices: {:#x}", as_unsigned(hr));
outnames.reserve(CaptureDevices.size()); outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break; break;

View file

@ -29,18 +29,17 @@
#include <memory.h> #include <memory.h>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <functional>
#include <vector> #include <vector>
#include "alc/alconfig.h" #include "alc/alconfig.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alsem.h" #include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h" #include "althrd_setname.h"
#include "core/device.h" #include "core/device.h"
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "fmt/format.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include <jack/jack.h> #include <jack/jack.h>
@ -51,7 +50,7 @@ namespace {
using namespace std::string_view_literals; using namespace std::string_view_literals;
#ifdef HAVE_DYNLOAD #if HAVE_DYNLOAD
#define JACK_FUNCS(MAGIC) \ #define JACK_FUNCS(MAGIC) \
MAGIC(jack_client_open); \ MAGIC(jack_client_open); \
MAGIC(jack_client_close); \ MAGIC(jack_client_close); \
@ -108,10 +107,12 @@ jack_options_t ClientOptions = JackNullOption;
bool jack_load() bool jack_load()
{ {
#ifdef HAVE_DYNLOAD #if HAVE_DYNLOAD
if(!jack_handle) if(!jack_handle)
{ {
#ifdef _WIN32 #if defined(_WIN64)
#define JACKLIB "libjack64.dll"
#elif defined(_WIN32)
#define JACKLIB "libjack.dll" #define JACKLIB "libjack.dll"
#else #else
#define JACKLIB "libjack.so.0" #define JACKLIB "libjack.so.0"
@ -119,7 +120,7 @@ bool jack_load()
jack_handle = LoadLib(JACKLIB); jack_handle = LoadLib(JACKLIB);
if(!jack_handle) if(!jack_handle)
{ {
WARN("Failed to load %s\n", JACKLIB); WARN("Failed to load {}", JACKLIB);
return false; return false;
} }
@ -137,7 +138,7 @@ bool jack_load()
if(!missing_funcs.empty()) if(!missing_funcs.empty())
{ {
WARN("Missing expected functions:%s\n", missing_funcs.c_str()); WARN("Missing expected functions:{}", missing_funcs);
CloseLib(jack_handle); CloseLib(jack_handle);
jack_handle = nullptr; jack_handle = nullptr;
return false; return false;
@ -194,15 +195,15 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
continue; continue;
const auto &entry = list.emplace_back(portdev, std::string{portdev}+":"); const auto &entry = list.emplace_back(portdev, fmt::format("{}:", portdev));
TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); TRACE("Got device: {} = {}", entry.mName, entry.mPattern);
} }
/* There are ports but couldn't get device names from them. Add a /* There are ports but couldn't get device names from them. Add a
* generic entry. * generic entry.
*/ */
if(ports[0] && list.empty()) if(ports[0] && list.empty())
{ {
WARN("No device names found in available ports, adding a generic name.\n"); WARN("No device names found in available ports, adding a generic name.");
list.emplace_back("JACK"sv, ""sv); list.emplace_back("JACK"sv, ""sv);
} }
} }
@ -216,7 +217,7 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
if(seppos >= nextpos || seppos == strpos) if(seppos >= nextpos || seppos == strpos)
{ {
const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos); const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos);
ERR("Invalid device entry: \"%.*s\"\n", al::sizei(entry), entry.data()); ERR("Invalid device entry: \"{}\"", entry);
if(nextpos != std::string::npos) ++nextpos; if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos; strpos = nextpos;
continue; continue;
@ -234,14 +235,13 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
{ {
/* If so, replace the name with this custom one. */ /* If so, replace the name with this custom one. */
itemmatch->mName = name; itemmatch->mName = name;
TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(), TRACE("Customized device name: {} = {}", itemmatch->mName, itemmatch->mPattern);
itemmatch->mPattern.c_str());
} }
else else
{ {
/* Otherwise, add a new device entry. */ /* Otherwise, add a new device entry. */
const auto &entry = list.emplace_back(name, pattern); const auto &entry = list.emplace_back(name, pattern);
TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); TRACE("Got custom device: {} = {}", entry.mName, entry.mPattern);
} }
if(nextpos != std::string::npos) ++nextpos; if(nextpos != std::string::npos) ++nextpos;
@ -277,7 +277,7 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
struct JackPlayback final : public BackendBase { struct JackPlayback final : public BackendBase {
JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~JackPlayback() override; ~JackPlayback() override;
int processRt(jack_nframes_t numframes) noexcept; int processRt(jack_nframes_t numframes) noexcept;
@ -329,13 +329,13 @@ JackPlayback::~JackPlayback()
int JackPlayback::processRt(jack_nframes_t numframes) noexcept int JackPlayback::processRt(jack_nframes_t numframes) noexcept
{ {
auto outptrs = std::array<jack_default_audio_sample_t*,MaxOutputChannels>{}; auto outptrs = std::array<void*,MaxOutputChannels>{};
auto numchans = size_t{0}; auto numchans = size_t{0};
for(auto port : mPort) for(auto port : mPort)
{ {
if(!port || numchans == mDevice->RealOut.Buffer.size()) if(!port || numchans == mDevice->RealOut.Buffer.size())
break; break;
outptrs[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes)); outptrs[numchans++] = jack_port_get_buffer(port, numframes);
} }
const auto dst = al::span{outptrs}.first(numchans); const auto dst = al::span{outptrs}.first(numchans);
@ -343,8 +343,8 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept
mDevice->renderSamples(dst, static_cast<uint>(numframes)); mDevice->renderSamples(dst, static_cast<uint>(numframes));
else else
{ {
std::for_each(dst.begin(), dst.end(), [numframes](float *outbuf) -> void std::for_each(dst.begin(), dst.end(), [numframes](void *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); }); { std::fill_n(static_cast<float*>(outbuf), numframes, 0.0f); });
} }
return 0; return 0;
@ -365,13 +365,13 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
if(mPlaying.load(std::memory_order_acquire)) LIKELY if(mPlaying.load(std::memory_order_acquire)) LIKELY
{ {
auto data = mRing->getReadVector(); auto data = mRing->getReadVector();
const auto update_size = size_t{mDevice->UpdateSize}; const auto update_size = size_t{mDevice->mUpdateSize};
const auto outlen = size_t{numframes / update_size}; const auto outlen = size_t{numframes / update_size};
const auto len1 = size_t{std::min(data.first.len/update_size, outlen)}; const auto len1 = size_t{std::min(data[0].len/update_size, outlen)};
const auto len2 = size_t{std::min(data.second.len/update_size, outlen-len1)}; const auto len2 = size_t{std::min(data[1].len/update_size, outlen-len1)};
auto src = al::span{reinterpret_cast<float*>(data.first.buf), update_size*len1*numchans}; auto src = al::span{reinterpret_cast<float*>(data[0].buf), update_size*len1*numchans};
for(size_t i{0};i < len1;++i) for(size_t i{0};i < len1;++i)
{ {
for(size_t c{0};c < numchans;++c) for(size_t c{0};c < numchans;++c)
@ -383,7 +383,7 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
total += update_size; total += update_size;
} }
src = al::span{reinterpret_cast<float*>(data.second.buf), update_size*len2*numchans}; src = al::span{reinterpret_cast<float*>(data[1].buf), update_size*len2*numchans};
for(size_t i{0};i < len2;++i) for(size_t i{0};i < len2;++i)
{ {
for(size_t c{0};c < numchans;++c) for(size_t c{0};c < numchans;++c)
@ -414,9 +414,9 @@ int JackPlayback::mixerProc()
SetRTPriority(); SetRTPriority();
althrd_setname(GetMixerThreadName()); althrd_setname(GetMixerThreadName());
const auto update_size = uint{mDevice->UpdateSize}; const auto update_size = uint{mDevice->mUpdateSize};
const auto num_channels = size_t{mDevice->channelsFromFmt()}; const auto num_channels = size_t{mDevice->channelsFromFmt()};
auto outptrs = std::vector<float*>(num_channels); auto outptrs = std::vector<void*>(num_channels);
while(!mKillNow.load(std::memory_order_acquire) while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire)) && mDevice->Connected.load(std::memory_order_acquire))
@ -428,12 +428,11 @@ int JackPlayback::mixerProc()
} }
auto data = mRing->getWriteVector(); auto data = mRing->getWriteVector();
const auto len1 = size_t{data.first.len / update_size}; const auto len1 = size_t{data[0].len / update_size};
const auto len2 = size_t{data.second.len / update_size}; const auto len2 = size_t{data[1].len / update_size};
std::lock_guard<std::mutex> dlock{mMutex}; std::lock_guard<std::mutex> dlock{mMutex};
auto buffer = al::span{reinterpret_cast<float*>(data.first.buf), auto buffer = al::span{reinterpret_cast<float*>(data[0].buf), data[0].len*num_channels};
data.first.len*num_channels};
auto bufiter = buffer.begin(); auto bufiter = buffer.begin();
for(size_t i{0};i < len1;++i) for(size_t i{0};i < len1;++i)
{ {
@ -447,8 +446,7 @@ int JackPlayback::mixerProc()
} }
if(len2 > 0) if(len2 > 0)
{ {
buffer = al::span{reinterpret_cast<float*>(data.second.buf), buffer = al::span{reinterpret_cast<float*>(data[1].buf), data[1].len*num_channels};
data.second.len*num_channels};
bufiter = buffer.begin(); bufiter = buffer.begin();
for(size_t i{0};i < len2;++i) for(size_t i{0};i < len2;++i)
{ {
@ -479,13 +477,14 @@ void JackPlayback::open(std::string_view name)
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
if(mClient == nullptr) if(mClient == nullptr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to open client connection: 0x%02x", status}; "Failed to open client connection: {:#02x}",
as_unsigned(al::to_underlying(status))};
if((status&JackServerStarted)) if((status&JackServerStarted))
TRACE("JACK server started\n"); TRACE("JACK server started");
if((status&JackNameNotUnique)) if((status&JackNameNotUnique))
{ {
client_name = jack_get_client_name(mClient); client_name = jack_get_client_name(mClient);
TRACE("Client name not unique, got '%s' instead\n", client_name); TRACE("Client name not unique, got '{}' instead", client_name);
} }
} }
@ -504,11 +503,11 @@ void JackPlayback::open(std::string_view name)
auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name); auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
if(iter == PlaybackList.cend()) if(iter == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
mPortPattern = iter->mPattern; mPortPattern = iter->mPattern;
} }
mDevice->DeviceName = name; mDeviceName = name;
} }
bool JackPlayback::reset() bool JackPlayback::reset()
@ -518,28 +517,29 @@ bool JackPlayback::reset()
std::for_each(mPort.begin(), mPort.end(), unregister_port); std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(nullptr); mPort.fill(nullptr);
mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true); mRTMixing = GetConfigValueBool(mDevice->mDeviceName, "jack", "rt-mix", true);
jack_set_process_callback(mClient, jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
/* Ignore the requested buffer metrics and just keep one JACK-sized buffer /* Ignore the requested buffer metrics and just keep one JACK-sized buffer
* ready for when requested. * ready for when requested.
*/ */
mDevice->Frequency = jack_get_sample_rate(mClient); mDevice->mSampleRate = jack_get_sample_rate(mClient);
mDevice->UpdateSize = jack_get_buffer_size(mClient); mDevice->mUpdateSize = jack_get_buffer_size(mClient);
if(mRTMixing) if(mRTMixing)
{ {
/* Assume only two periods when directly mixing. Should try to query /* Assume only two periods when directly mixing. Should try to query
* the total port latency when connected. * the total port latency when connected.
*/ */
mDevice->BufferSize = mDevice->UpdateSize * 2; mDevice->mBufferSize = mDevice->mUpdateSize * 2;
} }
else else
{ {
const std::string_view devname{mDevice->DeviceName}; const auto devname = std::string_view{mDevice->mDeviceName};
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; auto bufsize = ConfigValueUInt(devname, "jack", "buffer-size")
bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); .value_or(mDevice->mUpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize; bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize);
mDevice->mBufferSize = bufsize + mDevice->mUpdateSize;
} }
/* Force 32-bit float output. */ /* Force 32-bit float output. */
@ -558,7 +558,7 @@ bool JackPlayback::reset()
} }
if(bad_port != ports.end()) if(bad_port != ports.end())
{ {
ERR("Failed to register enough JACK ports for %s output\n", ERR("Failed to register enough JACK ports for {} output",
DevFmtChannelsString(mDevice->FmtChans)); DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == ports.begin()) return false; if(bad_port == ports.begin()) return false;
@ -586,7 +586,7 @@ void JackPlayback::start()
if(jack_activate(mClient)) if(jack_activate(mClient))
throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"}; throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
const std::string_view devname{mDevice->DeviceName}; const auto devname = std::string_view{mDevice->mDeviceName};
if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
{ {
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE,
@ -601,11 +601,11 @@ void JackPlayback::start()
{ {
if(!pnames[i]) if(!pnames[i])
{ {
ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i])); ERR("No physical playback port for \"{}\"", jack_port_name(mPort[i]));
break; break;
} }
if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i])) if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i]))
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]), ERR("Failed to connect output port \"{}\" to \"{}\"", jack_port_name(mPort[i]),
pnames[i]); pnames[i]);
} }
} }
@ -614,31 +614,32 @@ void JackPlayback::start()
* (it won't change again after jack_activate), then allocate the ring * (it won't change again after jack_activate), then allocate the ring
* buffer with the appropriate size. * buffer with the appropriate size.
*/ */
mDevice->Frequency = jack_get_sample_rate(mClient); mDevice->mSampleRate = jack_get_sample_rate(mClient);
mDevice->UpdateSize = jack_get_buffer_size(mClient); mDevice->mUpdateSize = jack_get_buffer_size(mClient);
mDevice->BufferSize = mDevice->UpdateSize * 2; mDevice->mBufferSize = mDevice->mUpdateSize * 2;
mRing = nullptr; mRing = nullptr;
if(mRTMixing) if(mRTMixing)
mPlaying.store(true, std::memory_order_release); mPlaying.store(true, std::memory_order_release);
else else
{ {
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size")
bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); .value_or(mDevice->mUpdateSize)};
mDevice->BufferSize = bufsize + mDevice->UpdateSize; bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize);
mDevice->mBufferSize = bufsize + mDevice->mUpdateSize;
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
try { try {
mPlaying.store(true, std::memory_order_release); mPlaying.store(true, std::memory_order_release);
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; mThread = std::thread{&JackPlayback::mixerProc, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
jack_deactivate(mClient); jack_deactivate(mClient);
mPlaying.store(false, std::memory_order_release); mPlaying.store(false, std::memory_order_release);
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()}; "Failed to start mixing thread: {}", e.what()};
} }
} }
} }
@ -665,8 +666,8 @@ ClockLatency JackPlayback::getClockLatency()
std::lock_guard<std::mutex> dlock{mMutex}; std::lock_guard<std::mutex> dlock{mMutex};
ClockLatency ret{}; ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime(); ret.ClockTime = mDevice->getClockTime();
ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize}; ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->mUpdateSize};
ret.Latency /= mDevice->Frequency; ret.Latency /= mDevice->mSampleRate;
return ret; return ret;
} }
@ -674,7 +675,7 @@ ClockLatency JackPlayback::getClockLatency()
void jack_msg_handler(const char *message) void jack_msg_handler(const char *message)
{ {
WARN("%s\n", message); WARN("{}", message);
} }
} // namespace } // namespace
@ -697,9 +698,9 @@ bool JackBackendFactory::init()
jack_set_error_function(old_error_cb); jack_set_error_function(old_error_cb);
if(!client) if(!client)
{ {
WARN("jack_client_open() failed, 0x%02x\n", status); WARN("jack_client_open() failed, {:#02x}", as_unsigned(al::to_underlying(status)));
if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer)) if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
ERR("Unable to connect to JACK server\n"); ERR("Unable to connect to JACK server");
return false; return false;
} }
@ -728,7 +729,7 @@ auto JackBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
jack_client_close(client); jack_client_close(client);
} }
else else
WARN("jack_client_open() failed, 0x%02x\n", status); WARN("jack_client_open() failed, {:#02x}", as_unsigned(al::to_underlying(status)));
outnames.reserve(PlaybackList.size()); outnames.reserve(PlaybackList.size());
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break; break;

View file

@ -28,7 +28,7 @@
namespace { namespace {
struct LoopbackBackend final : public BackendBase { struct LoopbackBackend final : public BackendBase {
LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } explicit LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
void open(std::string_view name) override; void open(std::string_view name) override;
bool reset() override; bool reset() override;
@ -39,7 +39,7 @@ struct LoopbackBackend final : public BackendBase {
void LoopbackBackend::open(std::string_view name) void LoopbackBackend::open(std::string_view name)
{ {
mDevice->DeviceName = name; mDeviceName = name;
} }
bool LoopbackBackend::reset() bool LoopbackBackend::reset()

View file

@ -27,11 +27,8 @@
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <functional>
#include <thread> #include <thread>
#include "almalloc.h"
#include "alstring.h"
#include "althrd_setname.h" #include "althrd_setname.h"
#include "core/device.h" #include "core/device.h"
#include "core/helpers.h" #include "core/helpers.h"
@ -48,7 +45,7 @@ using namespace std::string_view_literals;
struct NullBackend final : public BackendBase { struct NullBackend final : public BackendBase {
NullBackend(DeviceBase *device) noexcept : BackendBase{device} { } explicit NullBackend(DeviceBase *device) noexcept : BackendBase{device} { }
int mixerProc(); int mixerProc();
@ -63,7 +60,7 @@ struct NullBackend final : public BackendBase {
int NullBackend::mixerProc() int NullBackend::mixerProc()
{ {
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; const milliseconds restTime{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2};
SetRTPriority(); SetRTPriority();
althrd_setname(GetMixerThreadName()); althrd_setname(GetMixerThreadName());
@ -76,16 +73,17 @@ int NullBackend::mixerProc()
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
/* This converts from nanoseconds to nanosamples, then to samples. */ /* This converts from nanoseconds to nanosamples, then to samples. */
int64_t avail{std::chrono::duration_cast<seconds>((now-start) * mDevice->Frequency).count()}; const auto avail = int64_t{std::chrono::duration_cast<seconds>((now-start)
if(avail-done < mDevice->UpdateSize) * mDevice->mSampleRate).count()};
if(avail-done < mDevice->mUpdateSize)
{ {
std::this_thread::sleep_for(restTime); std::this_thread::sleep_for(restTime);
continue; continue;
} }
while(avail-done >= mDevice->UpdateSize) while(avail-done >= mDevice->mUpdateSize)
{ {
mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u); mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u);
done += mDevice->UpdateSize; done += mDevice->mUpdateSize;
} }
/* For every completed second, increment the start time and reduce the /* For every completed second, increment the start time and reduce the
@ -93,11 +91,11 @@ int NullBackend::mixerProc()
* and current time from growing too large, while maintaining the * and current time from growing too large, while maintaining the
* correct number of samples to render. * correct number of samples to render.
*/ */
if(done >= mDevice->Frequency) if(done >= mDevice->mSampleRate)
{ {
seconds s{done/mDevice->Frequency}; seconds s{done/mDevice->mSampleRate};
start += s; start += s;
done -= mDevice->Frequency*s.count(); done -= mDevice->mSampleRate*s.count();
} }
} }
@ -110,10 +108,10 @@ void NullBackend::open(std::string_view name)
if(name.empty()) if(name.empty())
name = GetDeviceName(); name = GetDeviceName();
else if(name != GetDeviceName()) else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
mDevice->DeviceName = name; mDeviceName = name;
} }
bool NullBackend::reset() bool NullBackend::reset()
@ -126,11 +124,11 @@ void NullBackend::start()
{ {
try { try {
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this}; mThread = std::thread{&NullBackend::mixerProc, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()}; "Failed to start mixing thread: {}", e.what()};
} }
} }

View file

@ -24,7 +24,7 @@ using namespace std::string_view_literals;
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
OboePlayback(DeviceBase *device) : BackendBase{device} { } explicit OboePlayback(DeviceBase *device) : BackendBase{device} { }
oboe::ManagedStream mStream; oboe::ManagedStream mStream;
@ -54,8 +54,9 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea
void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error) void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error)
{ {
if(error == oboe::Result::ErrorDisconnected) if(error == oboe::Result::ErrorDisconnected)
mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error)); mDevice->handleDisconnect("Oboe AudioStream was disconnected: {}",
TRACE("Error was %s", oboe::convertToText(error)); oboe::convertToText(error));
TRACE("Error was {}", oboe::convertToText(error));
} }
void OboePlayback::open(std::string_view name) void OboePlayback::open(std::string_view name)
@ -63,8 +64,8 @@ void OboePlayback::open(std::string_view name)
if(name.empty()) if(name.empty())
name = GetDeviceName(); name = GetDeviceName();
else if(name != GetDeviceName()) else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
/* Open a basic output stream, just to ensure it can work. */ /* Open a basic output stream, just to ensure it can work. */
oboe::ManagedStream stream; oboe::ManagedStream stream;
@ -72,10 +73,10 @@ void OboePlayback::open(std::string_view name)
->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->openManagedStream(stream)}; ->openManagedStream(stream)};
if(result != oboe::Result::OK) if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}",
oboe::convertToText(result)}; oboe::convertToText(result)};
mDevice->DeviceName = name; mDeviceName = name;
} }
bool OboePlayback::reset() bool OboePlayback::reset()
@ -95,7 +96,7 @@ bool OboePlayback::reset()
if(mDevice->Flags.test(FrequencyRequest)) if(mDevice->Flags.test(FrequencyRequest))
{ {
builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High); builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High);
builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency)); builder.setSampleRate(static_cast<int32_t>(mDevice->mSampleRate));
} }
if(mDevice->Flags.test(ChannelsRequest)) if(mDevice->Flags.test(ChannelsRequest))
{ {
@ -145,11 +146,11 @@ bool OboePlayback::reset()
result = builder.openManagedStream(mStream); result = builder.openManagedStream(mStream);
} }
if(result != oboe::Result::OK) if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}",
oboe::convertToText(result)}; oboe::convertToText(result)};
mStream->setBufferSizeInFrames(std::min(static_cast<int32_t>(mDevice->BufferSize), mStream->setBufferSizeInFrames(std::min(static_cast<int32_t>(mDevice->mBufferSize),
mStream->getBufferCapacityInFrames())); mStream->getBufferCapacityInFrames()));
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get()));
if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt()) if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
{ {
@ -159,7 +160,7 @@ bool OboePlayback::reset()
mDevice->FmtChans = DevFmtMono; mDevice->FmtChans = DevFmtMono;
else else
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Got unhandled channel count: %d", mStream->getChannelCount()}; "Got unhandled channel count: {}", mStream->getChannelCount()};
} }
setDefaultWFXChannelOrder(); setDefaultWFXChannelOrder();
@ -183,18 +184,18 @@ bool OboePlayback::reset()
case oboe::AudioFormat::Unspecified: case oboe::AudioFormat::Unspecified:
case oboe::AudioFormat::Invalid: case oboe::AudioFormat::Invalid:
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())}; "Got unhandled sample type: {}", oboe::convertToText(mStream->getFormat())};
} }
mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate()); mDevice->mSampleRate = static_cast<uint32_t>(mStream->getSampleRate());
/* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0 /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
* indicating variable updates, but OpenAL should have a reasonable minimum update size set. * indicating variable updates, but OpenAL should have a reasonable minimum update size set.
* FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
* update size. * update size.
*/ */
mDevice->UpdateSize = std::max(mDevice->Frequency/100u, mDevice->mUpdateSize = std::max(mDevice->mSampleRate/100u,
static_cast<uint32_t>(mStream->getFramesPerBurst())); static_cast<uint32_t>(mStream->getFramesPerBurst()));
mDevice->BufferSize = std::max(mDevice->UpdateSize*2u, mDevice->mBufferSize = std::max(mDevice->mUpdateSize*2u,
static_cast<uint32_t>(mStream->getBufferSizeInFrames())); static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
return true; return true;
@ -204,7 +205,7 @@ void OboePlayback::start()
{ {
const oboe::Result result{mStream->start()}; const oboe::Result result{mStream->start()};
if(result != oboe::Result::OK) if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}",
oboe::convertToText(result)}; oboe::convertToText(result)};
} }
@ -212,12 +213,12 @@ void OboePlayback::stop()
{ {
oboe::Result result{mStream->stop()}; oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK) if(result != oboe::Result::OK)
ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); ERR("Failed to stop stream: {}", oboe::convertToText(result));
} }
struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback { struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback {
OboeCapture(DeviceBase *device) : BackendBase{device} { } explicit OboeCapture(DeviceBase *device) : BackendBase{device} { }
oboe::ManagedStream mStream; oboe::ManagedStream mStream;
@ -246,8 +247,8 @@ void OboeCapture::open(std::string_view name)
if(name.empty()) if(name.empty())
name = GetDeviceName(); name = GetDeviceName();
else if(name != GetDeviceName()) else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
oboe::AudioStreamBuilder builder; oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Input) builder.setDirection(oboe::Direction::Input)
@ -255,7 +256,7 @@ void OboeCapture::open(std::string_view name)
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
->setChannelConversionAllowed(true) ->setChannelConversionAllowed(true)
->setFormatConversionAllowed(true) ->setFormatConversionAllowed(true)
->setSampleRate(static_cast<int32_t>(mDevice->Frequency)) ->setSampleRate(static_cast<int32_t>(mDevice->mSampleRate))
->setCallback(this); ->setCallback(this);
/* Only use mono or stereo at user request. There's no telling what /* Only use mono or stereo at user request. There's no telling what
* other counts may be inferred as. * other counts may be inferred as.
@ -276,7 +277,7 @@ void OboeCapture::open(std::string_view name)
case DevFmtX7144: case DevFmtX7144:
case DevFmtX3D71: case DevFmtX3D71:
case DevFmtAmbi3D: case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported",
DevFmtChannelsString(mDevice->FmtChans)}; DevFmtChannelsString(mDevice->FmtChans)};
} }
@ -301,28 +302,28 @@ void OboeCapture::open(std::string_view name)
case DevFmtUShort: case DevFmtUShort:
case DevFmtUInt: case DevFmtUInt:
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
} }
oboe::Result result{builder.openManagedStream(mStream)}; oboe::Result result{builder.openManagedStream(mStream)};
if(result != oboe::Result::OK) if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}",
oboe::convertToText(result)}; oboe::convertToText(result)};
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get()));
/* Ensure a minimum ringbuffer size of 100ms. */ /* Ensure a minimum ringbuffer size of 100ms. */
mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u), mRing = RingBuffer::Create(std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u),
static_cast<uint32_t>(mStream->getBytesPerFrame()), false); static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
mDevice->DeviceName = name; mDeviceName = name;
} }
void OboeCapture::start() void OboeCapture::start()
{ {
const oboe::Result result{mStream->start()}; const oboe::Result result{mStream->start()};
if(result != oboe::Result::OK) if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}",
oboe::convertToText(result)}; oboe::convertToText(result)};
} }
@ -330,7 +331,7 @@ void OboeCapture::stop()
{ {
const oboe::Result result{mStream->stop()}; const oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK) if(result != oboe::Result::OK)
ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); ERR("Failed to stop stream: {}", oboe::convertToText(result));
} }
uint OboeCapture::availableSamples() uint OboeCapture::availableSamples()

View file

@ -41,6 +41,7 @@
#include "core/device.h" #include "core/device.h"
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "ringbuffer.h" #include "ringbuffer.h"
@ -53,6 +54,32 @@ namespace {
using namespace std::string_view_literals; using namespace std::string_view_literals;
#if HAVE_DYNLOAD
#define SLES_SYMBOLS(MAGIC) \
MAGIC(slCreateEngine); \
MAGIC(SL_IID_ANDROIDCONFIGURATION); \
MAGIC(SL_IID_ANDROIDSIMPLEBUFFERQUEUE); \
MAGIC(SL_IID_ENGINE); \
MAGIC(SL_IID_PLAY); \
MAGIC(SL_IID_RECORD);
void *sles_handle;
#define MAKE_SYMBOL(f) decltype(f) * p##f
SLES_SYMBOLS(MAKE_SYMBOL)
#undef MAKE_SYMBOL
#ifndef IN_IDE_PARSER
#define slCreateEngine (*pslCreateEngine)
#define SL_IID_ANDROIDCONFIGURATION (*pSL_IID_ANDROIDCONFIGURATION)
#define SL_IID_ANDROIDSIMPLEBUFFERQUEUE (*pSL_IID_ANDROIDSIMPLEBUFFERQUEUE)
#define SL_IID_ENGINE (*pSL_IID_ENGINE)
#define SL_IID_PLAY (*pSL_IID_PLAY)
#define SL_IID_RECORD (*pSL_IID_RECORD)
#endif
#endif
/* Helper macros */ /* Helper macros */
#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__)) #define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS #define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
@ -156,12 +183,12 @@ constexpr const char *res_str(SLresult result) noexcept
inline void PrintErr(SLresult res, const char *str) inline void PrintErr(SLresult res, const char *str)
{ {
if(res != SL_RESULT_SUCCESS) UNLIKELY if(res != SL_RESULT_SUCCESS) UNLIKELY
ERR("%s: %s\n", str, res_str(res)); ERR("{}: {}", str, res_str(res));
} }
struct OpenSLPlayback final : public BackendBase { struct OpenSLPlayback final : public BackendBase {
OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OpenSLPlayback() override; ~OpenSLPlayback() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept; void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
@ -247,7 +274,7 @@ int OpenSLPlayback::mixerProc()
const size_t frame_step{mDevice->channelsFromFmt()}; const size_t frame_step{mDevice->channelsFromFmt()};
if(SL_RESULT_SUCCESS != result) if(SL_RESULT_SUCCESS != result)
mDevice->handleDisconnect("Failed to get playback buffer: 0x%08x", result); mDevice->handleDisconnect("Failed to get playback buffer: {:#08x}", result);
while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire)) && mDevice->Connected.load(std::memory_order_acquire))
@ -265,7 +292,7 @@ int OpenSLPlayback::mixerProc()
} }
if(SL_RESULT_SUCCESS != result) if(SL_RESULT_SUCCESS != result)
{ {
mDevice->handleDisconnect("Failed to start playback: 0x%08x", result); mDevice->handleDisconnect("Failed to start playback: {:#08x}", result);
break; break;
} }
@ -278,35 +305,35 @@ int OpenSLPlayback::mixerProc()
std::unique_lock<std::mutex> dlock{mMutex}; std::unique_lock<std::mutex> dlock{mMutex};
auto data = mRing->getWriteVector(); auto data = mRing->getWriteVector();
mDevice->renderSamples(data.first.buf, mDevice->renderSamples(data[0].buf,
static_cast<uint>(data.first.len)*mDevice->UpdateSize, frame_step); static_cast<uint>(data[0].len)*mDevice->mUpdateSize, frame_step);
if(data.second.len > 0) if(data[1].len > 0)
mDevice->renderSamples(data.second.buf, mDevice->renderSamples(data[1].buf,
static_cast<uint>(data.second.len)*mDevice->UpdateSize, frame_step); static_cast<uint>(data[1].len)*mDevice->mUpdateSize, frame_step);
size_t todo{data.first.len + data.second.len}; const auto todo = size_t{data[0].len + data[1].len};
mRing->writeAdvance(todo); mRing->writeAdvance(todo);
dlock.unlock(); dlock.unlock();
for(size_t i{0};i < todo;i++) for(size_t i{0};i < todo;i++)
{ {
if(!data.first.len) if(!data[0].len)
{ {
data.first = data.second; data[0] = data[1];
data.second.buf = nullptr; data[1].buf = nullptr;
data.second.len = 0; data[1].len = 0;
} }
result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize); result = VCALL(bufferQueue,Enqueue)(data[0].buf, mDevice->mUpdateSize*mFrameSize);
PrintErr(result, "bufferQueue->Enqueue"); PrintErr(result, "bufferQueue->Enqueue");
if(SL_RESULT_SUCCESS != result) if(SL_RESULT_SUCCESS != result)
{ {
mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result); mDevice->handleDisconnect("Failed to queue audio: {:#08x}", result);
break; break;
} }
data.first.len--; data[0].len--;
data.first.buf += mDevice->UpdateSize*mFrameSize; data[0].buf += mDevice->mUpdateSize*mFrameSize;
} }
} }
@ -319,8 +346,8 @@ void OpenSLPlayback::open(std::string_view name)
if(name.empty()) if(name.empty())
name = GetDeviceName(); name = GetDeviceName();
else if(name != GetDeviceName()) else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
/* There's only one device, so if it's already open, there's nothing to do. */ /* There's only one device, so if it's already open, there's nothing to do. */
if(mEngineObj) return; if(mEngineObj) return;
@ -361,10 +388,10 @@ void OpenSLPlayback::open(std::string_view name)
mEngine = nullptr; mEngine = nullptr;
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to initialize OpenSL device: 0x%08x", result}; "Failed to initialize OpenSL device: {:#08x}", result};
} }
mDevice->DeviceName = name; mDeviceName = name;
} }
bool OpenSLPlayback::reset() bool OpenSLPlayback::reset()
@ -397,14 +424,14 @@ bool OpenSLPlayback::reset()
SLDataLocator_AndroidSimpleBufferQueue loc_bufq{}; SLDataLocator_AndroidSimpleBufferQueue loc_bufq{};
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; loc_bufq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize;
SLDataSource audioSrc{}; SLDataSource audioSrc{};
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX #ifdef SL_ANDROID_DATAFORMAT_PCM_EX
SLAndroidDataFormat_PCM_EX format_pcm_ex{}; SLAndroidDataFormat_PCM_EX format_pcm_ex{};
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
format_pcm_ex.numChannels = mDevice->channelsFromFmt(); format_pcm_ex.numChannels = mDevice->channelsFromFmt();
format_pcm_ex.sampleRate = mDevice->Frequency * 1000; format_pcm_ex.sampleRate = mDevice->mSampleRate * 1000;
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample; format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans); format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
@ -435,7 +462,7 @@ bool OpenSLPlayback::reset()
SLDataFormat_PCM format_pcm{}; SLDataFormat_PCM format_pcm{};
format_pcm.formatType = SL_DATAFORMAT_PCM; format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = mDevice->channelsFromFmt(); format_pcm.numChannels = mDevice->channelsFromFmt();
format_pcm.samplesPerSec = mDevice->Frequency * 1000; format_pcm.samplesPerSec = mDevice->mSampleRate * 1000;
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample; format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
@ -472,8 +499,8 @@ bool OpenSLPlayback::reset()
} }
if(SL_RESULT_SUCCESS == result) if(SL_RESULT_SUCCESS == result)
{ {
const uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; const uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize};
mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true); mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->mUpdateSize, true);
} }
if(SL_RESULT_SUCCESS != result) if(SL_RESULT_SUCCESS != result)
@ -505,15 +532,15 @@ void OpenSLPlayback::start()
} }
if(SL_RESULT_SUCCESS != result) if(SL_RESULT_SUCCESS != result)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to register callback: 0x%08x", result}; "Failed to register callback: {:#08x}", result};
try { try {
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this); mThread = std::thread(&OpenSLPlayback::mixerProc, this);
} }
catch(std::exception& e) { catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()}; "Failed to start mixing thread: {}", e.what()};
} }
} }
@ -566,15 +593,15 @@ ClockLatency OpenSLPlayback::getClockLatency()
std::lock_guard<std::mutex> dlock{mMutex}; std::lock_guard<std::mutex> dlock{mMutex};
ret.ClockTime = mDevice->getClockTime(); ret.ClockTime = mDevice->getClockTime();
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize}; ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->mUpdateSize};
ret.Latency /= mDevice->Frequency; ret.Latency /= mDevice->mSampleRate;
return ret; return ret;
} }
struct OpenSLCapture final : public BackendBase { struct OpenSLCapture final : public BackendBase {
OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { } explicit OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~OpenSLCapture() override; ~OpenSLCapture() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept; void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
@ -623,8 +650,8 @@ void OpenSLCapture::open(std::string_view name)
if(name.empty()) if(name.empty())
name = GetDeviceName(); name = GetDeviceName();
else if(name != GetDeviceName()) else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PrintErr(result, "slCreateEngine"); PrintErr(result, "slCreateEngine");
@ -642,16 +669,16 @@ void OpenSLCapture::open(std::string_view name)
{ {
mFrameSize = mDevice->frameSizeFromFmt(); mFrameSize = mDevice->frameSizeFromFmt();
/* Ensure the total length is at least 100ms */ /* Ensure the total length is at least 100ms */
uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; uint length{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)};
/* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u, uint update_len{std::clamp(mDevice->mBufferSize/3u, mDevice->mSampleRate/100u,
mDevice->Frequency/100u*5u)}; mDevice->mSampleRate/100u*5u)};
uint num_updates{(length+update_len-1) / update_len}; uint num_updates{(length+update_len-1) / update_len};
mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
mDevice->UpdateSize = update_len; mDevice->mUpdateSize = update_len;
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace() * update_len); mDevice->mBufferSize = static_cast<uint>(mRing->writeSpace() * update_len);
} }
if(SL_RESULT_SUCCESS == result) if(SL_RESULT_SUCCESS == result)
{ {
@ -670,14 +697,14 @@ void OpenSLCapture::open(std::string_view name)
SLDataLocator_AndroidSimpleBufferQueue loc_bq{}; SLDataLocator_AndroidSimpleBufferQueue loc_bq{};
loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; loc_bq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize;
SLDataSink audioSnk{}; SLDataSink audioSnk{};
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX #ifdef SL_ANDROID_DATAFORMAT_PCM_EX
SLAndroidDataFormat_PCM_EX format_pcm_ex{}; SLAndroidDataFormat_PCM_EX format_pcm_ex{};
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
format_pcm_ex.numChannels = mDevice->channelsFromFmt(); format_pcm_ex.numChannels = mDevice->channelsFromFmt();
format_pcm_ex.sampleRate = mDevice->Frequency * 1000; format_pcm_ex.sampleRate = mDevice->mSampleRate * 1000;
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample; format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans); format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
@ -700,7 +727,7 @@ void OpenSLCapture::open(std::string_view name)
SLDataFormat_PCM format_pcm{}; SLDataFormat_PCM format_pcm{};
format_pcm.formatType = SL_DATAFORMAT_PCM; format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = mDevice->channelsFromFmt(); format_pcm.numChannels = mDevice->channelsFromFmt();
format_pcm.samplesPerSec = mDevice->Frequency * 1000; format_pcm.samplesPerSec = mDevice->mSampleRate * 1000;
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample; format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
@ -752,20 +779,20 @@ void OpenSLCapture::open(std::string_view name)
} }
if(SL_RESULT_SUCCESS == result) if(SL_RESULT_SUCCESS == result)
{ {
const uint chunk_size{mDevice->UpdateSize * mFrameSize}; const uint chunk_size{mDevice->mUpdateSize * mFrameSize};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0}; const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0};
auto data = mRing->getWriteVector(); auto data = mRing->getWriteVector();
std::fill_n(data.first.buf, data.first.len*chunk_size, silence); std::fill_n(data[0].buf, data[0].len*chunk_size, silence);
std::fill_n(data.second.buf, data.second.len*chunk_size, silence); std::fill_n(data[1].buf, data[1].len*chunk_size, silence);
for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++) for(size_t i{0u};i < data[0].len && SL_RESULT_SUCCESS == result;i++)
{ {
result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size); result = VCALL(bufferQueue,Enqueue)(data[0].buf + chunk_size*i, chunk_size);
PrintErr(result, "bufferQueue->Enqueue"); PrintErr(result, "bufferQueue->Enqueue");
} }
for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++) for(size_t i{0u};i < data[1].len && SL_RESULT_SUCCESS == result;i++)
{ {
result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size); result = VCALL(bufferQueue,Enqueue)(data[1].buf + chunk_size*i, chunk_size);
PrintErr(result, "bufferQueue->Enqueue"); PrintErr(result, "bufferQueue->Enqueue");
} }
} }
@ -782,10 +809,10 @@ void OpenSLCapture::open(std::string_view name)
mEngine = nullptr; mEngine = nullptr;
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to initialize OpenSL device: 0x%08x", result}; "Failed to initialize OpenSL device: {:#08x}", result};
} }
mDevice->DeviceName = name; mDeviceName = name;
} }
void OpenSLCapture::start() void OpenSLCapture::start()
@ -801,7 +828,7 @@ void OpenSLCapture::start()
} }
if(SL_RESULT_SUCCESS != result) if(SL_RESULT_SUCCESS != result)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start capture: 0x%08x", result}; "Failed to start capture: {:#08x}", result};
} }
void OpenSLCapture::stop() void OpenSLCapture::stop()
@ -819,7 +846,7 @@ void OpenSLCapture::stop()
void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
{ {
const uint update_size{mDevice->UpdateSize}; const uint update_size{mDevice->mUpdateSize};
const uint chunk_size{update_size * mFrameSize}; const uint chunk_size{update_size * mFrameSize};
/* Read the desired samples from the ring buffer then advance its read /* Read the desired samples from the ring buffer then advance its read
@ -830,7 +857,7 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
for(uint i{0};i < samples;) for(uint i{0};i < samples;)
{ {
const uint rem{std::min(samples - i, update_size - mSplOffset)}; const uint rem{std::min(samples - i, update_size - mSplOffset)};
std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, std::copy_n(rdata[0].buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
buffer + i*size_t{mFrameSize}); buffer + i*size_t{mFrameSize});
mSplOffset += rem; mSplOffset += rem;
@ -840,11 +867,11 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
mSplOffset = 0; mSplOffset = 0;
++adv_count; ++adv_count;
rdata.first.len -= 1; rdata[0].len -= 1;
if(!rdata.first.len) if(!rdata[0].len)
rdata.first = rdata.second; rdata[0] = rdata[1];
else else
rdata.first.buf += chunk_size; rdata[0].buf += chunk_size;
} }
i += rem; i += rem;
@ -858,7 +885,7 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
PrintErr(result, "recordObj->GetInterface"); PrintErr(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS != result) UNLIKELY if(SL_RESULT_SUCCESS != result) UNLIKELY
{ {
mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result); mDevice->handleDisconnect("Failed to get capture buffer queue: {:#08x}", result);
bufferQueue = nullptr; bufferQueue = nullptr;
} }
} }
@ -877,20 +904,20 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
SLresult result{SL_RESULT_SUCCESS}; SLresult result{SL_RESULT_SUCCESS};
auto wdata = mRing->getWriteVector(); auto wdata = mRing->getWriteVector();
if(adv_count > wdata.second.len) LIKELY if(adv_count > wdata[1].len) LIKELY
{ {
auto len1 = std::min(wdata.first.len, adv_count-wdata.second.len); auto len1 = std::min(wdata[0].len, adv_count-wdata[1].len);
auto buf1 = wdata.first.buf + chunk_size*(wdata.first.len-len1); auto buf1 = wdata[0].buf + chunk_size*(wdata[0].len-len1);
for(size_t i{0u};i < len1 && SL_RESULT_SUCCESS == result;i++) for(size_t i{0u};i < len1 && SL_RESULT_SUCCESS == result;i++)
{ {
result = VCALL(bufferQueue,Enqueue)(buf1 + chunk_size*i, chunk_size); result = VCALL(bufferQueue,Enqueue)(buf1 + chunk_size*i, chunk_size);
PrintErr(result, "bufferQueue->Enqueue"); PrintErr(result, "bufferQueue->Enqueue");
} }
} }
if(wdata.second.len > 0) if(wdata[1].len > 0)
{ {
auto len2 = std::min(wdata.second.len, adv_count); auto len2 = std::min(wdata[1].len, adv_count);
auto buf2 = wdata.second.buf + chunk_size*(wdata.second.len-len2); auto buf2 = wdata[1].buf + chunk_size*(wdata[1].len-len2);
for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++) for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++)
{ {
result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size); result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size);
@ -900,11 +927,43 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
} }
uint OpenSLCapture::availableSamples() uint OpenSLCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); } { return static_cast<uint>(mRing->readSpace()*mDevice->mUpdateSize - mSplOffset); }
} // namespace } // namespace
bool OSLBackendFactory::init() { return true; } bool OSLBackendFactory::init()
{
#if HAVE_DYNLOAD
if(!sles_handle)
{
#define SLES_LIBNAME "libOpenSLES.so"
sles_handle = LoadLib(SLES_LIBNAME);
if(!sles_handle)
{
WARN("Failed to load {}", SLES_LIBNAME);
return false;
}
std::string missing_syms;
#define LOAD_SYMBOL(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(sles_handle, #f)); \
if(p##f == nullptr) missing_syms += "\n" #f; \
} while(0)
SLES_SYMBOLS(LOAD_SYMBOL);
#undef LOAD_SYMBOL
if(!missing_syms.empty())
{
WARN("Missing expected symbols:{}", missing_syms);
CloseLib(sles_handle);
sles_handle = nullptr;
return false;
}
}
#endif
return true;
}
bool OSLBackendFactory::querySupport(BackendType type) bool OSLBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); } { return (type == BackendType::Playback || type == BackendType::Capture); }

View file

@ -33,7 +33,6 @@
#include <cerrno> #include <cerrno>
#include <cstring> #include <cstring>
#include <exception> #include <exception>
#include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include <string_view> #include <string_view>
@ -43,13 +42,12 @@
#include <vector> #include <vector>
#include "alc/alconfig.h" #include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alstring.h"
#include "althrd_setname.h" #include "althrd_setname.h"
#include "core/device.h" #include "core/device.h"
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "fmt/core.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include <sys/soundcard.h> #include <sys/soundcard.h>
@ -169,18 +167,13 @@ void ALCossListAppend(std::vector<DevMap> &list, std::string_view handle, std::s
auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; }; auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; };
return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
}; };
int count{1}; auto count = 1;
std::string newname{handle}; auto newname = std::string{handle};
while(checkName(newname)) while(checkName(newname))
{ newname = fmt::format("{} #{}", handle, ++count);
newname = handle;
newname += " #";
newname += std::to_string(++count);
}
const DevMap &entry = list.emplace_back(std::move(newname), path); const auto &entry = list.emplace_back(std::move(newname), path);
TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name);
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
} }
void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag) void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
@ -189,13 +182,13 @@ void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
FileHandle file; FileHandle file;
if(!file.open("/dev/mixer", O_RDONLY)) if(!file.open("/dev/mixer", O_RDONLY))
{ {
TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str()); TRACE("Could not open /dev/mixer: {}", std::generic_category().message(errno));
goto done; goto done;
} }
if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1) if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1)
{ {
TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno).c_str()); TRACE("SNDCTL_SYSINFO failed: {}", std::generic_category().message(errno));
goto done; goto done;
} }
@ -205,8 +198,7 @@ void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
ai.dev = i; ai.dev = i;
if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1) if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1)
{ {
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, ERR("SNDCTL_AUDIOINFO ({}) failed: {}", i, std::generic_category().message(errno));
std::generic_category().message(errno).c_str());
continue; continue;
} }
if(!(ai.caps&type_flag) || ai.devnode[0] == '\0') if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
@ -256,7 +248,7 @@ uint log2i(uint x)
struct OSSPlayback final : public BackendBase { struct OSSPlayback final : public BackendBase {
OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OSSPlayback() override; ~OSSPlayback() override;
int mixerProc(); int mixerProc();
@ -302,13 +294,13 @@ int OSSPlayback::mixerProc()
if(errno == EINTR || errno == EAGAIN) if(errno == EINTR || errno == EAGAIN)
continue; continue;
const auto errstr = std::generic_category().message(errno); const auto errstr = std::generic_category().message(errno);
ERR("poll failed: %s\n", errstr.c_str()); ERR("poll failed: {}", errstr);
mDevice->handleDisconnect("Failed waiting for playback buffer: %s", errstr.c_str()); mDevice->handleDisconnect("Failed waiting for playback buffer: {}", errstr);
break; break;
} }
else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */ else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
{ {
WARN("poll timeout\n"); WARN("poll timeout");
continue; continue;
} }
@ -323,8 +315,8 @@ int OSSPlayback::mixerProc()
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue; continue;
const auto errstr = std::generic_category().message(errno); const auto errstr = std::generic_category().message(errno);
ERR("write failed: %s\n", errstr.c_str()); ERR("write failed: {}", errstr);
mDevice->handleDisconnect("Failed writing playback samples: %s", errstr.c_str()); mDevice->handleDisconnect("Failed writing playback samples: {}", errstr);
break; break;
} }
@ -352,20 +344,20 @@ void OSSPlayback::open(std::string_view name)
); );
if(iter == PlaybackDevices.cend()) if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
devname = iter->device_name.c_str(); devname = iter->device_name.c_str();
} }
int fd{::open(devname, O_WRONLY)}; const auto fd = ::open(devname, O_WRONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */
if(fd == -1) if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname,
std::generic_category().message(errno).c_str()}; std::generic_category().message(errno)};
if(mFd != -1) if(mFd != -1)
::close(mFd); ::close(mFd);
mFd = fd; mFd = fd;
mDevice->DeviceName = name; mDeviceName = name;
} }
bool OSSPlayback::reset() bool OSSPlayback::reset()
@ -390,31 +382,33 @@ bool OSSPlayback::reset()
break; break;
} }
uint periods{mDevice->BufferSize / mDevice->UpdateSize}; uint periods{mDevice->mBufferSize / mDevice->mUpdateSize};
uint numChannels{mDevice->channelsFromFmt()}; uint numChannels{mDevice->channelsFromFmt()};
uint ossSpeed{mDevice->Frequency}; uint ossSpeed{mDevice->mSampleRate};
uint frameSize{numChannels * mDevice->bytesFromFmt()}; uint frameSize{numChannels * mDevice->bytesFromFmt()};
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */ /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
uint log2FragmentSize{std::max(log2i(mDevice->UpdateSize*frameSize), 4u)}; uint log2FragmentSize{std::max(log2i(mDevice->mUpdateSize*frameSize), 4u)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{}; audio_buf_info info{};
#define CHECKERR(func) if((func) < 0) \ #define CHECKERR(func) if((func) < 0) \
throw al::backend_exception{al::backend_error::DeviceError, "%s failed: %s\n", #func, \ throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \
std::generic_category().message(errno).c_str()}; std::generic_category().message(errno)};
/* Don't fail if SETFRAGMENT fails. We can handle just about anything /* Don't fail if SETFRAGMENT fails. We can handle just about anything
* that's reported back via GETOSPACE */ * that's reported back via GETOSPACE */
/* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */
ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize); ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info)); CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
/* NOLINTEND(cppcoreguidelines-pro-type-vararg) */
#undef CHECKERR #undef CHECKERR
if(mDevice->channelsFromFmt() != numChannels) if(mDevice->channelsFromFmt() != numChannels)
{ {
ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans), ERR("Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans),
numChannels); numChannels);
return false; return false;
} }
@ -423,18 +417,18 @@ bool OSSPlayback::reset()
(ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) ||
(ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
{ {
ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType), ERR("Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType),
ossFormat); as_unsigned(ossFormat));
return false; return false;
} }
mDevice->Frequency = ossSpeed; mDevice->mSampleRate = ossSpeed;
mDevice->UpdateSize = static_cast<uint>(info.fragsize) / frameSize; mDevice->mUpdateSize = static_cast<uint>(info.fragsize) / frameSize;
mDevice->BufferSize = static_cast<uint>(info.fragments) * mDevice->UpdateSize; mDevice->mBufferSize = static_cast<uint>(info.fragments) * mDevice->mUpdateSize;
setDefaultChannelOrder(); setDefaultChannelOrder();
mMixData.resize(size_t{mDevice->UpdateSize} * mDevice->frameSizeFromFmt()); mMixData.resize(size_t{mDevice->mUpdateSize} * mDevice->frameSizeFromFmt());
return true; return true;
} }
@ -443,11 +437,11 @@ void OSSPlayback::start()
{ {
try { try {
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this}; mThread = std::thread{&OSSPlayback::mixerProc, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()}; "Failed to start mixing thread: {}", e.what()};
} }
} }
@ -457,13 +451,13 @@ void OSSPlayback::stop()
return; return;
mThread.join(); mThread.join();
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */
ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str()); ERR("Error resetting device: {}", std::generic_category().message(errno));
} }
struct OSScapture final : public BackendBase { struct OSScapture final : public BackendBase {
OSScapture(DeviceBase *device) noexcept : BackendBase{device} { } explicit OSScapture(DeviceBase *device) noexcept : BackendBase{device} { }
~OSScapture() override; ~OSScapture() override;
int recordProc(); int recordProc();
@ -507,25 +501,25 @@ int OSScapture::recordProc()
if(errno == EINTR || errno == EAGAIN) if(errno == EINTR || errno == EAGAIN)
continue; continue;
const auto errstr = std::generic_category().message(errno); const auto errstr = std::generic_category().message(errno);
ERR("poll failed: %s\n", errstr.c_str()); ERR("poll failed: {}", errstr);
mDevice->handleDisconnect("Failed to check capture samples: %s", errstr.c_str()); mDevice->handleDisconnect("Failed to check capture samples: {}", errstr);
break; break;
} }
else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */ else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
{ {
WARN("poll timeout\n"); WARN("poll timeout");
continue; continue;
} }
auto vec = mRing->getWriteVector(); auto vec = mRing->getWriteVector();
if(vec.first.len > 0) if(vec[0].len > 0)
{ {
ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)}; ssize_t amt{read(mFd, vec[0].buf, vec[0].len*frame_size)};
if(amt < 0) if(amt < 0)
{ {
const auto errstr = std::generic_category().message(errno); const auto errstr = std::generic_category().message(errno);
ERR("read failed: %s\n", errstr.c_str()); ERR("read failed: {}", errstr);
mDevice->handleDisconnect("Failed reading capture samples: %s", errstr.c_str()); mDevice->handleDisconnect("Failed reading capture samples: {}", errstr);
break; break;
} }
mRing->writeAdvance(static_cast<size_t>(amt)/frame_size); mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
@ -552,14 +546,14 @@ void OSScapture::open(std::string_view name)
); );
if(iter == CaptureDevices.cend()) if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
devname = iter->device_name.c_str(); devname = iter->device_name.c_str();
} }
mFd = ::open(devname, O_RDONLY); mFd = ::open(devname, O_RDONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */
if(mFd == -1) if(mFd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname,
std::generic_category().message(errno).c_str()}; std::generic_category().message(errno)};
int ossFormat{}; int ossFormat{};
switch(mDevice->FmtType) switch(mDevice->FmtType)
@ -578,55 +572,57 @@ void OSScapture::open(std::string_view name)
case DevFmtUInt: case DevFmtUInt:
case DevFmtFloat: case DevFmtFloat:
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
} }
uint periods{4}; uint periods{4};
uint numChannels{mDevice->channelsFromFmt()}; uint numChannels{mDevice->channelsFromFmt()};
uint frameSize{numChannels * mDevice->bytesFromFmt()}; uint frameSize{numChannels * mDevice->bytesFromFmt()};
uint ossSpeed{mDevice->Frequency}; uint ossSpeed{mDevice->mSampleRate};
/* according to the OSS spec, 16 bytes are the minimum */ /* according to the OSS spec, 16 bytes are the minimum */
uint log2FragmentSize{std::max(log2i(mDevice->BufferSize * frameSize / periods), 4u)}; uint log2FragmentSize{std::max(log2i(mDevice->mBufferSize * frameSize / periods), 4u)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{}; audio_buf_info info{};
#define CHECKERR(func) if((func) < 0) { \ #define CHECKERR(func) if((func) < 0) { \
throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \ throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \
std::generic_category().message(errno).c_str()}; \ std::generic_category().message(errno)}; \
} }
/* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info)); CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info));
/* NOLINTEND(cppcoreguidelines-pro-type-vararg) */
#undef CHECKERR #undef CHECKERR
if(mDevice->channelsFromFmt() != numChannels) if(mDevice->channelsFromFmt() != numChannels)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans), "Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans),
numChannels}; numChannels};
if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte)
|| (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte)
|| (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType), "Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType),
ossFormat}; as_unsigned(ossFormat)};
mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false); mRing = RingBuffer::Create(mDevice->mBufferSize, frameSize, false);
mDevice->DeviceName = name; mDeviceName = name;
} }
void OSScapture::start() void OSScapture::start()
{ {
try { try {
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this}; mThread = std::thread{&OSScapture::recordProc, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording thread: %s", e.what()}; "Failed to start recording thread: {}", e.what()};
} }
} }
@ -636,8 +632,8 @@ void OSScapture::stop()
return; return;
mThread.join(); mThread.join();
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */
ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str()); ERR("Error resetting device: {}", std::generic_category().message(errno));
} }
void OSScapture::captureSamples(std::byte *buffer, uint samples) void OSScapture::captureSamples(std::byte *buffer, uint samples)

View file

@ -0,0 +1,700 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2024 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "otherio.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winreg.h>
#include <cstdio>
#include <cstdlib>
#include <memory.h>
#include <wtypes.h>
#include <cguid.h>
#include <devpropdef.h>
#include <mmreg.h>
#include <propsys.h>
#include <propkey.h>
#include <devpkey.h>
#ifndef _WAVEFORMATEXTENSIBLE_
#include <ks.h>
#include <ksmedia.h>
#endif
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <deque>
#include <future>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#include "albit.h"
#include "alnumeric.h"
#include "althrd_setname.h"
#include "comptr.h"
#include "core/converter.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "strutils.h"
/* A custom C++ interface that should be capable of interoperating with ASIO
* drivers.
*/
enum class ORIOError : LONG {
Okay = 0,
Success = 0x3f4847a0,
NotPresent = -1000,
HWMalfunction,
InvalidParameter,
InvalidMode,
SPNotAdvancing,
NoClock,
NoMemory,
};
/* A 64-bit integer or double, which has the most significant 32-bit word first. */
struct ORIO64Bit {
uint32_t hi;
uint32_t lo;
template<typename T>
auto as() const -> T = delete;
};
template<> [[nodiscard]]
auto ORIO64Bit::as() const -> uint64_t { return (uint64_t{hi}<<32) | lo; }
template<> [[nodiscard]]
auto ORIO64Bit::as() const -> int64_t { return static_cast<int64_t>(as<uint64_t>()); }
template<> [[nodiscard]]
auto ORIO64Bit::as() const -> double { return al::bit_cast<double>(as<uint64_t>()); }
enum class ORIOSampleType : LONG {
Int16BE = 0,
Int24BE = 1,
Int32BE = 2,
Float32BE = 3,
Float64BE = 4,
Int32BE16 = 8,
Int32BE18 = 9,
Int32BE20 = 10,
Int32BE24 = 11,
Int16LE = 16,
Int24LE = 17,
Int32LE = 18,
Float32LE = 19,
Float64LE = 20,
Int32LE16 = 24,
Int32LE18 = 25,
Int32LE20 = 26,
Int32LE24 = 27,
DSDInt8LSB1 = 32,
DSDInt8MSB1 = 33,
DSDInt8 = 40,
};
struct ORIOClockSource {
LONG mIndex;
LONG mAssocChannel;
LONG mAssocGroup;
LONG mIsCurrent;
std::array<char,32> mName;
};
struct ORIOChannelInfo {
LONG mChannel;
LONG mIsInput;
LONG mIsActive;
LONG mGroup;
ORIOSampleType mSampleType;
std::array<char,32> mName;
};
struct ORIOBufferInfo {
LONG mIsInput;
LONG mChannelNum;
std::array<void*,2> mBuffers;
};
struct ORIOTime {
struct TimeInfo {
double mSpeed;
ORIO64Bit mSystemTime;
ORIO64Bit mSamplePosition;
double mSampleRate;
ULONG mFlags;
std::array<char,12> mReserved;
};
struct TimeCode {
double mSpeed;
ORIO64Bit mTimeCodeSamples;
ULONG mFlags;
std::array<char,64> mFuture;
};
std::array<LONG,4> mReserved;
TimeInfo mTimeInfo;
TimeCode mTimeCode;
};
#ifdef _WIN64
#define ORIO_CALLBACK CALLBACK
#else
#define ORIO_CALLBACK
#endif
struct ORIOCallbacks {
void (ORIO_CALLBACK*BufferSwitch)(LONG bufferIndex, LONG directProcess) noexcept;
void (ORIO_CALLBACK*SampleRateDidChange)(double srate) noexcept;
auto (ORIO_CALLBACK*Message)(LONG selector, LONG value, void *message, double *opt) noexcept -> LONG;
auto (ORIO_CALLBACK*BufferSwitchTimeInfo)(ORIOTime *timeInfo, LONG bufferIndex, LONG directProcess) noexcept -> ORIOTime*;
};
/* COM interfaces don't include a virtual destructor in their pure-virtual
* classes, and we can't add one without breaking ABI.
*/
#ifdef __GNUC__
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"")
#endif
/* NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor) */
struct ORIOiface : public IUnknown {
STDMETHOD_(LONG, Init)(void *sysHandle) = 0;
/* A fixed-length span should be passed exactly the same as one pointer.
* This ensures an appropriately-sized buffer for the driver.
*/
STDMETHOD_(void, GetDriverName)(al::span<char,32> name) = 0;
STDMETHOD_(LONG, GetDriverVersion)() = 0;
STDMETHOD_(void, GetErrorMessage)(al::span<char,124> message) = 0;
STDMETHOD_(ORIOError, Start)() = 0;
STDMETHOD_(ORIOError, Stop)() = 0;
STDMETHOD_(ORIOError, GetChannels)(LONG *numInput, LONG *numOutput) = 0;
STDMETHOD_(ORIOError, GetLatencies)(LONG *inputLatency, LONG *outputLatency) = 0;
STDMETHOD_(ORIOError, GetBufferSize)(LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity) = 0;
STDMETHOD_(ORIOError, CanSampleRate)(double srate) = 0;
STDMETHOD_(ORIOError, GetSampleRate)(double *srate) = 0;
STDMETHOD_(ORIOError, SetSampleRate)(double srate) = 0;
STDMETHOD_(ORIOError, GetClockSources)(ORIOClockSource *clocks, LONG *numSources) = 0;
STDMETHOD_(ORIOError, SetClockSource)(LONG index) = 0;
STDMETHOD_(ORIOError, GetSamplePosition)(ORIO64Bit *splPos, ORIO64Bit *tstampNS) = 0;
STDMETHOD_(ORIOError, GetChannelInfo)(ORIOChannelInfo *info) = 0;
STDMETHOD_(ORIOError, CreateBuffers)(ORIOBufferInfo *infos, LONG numInfos, LONG bufferSize, ORIOCallbacks *callbacks) = 0;
STDMETHOD_(ORIOError, DisposeBuffers)() = 0;
STDMETHOD_(ORIOError, ControlPanel)() = 0;
STDMETHOD_(ORIOError, Future)(LONG selector, void *opt) = 0;
STDMETHOD_(ORIOError, OutputReady)() = 0;
ORIOiface() = default;
ORIOiface(const ORIOiface&) = delete;
auto operator=(const ORIOiface&) -> ORIOiface& = delete;
~ORIOiface() = delete;
};
#ifdef __GNUC__
_Pragma("GCC diagnostic pop")
#endif
namespace {
using namespace std::string_view_literals;
using std::chrono::nanoseconds;
using std::chrono::milliseconds;
using std::chrono::seconds;
struct DeviceEntry {
std::string mDrvName;
CLSID mDrvGuid{};
};
std::vector<DeviceEntry> gDeviceList;
struct KeyCloser {
void operator()(HKEY key) { RegCloseKey(key); }
};
using KeyPtr = std::unique_ptr<std::remove_pointer_t<HKEY>,KeyCloser>;
[[nodiscard]]
auto PopulateDeviceList() -> HRESULT
{
auto regbase = KeyPtr{};
auto res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\ASIO", 0, KEY_READ,
al::out_ptr(regbase));
if(res != ERROR_SUCCESS)
{
ERR("Error opening HKLM\\Software\\ASIO: {}", res);
return E_NOINTERFACE;
}
auto numkeys = DWORD{};
auto maxkeylen = DWORD{};
res = RegQueryInfoKeyW(regbase.get(), nullptr, nullptr, nullptr, &numkeys, &maxkeylen, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr);
if(res != ERROR_SUCCESS)
{
ERR("Error querying HKLM\\Software\\ASIO info: {}", res);
return E_FAIL;
}
/* maxkeylen is the max number of unicode characters a subkey is. A unicode
* character can occupy two WCHARs, so ensure there's enough space for them
* and the null char.
*/
auto keyname = std::vector<WCHAR>(maxkeylen*2 + 1);
for(DWORD i{0};i < numkeys;++i)
{
auto namelen = static_cast<DWORD>(keyname.size());
res = RegEnumKeyExW(regbase.get(), i, keyname.data(), &namelen, nullptr, nullptr, nullptr,
nullptr);
if(res != ERROR_SUCCESS)
{
ERR("Error querying HKLM\\Software\\ASIO subkey {}: {}", i, res);
continue;
}
if(namelen == 0)
{
ERR("HKLM\\Software\\ASIO subkey {} is blank?", i);
continue;
}
auto subkeyname = wstr_to_utf8({keyname.data(), namelen});
auto subkey = KeyPtr{};
res = RegOpenKeyExW(regbase.get(), keyname.data(), 0, KEY_READ, al::out_ptr(subkey));
if(res != ERROR_SUCCESS)
{
ERR("Error opening HKLM\\Software\\ASIO\\{}: {}", subkeyname, res);
continue;
}
auto idstr = std::array<WCHAR,48>{};
auto readsize = DWORD{idstr.size()*sizeof(WCHAR)};
res = RegGetValueW(subkey.get(), L"", L"CLSID", RRF_RT_REG_SZ, nullptr, idstr.data(),
&readsize);
if(res != ERROR_SUCCESS)
{
ERR("Failed to read HKLM\\Software\\ASIO\\{}\\CLSID: {}", subkeyname, res);
continue;
}
idstr.back() = 0;
auto guid = CLSID{};
if(auto hr = CLSIDFromString(idstr.data(), &guid); FAILED(hr))
{
ERR("Failed to parse CLSID \"{}\": {:#x}", wstr_to_utf8(idstr.data()),
as_unsigned(hr));
continue;
}
/* The CLSID is also used for the IID. */
auto iface = ComPtr<ORIOiface>{};
auto hr = CoCreateInstance(guid, nullptr, CLSCTX_INPROC_SERVER, guid, al::out_ptr(iface));
if(SUCCEEDED(hr))
{
#if !ALSOFT_UWP
if(!iface->Init(GetForegroundWindow()))
#else
if(!iface->Init(nullptr))
#endif
{
ERR("Failed to initialize {}", subkeyname);
continue;
}
auto drvname = std::array<char,32>{};
iface->GetDriverName(drvname);
auto drvver = iface->GetDriverVersion();
auto &entry = gDeviceList.emplace_back();
entry.mDrvName = drvname.data();
entry.mDrvGuid = guid;
TRACE("Got {} v{}, CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
entry.mDrvName, drvver, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0],
guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
guid.Data4[6], guid.Data4[7]);
}
else
ERR("Failed to create {} instance for CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}: {:#x}",
subkeyname.c_str(), guid.Data1, guid.Data2, guid.Data3, guid.Data4[0],
guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
guid.Data4[6], guid.Data4[7], as_unsigned(hr));
}
return S_OK;
}
enum class MsgType {
OpenDevice,
ResetDevice,
StartDevice,
StopDevice,
CloseDevice,
QuitThread
};
constexpr const char *GetMessageTypeName(MsgType type) noexcept
{
switch(type)
{
case MsgType::OpenDevice: return "Open Device";
case MsgType::ResetDevice: return "Reset Device";
case MsgType::StartDevice: return "Start Device";
case MsgType::StopDevice: return "Stop Device";
case MsgType::CloseDevice: return "Close Device";
case MsgType::QuitThread: break;
}
return "";
}
/* Proxy interface used by the message handler, to ensure COM objects are used
* on a thread where COM is initialized.
*/
struct OtherIOProxy {
OtherIOProxy() = default;
OtherIOProxy(const OtherIOProxy&) = delete;
OtherIOProxy(OtherIOProxy&&) = delete;
virtual ~OtherIOProxy() = default;
void operator=(const OtherIOProxy&) = delete;
void operator=(OtherIOProxy&&) = delete;
virtual HRESULT openProxy(std::string_view name) = 0;
virtual void closeProxy() = 0;
virtual HRESULT resetProxy() = 0;
virtual HRESULT startProxy() = 0;
virtual void stopProxy() = 0;
struct Msg {
MsgType mType;
OtherIOProxy *mProxy;
std::string_view mParam;
std::promise<HRESULT> mPromise;
explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
};
static inline std::deque<Msg> mMsgQueue;
static inline std::mutex mMsgQueueLock;
static inline std::condition_variable mMsgQueueCond;
auto pushMessage(MsgType type, std::string_view param={}) -> std::future<HRESULT>
{
auto promise = std::promise<HRESULT>{};
auto future = std::future<HRESULT>{promise.get_future()};
{
auto msglock = std::lock_guard{mMsgQueueLock};
mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
}
mMsgQueueCond.notify_one();
return future;
}
static auto popMessage() -> Msg
{
auto lock = std::unique_lock{mMsgQueueLock};
mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
auto msg = Msg{std::move(mMsgQueue.front())};
mMsgQueue.pop_front();
return msg;
}
static void messageHandler(std::promise<HRESULT> *promise);
};
void OtherIOProxy::messageHandler(std::promise<HRESULT> *promise)
{
TRACE("Starting COM message thread");
auto com = ComWrapper{COINIT_APARTMENTTHREADED};
if(!com)
{
WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status()));
promise->set_value(com.status());
return;
}
auto hr = PopulateDeviceList();
if(FAILED(hr))
{
promise->set_value(hr);
return;
}
promise->set_value(S_OK);
promise = nullptr;
TRACE("Starting message loop");
while(Msg msg{popMessage()})
{
TRACE("Got message \"{}\" ({:#04x}, this={}, param=\"{}\")",
GetMessageTypeName(msg.mType), static_cast<uint>(msg.mType),
static_cast<void*>(msg.mProxy), msg.mParam);
switch(msg.mType)
{
case MsgType::OpenDevice:
hr = msg.mProxy->openProxy(msg.mParam);
msg.mPromise.set_value(hr);
continue;
case MsgType::ResetDevice:
hr = msg.mProxy->resetProxy();
msg.mPromise.set_value(hr);
continue;
case MsgType::StartDevice:
hr = msg.mProxy->startProxy();
msg.mPromise.set_value(hr);
continue;
case MsgType::StopDevice:
msg.mProxy->stopProxy();
msg.mPromise.set_value(S_OK);
continue;
case MsgType::CloseDevice:
msg.mProxy->closeProxy();
msg.mPromise.set_value(S_OK);
continue;
case MsgType::QuitThread:
break;
}
ERR("Unexpected message: {}", int{al::to_underlying(msg.mType)});
msg.mPromise.set_value(E_FAIL);
}
TRACE("Message loop finished");
}
struct OtherIOPlayback final : public BackendBase, OtherIOProxy {
explicit OtherIOPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OtherIOPlayback() final;
void mixerProc();
void open(std::string_view name) final;
auto openProxy(std::string_view name) -> HRESULT final;
void closeProxy() final;
auto reset() -> bool final;
auto resetProxy() -> HRESULT final;
void start() final;
auto startProxy() -> HRESULT final;
void stop() final;
void stopProxy() final;
HRESULT mOpenStatus{E_FAIL};
std::atomic<bool> mKillNow{true};
std::thread mThread;
};
OtherIOPlayback::~OtherIOPlayback()
{
if(SUCCEEDED(mOpenStatus))
pushMessage(MsgType::CloseDevice).wait();
}
void OtherIOPlayback::mixerProc()
{
const auto restTime = milliseconds{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2};
SetRTPriority();
althrd_setname(GetMixerThreadName());
auto done = int64_t{0};
auto start = std::chrono::steady_clock::now();
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
auto now = std::chrono::steady_clock::now();
/* This converts from nanoseconds to nanosamples, then to samples. */
const auto avail = int64_t{std::chrono::duration_cast<seconds>((now-start)
* mDevice->mSampleRate).count()};
if(avail-done < mDevice->mUpdateSize)
{
std::this_thread::sleep_for(restTime);
continue;
}
while(avail-done >= mDevice->mUpdateSize)
{
mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u);
done += mDevice->mUpdateSize;
}
if(done >= mDevice->mSampleRate)
{
auto s = seconds{done/mDevice->mSampleRate};
start += s;
done -= mDevice->mSampleRate*s.count();
}
}
}
void OtherIOPlayback::open(std::string_view name)
{
if(name.empty() && !gDeviceList.empty())
name = gDeviceList[0].mDrvName;
else
{
auto iter = std::find_if(gDeviceList.cbegin(), gDeviceList.cend(),
[name](const DeviceEntry &entry) { return entry.mDrvName == name; });
if(iter == gDeviceList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"{}\" not found", name};
}
mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
if(FAILED(mOpenStatus))
throw al::backend_exception{al::backend_error::DeviceError, "Failed to open \"{}\"", name};
mDeviceName = name;
}
auto OtherIOPlayback::openProxy(std::string_view name [[maybe_unused]]) -> HRESULT
{
return S_OK;
}
void OtherIOPlayback::closeProxy()
{
}
auto OtherIOPlayback::reset() -> bool
{
return SUCCEEDED(pushMessage(MsgType::ResetDevice).get());
}
auto OtherIOPlayback::resetProxy() -> HRESULT
{
setDefaultWFXChannelOrder();
return S_OK;
}
void OtherIOPlayback::start()
{
auto hr = pushMessage(MsgType::StartDevice).get();
if(FAILED(hr))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start playback: {:#x}", as_unsigned(hr)};
}
auto OtherIOPlayback::startProxy() -> HRESULT
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{&OtherIOPlayback::mixerProc, this};
return S_OK;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: {}", e.what());
}
return E_FAIL;
}
void OtherIOPlayback::stop()
{
pushMessage(MsgType::StopDevice).wait();
}
void OtherIOPlayback::stopProxy()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mThread.join();
}
} // namespace
auto OtherIOBackendFactory::init() -> bool
{
static HRESULT InitResult{E_FAIL};
if(FAILED(InitResult)) try
{
auto promise = std::promise<HRESULT>{};
auto future = promise.get_future();
std::thread{&OtherIOProxy::messageHandler, &promise}.detach();
InitResult = future.get();
}
catch(...) {
}
return SUCCEEDED(InitResult);
}
auto OtherIOBackendFactory::querySupport(BackendType type) -> bool
{ return type == BackendType::Playback; }
auto OtherIOBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::vector<std::string> outnames;
switch(type)
{
case BackendType::Playback:
std::for_each(gDeviceList.cbegin(), gDeviceList.cend(),
[&outnames](const DeviceEntry &entry) { outnames.emplace_back(entry.mDrvName); });
break;
case BackendType::Capture:
break;
}
return outnames;
}
auto OtherIOBackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr
{
if(type == BackendType::Playback)
return BackendPtr{new OtherIOPlayback{device}};
return nullptr;
}
auto OtherIOBackendFactory::getFactory() -> BackendFactory&
{
static auto factory = OtherIOBackendFactory{};
return factory;
}
auto OtherIOBackendFactory::queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport
{
return alc::EventSupport::NoSupport;
}

View file

@ -0,0 +1,21 @@
#ifndef BACKENDS_OTHERIO_H
#define BACKENDS_OTHERIO_H
#include "base.h"
struct OtherIOBackendFactory final : public BackendFactory {
public:
auto init() -> bool final;
auto querySupport(BackendType type) -> bool final;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
auto enumerate(BackendType type) -> std::vector<std::string> final;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OTHERIO_H */

View file

@ -35,6 +35,7 @@
#include <cerrno> #include <cerrno>
#include <chrono> #include <chrono>
#include <ctime> #include <ctime>
#include <functional>
#include <iterator> #include <iterator>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -54,6 +55,8 @@
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "fmt/core.h"
#include "fmt/ranges.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "ringbuffer.h" #include "ringbuffer.h"
@ -132,6 +135,9 @@ _Pragma("GCC diagnostic pop")
namespace { namespace {
template<typename T> [[nodiscard]] constexpr
auto as_const_ptr(T *ptr) noexcept -> std::add_const_t<T>* { return ptr; }
struct PodDynamicBuilder { struct PodDynamicBuilder {
private: private:
std::vector<std::byte> mStorage; std::vector<std::byte> mStorage;
@ -143,7 +149,7 @@ private:
mStorage.resize(size); mStorage.resize(size);
} }
catch(...) { catch(...) {
ERR("Failed to resize POD storage\n"); ERR("Failed to resize POD storage");
return -ENOMEM; return -ENOMEM;
} }
mPod.data = mStorage.data(); mPod.data = mStorage.data();
@ -152,7 +158,7 @@ private:
} }
public: public:
PodDynamicBuilder(uint32_t initSize=0) : mStorage(initSize) explicit PodDynamicBuilder(uint32_t initSize=1024) : mStorage(initSize)
, mPod{make_pod_builder(mStorage.data(), initSize)} , mPod{make_pod_builder(mStorage.data(), initSize)}
{ {
static constexpr auto callbacks{[] static constexpr auto callbacks{[]
@ -189,12 +195,13 @@ bool check_version(const char *version)
* future. * future.
*/ */
int major{0}, minor{0}, revision{0}; int major{0}, minor{0}, revision{0};
/* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */
int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)}; int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)};
return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR) return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR)
|| (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)); || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO));
} }
#ifdef HAVE_DYNLOAD #if HAVE_DYNLOAD
#define PWIRE_FUNCS(MAGIC) \ #define PWIRE_FUNCS(MAGIC) \
MAGIC(pw_context_connect) \ MAGIC(pw_context_connect) \
MAGIC(pw_context_destroy) \ MAGIC(pw_context_destroy) \
@ -251,7 +258,7 @@ bool pwire_load()
pwire_handle = LoadLib(pwire_library); pwire_handle = LoadLib(pwire_library);
if(!pwire_handle) if(!pwire_handle)
{ {
WARN("Failed to load %s\n", pwire_library); WARN("Failed to load {}", pwire_library);
return false; return false;
} }
@ -265,7 +272,7 @@ bool pwire_load()
if(!missing_funcs.empty()) if(!missing_funcs.empty())
{ {
WARN("Missing expected functions:%s\n", missing_funcs.c_str()); WARN("Missing expected functions:{}", missing_funcs);
CloseLib(pwire_handle); CloseLib(pwire_handle);
pwire_handle = nullptr; pwire_handle = nullptr;
return false; return false;
@ -411,9 +418,10 @@ struct PwStreamDeleter {
}; };
using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>; using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>;
/* Enums for bitflags... again... *sigh* */ /* NOLINTBEGIN(*EnumCastOutOfRange) Enums for bitflags... again... *sigh* */
constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept
{ return static_cast<pw_stream_flags>(lhs | al::to_underlying(rhs)); } { return static_cast<pw_stream_flags>(lhs | al::to_underlying(rhs)); }
/* NOLINTEND(*EnumCastOutOfRange) */
constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept
{ lhs = lhs | rhs; return lhs; } { lhs = lhs | rhs; return lhs; }
@ -497,7 +505,7 @@ struct NodeProxy {
uint32_t mId{}; uint32_t mId{};
PwNodePtr mNode{}; PwNodePtr mNode;
spa_hook mListener{}; spa_hook mListener{};
NodeProxy(uint32_t id, PwNodePtr node) NodeProxy(uint32_t id, PwNodePtr node)
@ -532,7 +540,7 @@ struct MetadataProxy {
uint32_t mId{}; uint32_t mId{};
PwMetadataPtr mMetadata{}; PwMetadataPtr mMetadata;
spa_hook mListener{}; spa_hook mListener{};
MetadataProxy(uint32_t id, PwMetadataPtr mdata) MetadataProxy(uint32_t id, PwMetadataPtr mdata)
@ -553,10 +561,10 @@ struct MetadataProxy {
* to objects being added to or removed from the registry. * to objects being added to or removed from the registry.
*/ */
struct EventManager { struct EventManager {
ThreadMainloop mLoop{}; ThreadMainloop mLoop;
PwContextPtr mContext{}; PwContextPtr mContext;
PwCorePtr mCore{}; PwCorePtr mCore;
PwRegistryPtr mRegistry{}; PwRegistryPtr mRegistry;
spa_hook mRegistryListener{}; spa_hook mRegistryListener{};
spa_hook mCoreListener{}; spa_hook mCoreListener{};
@ -585,8 +593,8 @@ struct EventManager {
auto lock() const { return mLoop.lock(); } auto lock() const { return mLoop.lock(); }
auto unlock() const { return mLoop.unlock(); } auto unlock() const { return mLoop.unlock(); }
[[nodiscard]] inline [[nodiscard]]
bool initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept auto initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept -> bool
{ return mInitDone.load(m); } { return mInitDone.load(m); }
/** /**
@ -759,7 +767,7 @@ void DeviceNode::Remove(uint32_t id)
{ {
if(n.mId != id) if(n.mId != id)
return false; return false;
TRACE("Removing device \"%s\"\n", n.mDevName.c_str()); TRACE("Removing device \"{}\"", n.mDevName);
if(gEventHandler.initIsDone(std::memory_order_relaxed)) if(gEventHandler.initIsDone(std::memory_order_relaxed))
{ {
const std::string msg{"Device removed: "+n.mName}; const std::string msg{"Device removed: "+n.mName};
@ -828,7 +836,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce
const uint podType{get_pod_type(value)}; const uint podType{get_pod_type(value)};
if(podType != SPA_TYPE_Int) if(podType != SPA_TYPE_Int)
{ {
WARN(" Unhandled sample rate POD type: %u\n", podType); WARN(" Unhandled sample rate POD type: {}", podType);
return; return;
} }
@ -836,13 +844,13 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce
{ {
if(nvals != 3) if(nvals != 3)
{ {
WARN(" Unexpected SPA_CHOICE_Range count: %u\n", nvals); WARN(" Unexpected SPA_CHOICE_Range count: {}", nvals);
return; return;
} }
auto srates = get_pod_body<int32_t,3>(value); auto srates = get_pod_body<int32_t,3>(value);
/* [0] is the default, [1] is the min, and [2] is the max. */ /* [0] is the default, [1] is the min, and [2] is the max. */
TRACE(" sample rate: %d (range: %d -> %d)\n", srates[0], srates[1], srates[2]); TRACE(" sample rate: {}, range: {}", srates[0], srates.subspan<1>());
if(!mSampleRate || force_update) if(!mSampleRate || force_update)
mSampleRate = static_cast<uint>(std::clamp<int>(srates[0], MinOutputRate, mSampleRate = static_cast<uint>(std::clamp<int>(srates[0], MinOutputRate,
MaxOutputRate)); MaxOutputRate));
@ -853,19 +861,13 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce
{ {
if(nvals == 0) if(nvals == 0)
{ {
WARN(" Unexpected SPA_CHOICE_Enum count: %u\n", nvals); WARN(" Unexpected SPA_CHOICE_Enum count: {}", nvals);
return; return;
} }
auto srates = get_pod_body<int32_t>(value, nvals); auto srates = get_pod_body<int32_t>(value, nvals);
/* [0] is the default, [1...size()-1] are available selections. */ /* [0] is the default, [1...size()-1] are available selections. */
std::string others{(srates.size() > 1) ? std::to_string(srates[1]) : std::string{}}; TRACE(" sample rate: {}, list: {}", srates[0], srates.subspan(1));
for(size_t i{2};i < srates.size();++i)
{
others += ", ";
others += std::to_string(srates[i]);
}
TRACE(" sample rate: %d (%s)\n", srates[0], others.c_str());
/* Pick the first rate listed that's within the allowed range (default /* Pick the first rate listed that's within the allowed range (default
* rate if possible). * rate if possible).
*/ */
@ -885,19 +887,19 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce
{ {
if(nvals != 1) if(nvals != 1)
{ {
WARN(" Unexpected SPA_CHOICE_None count: %u\n", nvals); WARN(" Unexpected SPA_CHOICE_None count: {}", nvals);
return; return;
} }
auto srates = get_pod_body<int32_t,1>(value); auto srates = get_pod_body<int32_t,1>(value);
TRACE(" sample rate: %d\n", srates[0]); TRACE(" sample rate: {}", srates[0]);
if(!mSampleRate || force_update) if(!mSampleRate || force_update)
mSampleRate = static_cast<uint>(std::clamp<int>(srates[0], MinOutputRate, mSampleRate = static_cast<uint>(std::clamp<int>(srates[0], MinOutputRate,
MaxOutputRate)); MaxOutputRate));
return; return;
} }
WARN(" Unhandled sample rate choice type: %u\n", choiceType); WARN(" Unhandled sample rate choice type: {}", choiceType);
} }
void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept
@ -907,7 +909,7 @@ void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcep
if(choiceType != SPA_CHOICE_None || choiceCount != 1) if(choiceType != SPA_CHOICE_None || choiceCount != 1)
{ {
ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); ERR(" Unexpected positions choice: type={}, count={}", choiceType, choiceCount);
return; return;
} }
@ -938,7 +940,7 @@ void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcep
else else
mChannels = DevFmtMono; mChannels = DevFmtMono;
} }
TRACE(" %zu position%s for %s%s\n", chanmap.size(), (chanmap.size()==1)?"":"s", TRACE(" {} position{} for {}{}", chanmap.size(), (chanmap.size()==1)?"":"s",
DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":"");
} }
@ -950,7 +952,7 @@ void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noex
if(choiceType != SPA_CHOICE_None || choiceCount != 1) if(choiceType != SPA_CHOICE_None || choiceCount != 1)
{ {
ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); ERR(" Unexpected positions choice: type={}, count={}", choiceType, choiceCount);
return; return;
} }
@ -966,7 +968,7 @@ void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noex
else if(*chancount >= 1) else if(*chancount >= 1)
mChannels = DevFmtMono; mChannels = DevFmtMono;
} }
TRACE(" %d channel%s for %s\n", *chancount, (*chancount==1)?"":"s", TRACE(" {} channel{} for {}", *chancount, (*chancount==1)?"":"s",
DevFmtChannelsString(mChannels)); DevFmtChannelsString(mChannels));
} }
@ -1005,7 +1007,7 @@ void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept
ntype = NodeType::Duplex; ntype = NodeType::Duplex;
else else
{ {
TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class); TRACE("Dropping device node {} which became type \"{}\"", info->id, media_class);
DeviceNode::Remove(info->id); DeviceNode::Remove(info->id);
return; return;
} }
@ -1024,20 +1026,23 @@ void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept
serial_id = std::strtoull(serial_str, &serial_end, 0); serial_id = std::strtoull(serial_str, &serial_end, 0);
if(*serial_end != '\0' || errno == ERANGE) if(*serial_end != '\0' || errno == ERANGE)
{ {
ERR("Unexpected object serial: %s\n", serial_str); ERR("Unexpected object serial: {}", serial_str);
serial_id = info->id; serial_id = info->id;
} }
} }
#endif #endif
std::string name; auto name = std::invoke([nodeName,info]() -> std::string
if(nodeName && *nodeName) name = nodeName; {
else name = "PipeWire node #"+std::to_string(info->id); if(nodeName && *nodeName)
return std::string{nodeName};
return fmt::format("PipeWire node #{}", info->id);
});
const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)}; const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)};
TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)", TRACE("Got {} device \"{}\"{}{}{}", AsString(ntype), devName ? devName : "(nil)",
form_factor?" (":"", form_factor?form_factor:"", form_factor?")":""); form_factor?" (":"", form_factor?form_factor:"", form_factor?")":"");
TRACE(" \"%s\" = ID %" PRIu64 "\n", name.c_str(), serial_id); TRACE(" \"{}\" = ID {}", name, serial_id);
DeviceNode &node = DeviceNode::Add(info->id); DeviceNode &node = DeviceNode::Add(info->id);
node.mSerial = serial_id; node.mSerial = serial_id;
@ -1087,7 +1092,7 @@ void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_po
DeviceNode *node{DeviceNode::Find(mId)}; DeviceNode *node{DeviceNode::Find(mId)};
if(!node) UNLIKELY return; if(!node) UNLIKELY return;
TRACE("Device ID %" PRIu64 " %s format%s:\n", node->mSerial, TRACE("Device ID {} {} format{}:", node->mSerial,
(id == SPA_PARAM_EnumFormat) ? "available" : "current", (id == SPA_PARAM_EnumFormat) ? "available" : "current",
(id == SPA_PARAM_EnumFormat) ? "s" : ""); (id == SPA_PARAM_EnumFormat) ? "s" : "");
@ -1122,14 +1127,14 @@ auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const
if(!type) if(!type)
{ {
TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback"); TRACE("Default {} device cleared", isCapture ? "capture" : "playback");
if(!isCapture) DefaultSinkDevice.clear(); if(!isCapture) DefaultSinkDevice.clear();
else DefaultSourceDevice.clear(); else DefaultSourceDevice.clear();
return 0; return 0;
} }
if("Spa:String:JSON"sv != type) if("Spa:String:JSON"sv != type)
{ {
ERR("Unexpected %s property type: %s\n", key, type); ERR("Unexpected {} property type: {}", key, type);
return 0; return 0;
} }
@ -1160,8 +1165,8 @@ auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const
auto propValue = get_json_string(&std::get<1>(it)); auto propValue = get_json_string(&std::get<1>(it));
if(!propValue) break; if(!propValue) break;
TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback", TRACE("Got default {} device \"{}\"", isCapture ? "capture" : "playback",
propValue->c_str()); *propValue);
if(!isCapture && DefaultSinkDevice != *propValue) if(!isCapture && DefaultSinkDevice != *propValue)
{ {
if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) if(gEventHandler.mInitDone.load(std::memory_order_relaxed))
@ -1203,28 +1208,28 @@ bool EventManager::init()
mLoop = ThreadMainloop::Create("PWEventThread"); mLoop = ThreadMainloop::Create("PWEventThread");
if(!mLoop) if(!mLoop)
{ {
ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno); ERR("Failed to create PipeWire event thread loop (errno: {})", errno);
return false; return false;
} }
mContext = mLoop.newContext(); mContext = mLoop.newContext();
if(!mContext) if(!mContext)
{ {
ERR("Failed to create PipeWire event context (errno: %d)\n", errno); ERR("Failed to create PipeWire event context (errno: {})", errno);
return false; return false;
} }
mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
if(!mCore) if(!mCore)
{ {
ERR("Failed to connect PipeWire event context (errno: %d)\n", errno); ERR("Failed to connect PipeWire event context (errno: {})", errno);
return false; return false;
} }
mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)}; mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)};
if(!mRegistry) if(!mRegistry)
{ {
ERR("Failed to get PipeWire event registry (errno: %d)\n", errno); ERR("Failed to get PipeWire event registry (errno: {})", errno);
return false; return false;
} }
@ -1241,7 +1246,7 @@ bool EventManager::init()
if(int res{mLoop.start()}) if(int res{mLoop.start()})
{ {
ERR("Failed to start PipeWire event thread loop (res: %d)\n", res); ERR("Failed to start PipeWire event thread loop (res: {})", res);
return false; return false;
} }
@ -1279,7 +1284,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t
if(!isGood) if(!isGood)
{ {
if(!al::contains(className, "/Video"sv) && !al::starts_with(className, "Stream/"sv)) if(!al::contains(className, "/Video"sv) && !al::starts_with(className, "Stream/"sv))
TRACE("Ignoring node class %s\n", media_class); TRACE("Ignoring node class {}", media_class);
return; return;
} }
@ -1288,7 +1293,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t
version, 0))}; version, 0))};
if(!node) if(!node)
{ {
ERR("Failed to create node proxy object (errno: %d)\n", errno); ERR("Failed to create node proxy object (errno: {})", errno);
return; return;
} }
@ -1311,13 +1316,13 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t
if("default"sv != data_class) if("default"sv != data_class)
{ {
TRACE("Ignoring metadata \"%s\"\n", data_class); TRACE("Ignoring metadata \"{}\"", data_class);
return; return;
} }
if(mDefaultMetadata) if(mDefaultMetadata)
{ {
ERR("Duplicate default metadata\n"); ERR("Duplicate default metadata");
return; return;
} }
@ -1325,7 +1330,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t
type, version, 0))}; type, version, 0))};
if(!mdata) if(!mdata)
{ {
ERR("Failed to create metadata proxy object (errno: %d)\n", errno); ERR("Failed to create metadata proxy object (errno: {})", errno);
return; return;
} }
@ -1382,7 +1387,7 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u
case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break; case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break;
} }
info.rate = device->Frequency; info.rate = device->mSampleRate;
al::span<const spa_audio_channel> map{}; al::span<const spa_audio_channel> map{};
switch(device->FmtChans) switch(device->FmtChans)
@ -1432,7 +1437,7 @@ class PipeWirePlayback final : public BackendBase {
PwStreamPtr mStream; PwStreamPtr mStream;
spa_hook mStreamListener{}; spa_hook mStreamListener{};
spa_io_rate_match *mRateMatch{}; spa_io_rate_match *mRateMatch{};
std::vector<float*> mChannelPtrs; std::vector<void*> mChannelPtrs;
static constexpr pw_stream_events CreateEvents() static constexpr pw_stream_events CreateEvents()
{ {
@ -1448,7 +1453,7 @@ class PipeWirePlayback final : public BackendBase {
} }
public: public:
PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~PipeWirePlayback() final ~PipeWirePlayback() final
{ {
/* Stop the mainloop so the stream can be properly destroyed. */ /* Stop the mainloop so the stream can be properly destroyed. */
@ -1492,7 +1497,7 @@ void PipeWirePlayback::outputCallback() noexcept
uint length{mRateMatch ? mRateMatch->size : 0u}; uint length{mRateMatch ? mRateMatch->size : 0u};
#endif #endif
/* If no length is specified, use the device's update size as a fallback. */ /* If no length is specified, use the device's update size as a fallback. */
if(!length) UNLIKELY length = mDevice->UpdateSize; if(!length) UNLIKELY length = mDevice->mUpdateSize;
/* For planar formats, each datas[] seems to contain one channel, so store /* For planar formats, each datas[] seems to contain one channel, so store
* the pointers in an array. Limit the render length in case the available * the pointers in an array. Limit the render length in case the available
@ -1503,7 +1508,7 @@ void PipeWirePlayback::outputCallback() noexcept
for(const auto &data : datas) for(const auto &data : datas)
{ {
length = std::min(length, data.maxsize/uint{sizeof(float)}); length = std::min(length, data.maxsize/uint{sizeof(float)});
*chanptr_end = static_cast<float*>(data.data); *chanptr_end = data.data;
++chanptr_end; ++chanptr_end;
data.chunk->offset = 0; data.chunk->offset = 0;
@ -1560,7 +1565,7 @@ void PipeWirePlayback::open(std::string_view name)
auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
if(match == devlist.cend()) if(match == devlist.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
targetid = match->mSerial; targetid = match->mSerial;
devname = match->mName; devname = match->mName;
@ -1568,15 +1573,15 @@ void PipeWirePlayback::open(std::string_view name)
if(!mLoop) if(!mLoop)
{ {
const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; const auto count = OpenCount.fetch_add(1u, std::memory_order_relaxed);
const std::string thread_name{"ALSoftP" + std::to_string(count)}; const auto thread_name = fmt::format("ALSoftP{}", count);
mLoop = ThreadMainloop::Create(thread_name.c_str()); mLoop = ThreadMainloop::Create(thread_name.c_str());
if(!mLoop) if(!mLoop)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create PipeWire mainloop (errno: %d)", errno}; "Failed to create PipeWire mainloop (errno: {})", errno};
if(int res{mLoop.start()}) if(int res{mLoop.start()})
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start PipeWire mainloop (res: %d)", res}; "Failed to start PipeWire mainloop (res: {})", res};
} }
MainloopUniqueLock mlock{mLoop}; MainloopUniqueLock mlock{mLoop};
if(!mContext) if(!mContext)
@ -1585,14 +1590,14 @@ void PipeWirePlayback::open(std::string_view name)
mContext = mLoop.newContext(cprops); mContext = mLoop.newContext(cprops);
if(!mContext) if(!mContext)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create PipeWire event context (errno: %d)\n", errno}; "Failed to create PipeWire event context (errno: {})\n", errno};
} }
if(!mCore) if(!mCore)
{ {
mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
if(!mCore) if(!mCore)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to connect PipeWire event context (errno: %d)\n", errno}; "Failed to connect PipeWire event context (errno: {})\n", errno};
} }
mlock.unlock(); mlock.unlock();
@ -1600,9 +1605,9 @@ void PipeWirePlayback::open(std::string_view name)
mTargetId = targetid; mTargetId = targetid;
if(!devname.empty()) if(!devname.empty())
mDevice->DeviceName = std::move(devname); mDeviceName = std::move(devname);
else else
mDevice->DeviceName = "PipeWire Output"sv; mDeviceName = "PipeWire Output"sv;
} }
bool PipeWirePlayback::reset() bool PipeWirePlayback::reset()
@ -1634,13 +1639,13 @@ bool PipeWirePlayback::reset()
if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0) if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0)
{ {
/* Scale the update size if the sample rate changes. */ /* Scale the update size if the sample rate changes. */
const double scale{static_cast<double>(match->mSampleRate) / mDevice->Frequency}; const double scale{static_cast<double>(match->mSampleRate) / mDevice->mSampleRate};
const double updatesize{std::round(mDevice->UpdateSize * scale)}; const double updatesize{std::round(mDevice->mUpdateSize * scale)};
const double buffersize{std::round(mDevice->BufferSize * scale)}; const double buffersize{std::round(mDevice->mBufferSize * scale)};
mDevice->Frequency = match->mSampleRate; mDevice->mSampleRate = match->mSampleRate;
mDevice->UpdateSize = static_cast<uint>(std::clamp(updatesize, 64.0, 8192.0)); mDevice->mUpdateSize = static_cast<uint>(std::clamp(updatesize, 64.0, 8192.0));
mDevice->BufferSize = static_cast<uint>(std::max(buffersize, 128.0)); mDevice->mBufferSize = static_cast<uint>(std::max(buffersize, 128.0));
} }
if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig) if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig)
mDevice->FmtChans = match->mChannels; mDevice->FmtChans = match->mChannels;
@ -1652,12 +1657,10 @@ bool PipeWirePlayback::reset()
/* Force planar 32-bit float output for playback. This is what PipeWire /* Force planar 32-bit float output for playback. This is what PipeWire
* handles internally, and it's easier for us too. * handles internally, and it's easier for us too.
*/ */
spa_audio_info_raw info{make_spa_info(mDevice, is51rear, ForceF32Planar)}; auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, ForceF32Planar)};
static constexpr uint32_t pod_buffer_size{1024}; auto b = PodDynamicBuilder{};
PodDynamicBuilder b(pod_buffer_size); auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info));
const spa_pod *params{spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)};
if(!params) if(!params)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set PipeWire audio format parameters"}; "Failed to set PipeWire audio format parameters"};
@ -1666,7 +1669,7 @@ bool PipeWirePlayback::reset()
* be useful? * be useful?
*/ */
auto&& binary = GetProcBinary(); auto&& binary = GetProcBinary();
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"};
pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname, pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname,
PW_KEY_NODE_DESCRIPTION, appname, PW_KEY_NODE_DESCRIPTION, appname,
PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_TYPE, "Audio",
@ -1676,11 +1679,11 @@ bool PipeWirePlayback::reset()
nullptr)}; nullptr)};
if(!props) if(!props)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create PipeWire stream properties (errno: %d)", errno}; "Failed to create PipeWire stream properties (errno: {})", errno};
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize, pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->mUpdateSize,
mDevice->Frequency); mDevice->mSampleRate);
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->mSampleRate);
#ifdef PW_KEY_TARGET_OBJECT #ifdef PW_KEY_TARGET_OBJECT
pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
#else #else
@ -1692,17 +1695,17 @@ bool PipeWirePlayback::reset()
mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Playback Stream", props)}; mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Playback Stream", props)};
if(!mStream) if(!mStream)
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Failed to create PipeWire stream (errno: %d)", errno}; "Failed to create PipeWire stream (errno: {})", errno};
static constexpr pw_stream_events streamEvents{CreateEvents()}; static constexpr pw_stream_events streamEvents{CreateEvents()};
pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
| PW_STREAM_FLAG_MAP_BUFFERS}; | PW_STREAM_FLAG_MAP_BUFFERS};
if(GetConfigValueBool(mDevice->DeviceName, "pipewire", "rt-mix", false)) if(GetConfigValueBool(mDevice->mDeviceName, "pipewire", "rt-mix", false))
flags |= PW_STREAM_FLAG_RT_PROCESS; flags |= PW_STREAM_FLAG_RT_PROCESS;
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, &params, 1)}) if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, &params, 1)})
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream (res: %d)", res}; "Error connecting PipeWire stream (res: {})", res};
/* Wait for the stream to become paused (ready to start streaming). */ /* Wait for the stream to become paused (ready to start streaming). */
plock.wait([stream=mStream.get()]() plock.wait([stream=mStream.get()]()
@ -1711,12 +1714,12 @@ bool PipeWirePlayback::reset()
pw_stream_state state{pw_stream_get_state(stream, &error)}; pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR) if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream: \"%s\"", error}; "Error connecting PipeWire stream: \"{}\"", error};
return state == PW_STREAM_STATE_PAUSED; return state == PW_STREAM_STATE_PAUSED;
}); });
/* TODO: Update mDevice->UpdateSize with the stream's quantum, and /* TODO: Update mDevice->mUpdateSize with the stream's quantum, and
* mDevice->BufferSize with the total known buffering delay from the head * mDevice->mBufferSize with the total known buffering delay from the head
* of this playback stream to the tail of the device output. * of this playback stream to the tail of the device output.
* *
* This info is apparently not available until after the stream starts. * This info is apparently not available until after the stream starts.
@ -1735,7 +1738,7 @@ void PipeWirePlayback::start()
MainloopUniqueLock plock{mLoop}; MainloopUniqueLock plock{mLoop};
if(int res{pw_stream_set_active(mStream.get(), true)}) if(int res{pw_stream_set_active(mStream.get(), true)})
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start PipeWire stream (res: %d)", res}; "Failed to start PipeWire stream (res: {})", res};
/* Wait for the stream to start playing (would be nice to not, but we need /* Wait for the stream to start playing (would be nice to not, but we need
* the actual update size which is only available after starting). * the actual update size which is only available after starting).
@ -1746,7 +1749,7 @@ void PipeWirePlayback::start()
pw_stream_state state{pw_stream_get_state(stream, &error)}; pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR) if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"PipeWire stream error: %s", error ? error : "(unknown)"}; "PipeWire stream error: {}", error ? error : "(unknown)"};
return state == PW_STREAM_STATE_STREAMING; return state == PW_STREAM_STATE_STREAMING;
}); });
@ -1761,7 +1764,7 @@ void PipeWirePlayback::start()
pw_time ptime{}; pw_time ptime{};
if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
{ {
ERR("Failed to get PipeWire stream time (res: %d)\n", res); ERR("Failed to get PipeWire stream time (res: {})", res);
break; break;
} }
@ -1776,11 +1779,11 @@ void PipeWirePlayback::start()
const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers}; const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers};
/* Ensure the delay is in sample frames. */ /* Ensure the delay is in sample frames. */
const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency * const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->mSampleRate *
ptime.rate.num / ptime.rate.denom}; ptime.rate.num / ptime.rate.denom};
mDevice->UpdateSize = updatesize; mDevice->mUpdateSize = updatesize;
mDevice->BufferSize = static_cast<uint>(ptime.buffered + delay + mDevice->mBufferSize = static_cast<uint>(ptime.buffered + delay +
uint64_t{totalbuffers}*updatesize); uint64_t{totalbuffers}*updatesize);
break; break;
} }
@ -1791,17 +1794,17 @@ void PipeWirePlayback::start()
if(ptime.rate.denom > 0 && updatesize > 0) if(ptime.rate.denom > 0 && updatesize > 0)
{ {
/* Ensure the delay is in sample frames. */ /* Ensure the delay is in sample frames. */
const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency * const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->mSampleRate *
ptime.rate.num / ptime.rate.denom}; ptime.rate.num / ptime.rate.denom};
mDevice->UpdateSize = updatesize; mDevice->mUpdateSize = updatesize;
mDevice->BufferSize = static_cast<uint>(delay + updatesize); mDevice->mBufferSize = static_cast<uint>(delay + updatesize);
break; break;
} }
#endif #endif
if(!--wait_count) if(!--wait_count)
{ {
ERR("Timeout getting PipeWire stream buffering info\n"); ERR("Timeout getting PipeWire stream buffering info");
break; break;
} }
@ -1815,7 +1818,7 @@ void PipeWirePlayback::stop()
{ {
MainloopUniqueLock plock{mLoop}; MainloopUniqueLock plock{mLoop};
if(int res{pw_stream_set_active(mStream.get(), false)}) if(int res{pw_stream_set_active(mStream.get(), false)})
ERR("Failed to stop PipeWire stream (res: %d)\n", res); ERR("Failed to stop PipeWire stream (res: {})", res);
/* Wait for the stream to stop playing. */ /* Wait for the stream to stop playing. */
plock.wait([stream=mStream.get()]() plock.wait([stream=mStream.get()]()
@ -1836,7 +1839,7 @@ ClockLatency PipeWirePlayback::getClockLatency()
{ {
MainloopLockGuard looplock{mLoop}; MainloopLockGuard looplock{mLoop};
if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
ERR("Failed to get PipeWire stream time (res: %d)\n", res); ERR("Failed to get PipeWire stream time (res: {})", res);
} }
/* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
@ -1864,7 +1867,7 @@ ClockLatency PipeWirePlayback::getClockLatency()
*/ */
ptime.now = monoclock.count(); ptime.now = monoclock.count();
curtic = mixtime; curtic = mixtime;
delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency; delay = nanoseconds{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate;
} }
else else
{ {
@ -1924,7 +1927,7 @@ class PipeWireCapture final : public BackendBase {
PwStreamPtr mStream; PwStreamPtr mStream;
spa_hook mStreamListener{}; spa_hook mStreamListener{};
RingBufferPtr mRing{}; RingBufferPtr mRing;
static constexpr pw_stream_events CreateEvents() static constexpr pw_stream_events CreateEvents()
{ {
@ -1938,7 +1941,7 @@ class PipeWireCapture final : public BackendBase {
} }
public: public:
PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { } explicit PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~PipeWireCapture() final { if(mLoop) mLoop.stop(); } ~PipeWireCapture() final { if(mLoop) mLoop.stop(); }
}; };
@ -2025,7 +2028,7 @@ void PipeWireCapture::open(std::string_view name)
} }
if(match == devlist.cend()) if(match == devlist.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
targetid = match->mSerial; targetid = match->mSerial;
if(match->mType != NodeType::Sink) devname = match->mName; if(match->mType != NodeType::Sink) devname = match->mName;
@ -2034,15 +2037,15 @@ void PipeWireCapture::open(std::string_view name)
if(!mLoop) if(!mLoop)
{ {
const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; const auto count = OpenCount.fetch_add(1u, std::memory_order_relaxed);
const std::string thread_name{"ALSoftC" + std::to_string(count)}; const auto thread_name = fmt::format("ALSoftC{}", count);
mLoop = ThreadMainloop::Create(thread_name.c_str()); mLoop = ThreadMainloop::Create(thread_name.c_str());
if(!mLoop) if(!mLoop)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create PipeWire mainloop (errno: %d)", errno}; "Failed to create PipeWire mainloop (errno: {})", errno};
if(int res{mLoop.start()}) if(int res{mLoop.start()})
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start PipeWire mainloop (res: %d)", res}; "Failed to start PipeWire mainloop (res: {})", res};
} }
MainloopUniqueLock mlock{mLoop}; MainloopUniqueLock mlock{mLoop};
if(!mContext) if(!mContext)
@ -2051,14 +2054,14 @@ void PipeWireCapture::open(std::string_view name)
mContext = mLoop.newContext(cprops); mContext = mLoop.newContext(cprops);
if(!mContext) if(!mContext)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create PipeWire event context (errno: %d)\n", errno}; "Failed to create PipeWire event context (errno: {})\n", errno};
} }
if(!mCore) if(!mCore)
{ {
mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
if(!mCore) if(!mCore)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to connect PipeWire event context (errno: %d)\n", errno}; "Failed to connect PipeWire event context (errno: {})\n", errno};
} }
mlock.unlock(); mlock.unlock();
@ -2066,9 +2069,9 @@ void PipeWireCapture::open(std::string_view name)
mTargetId = targetid; mTargetId = targetid;
if(!devname.empty()) if(!devname.empty())
mDevice->DeviceName = std::move(devname); mDeviceName = std::move(devname);
else else
mDevice->DeviceName = "PipeWire Input"sv; mDeviceName = "PipeWire Input"sv;
bool is51rear{false}; bool is51rear{false};
@ -2083,19 +2086,16 @@ void PipeWireCapture::open(std::string_view name)
if(match != devlist.cend()) if(match != devlist.cend())
is51rear = match->mIs51Rear; is51rear = match->mIs51Rear;
} }
spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)}; auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, UseDevType)};
static constexpr uint32_t pod_buffer_size{1024}; auto b = PodDynamicBuilder{};
PodDynamicBuilder b(pod_buffer_size); auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info));
if(!params)
std::array params{static_cast<const spa_pod*>(spa_format_audio_raw_build(b.get(),
SPA_PARAM_EnumFormat, &info))};
if(!params[0])
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set PipeWire audio format parameters"}; "Failed to set PipeWire audio format parameters"};
auto&& binary = GetProcBinary(); auto&& binary = GetProcBinary();
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"};
pw_properties *props{pw_properties_new( pw_properties *props{pw_properties_new(
PW_KEY_NODE_NAME, appname, PW_KEY_NODE_NAME, appname,
PW_KEY_NODE_DESCRIPTION, appname, PW_KEY_NODE_DESCRIPTION, appname,
@ -2106,15 +2106,15 @@ void PipeWireCapture::open(std::string_view name)
nullptr)}; nullptr)};
if(!props) if(!props)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create PipeWire stream properties (errno: %d)", errno}; "Failed to create PipeWire stream properties (errno: {})", errno};
/* We don't actually care what the latency/update size is, as long as it's /* We don't actually care what the latency/update size is, as long as it's
* reasonable. Unfortunately, when unspecified PipeWire seems to default to * reasonable. Unfortunately, when unspecified PipeWire seems to default to
* around 40ms, which isn't great. So request 20ms instead. * around 40ms, which isn't great. So request 20ms instead.
*/ */
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->Frequency+25) / 50, pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->mSampleRate+25) / 50,
mDevice->Frequency); mDevice->mSampleRate);
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->mSampleRate);
#ifdef PW_KEY_TARGET_OBJECT #ifdef PW_KEY_TARGET_OBJECT
pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
#else #else
@ -2125,15 +2125,15 @@ void PipeWireCapture::open(std::string_view name)
mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)}; mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)};
if(!mStream) if(!mStream)
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Failed to create PipeWire stream (errno: %d)", errno}; "Failed to create PipeWire stream (errno: {})", errno};
static constexpr pw_stream_events streamEvents{CreateEvents()}; static constexpr pw_stream_events streamEvents{CreateEvents()};
pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
| PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS}; | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params.data(), 1)}) if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, &params, 1)})
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream (res: %d)", res}; "Error connecting PipeWire stream (res: {})", res};
/* Wait for the stream to become paused (ready to start streaming). */ /* Wait for the stream to become paused (ready to start streaming). */
plock.wait([stream=mStream.get()]() plock.wait([stream=mStream.get()]()
@ -2142,7 +2142,7 @@ void PipeWireCapture::open(std::string_view name)
pw_stream_state state{pw_stream_get_state(stream, &error)}; pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR) if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream: \"%s\"", error}; "Error connecting PipeWire stream: \"{}\"", error};
return state == PW_STREAM_STATE_PAUSED; return state == PW_STREAM_STATE_PAUSED;
}); });
plock.unlock(); plock.unlock();
@ -2150,7 +2150,7 @@ void PipeWireCapture::open(std::string_view name)
setDefaultWFXChannelOrder(); setDefaultWFXChannelOrder();
/* Ensure at least a 100ms capture buffer. */ /* Ensure at least a 100ms capture buffer. */
mRing = RingBuffer::Create(std::max(mDevice->Frequency/10u, mDevice->BufferSize), mRing = RingBuffer::Create(std::max(mDevice->mSampleRate/10u, mDevice->mBufferSize),
mDevice->frameSizeFromFmt(), false); mDevice->frameSizeFromFmt(), false);
} }
@ -2160,7 +2160,7 @@ void PipeWireCapture::start()
MainloopUniqueLock plock{mLoop}; MainloopUniqueLock plock{mLoop};
if(int res{pw_stream_set_active(mStream.get(), true)}) if(int res{pw_stream_set_active(mStream.get(), true)})
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start PipeWire stream (res: %d)", res}; "Failed to start PipeWire stream (res: {})", res};
plock.wait([stream=mStream.get()]() plock.wait([stream=mStream.get()]()
{ {
@ -2168,7 +2168,7 @@ void PipeWireCapture::start()
pw_stream_state state{pw_stream_get_state(stream, &error)}; pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR) if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"PipeWire stream error: %s", error ? error : "(unknown)"}; "PipeWire stream error: {}", error ? error : "(unknown)"};
return state == PW_STREAM_STATE_STREAMING; return state == PW_STREAM_STATE_STREAMING;
}); });
} }
@ -2177,7 +2177,7 @@ void PipeWireCapture::stop()
{ {
MainloopUniqueLock plock{mLoop}; MainloopUniqueLock plock{mLoop};
if(int res{pw_stream_set_active(mStream.get(), false)}) if(int res{pw_stream_set_active(mStream.get(), false)})
ERR("Failed to stop PipeWire stream (res: %d)\n", res); ERR("Failed to stop PipeWire stream (res: {})", res);
plock.wait([stream=mStream.get()]() plock.wait([stream=mStream.get()]()
{ return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
@ -2200,11 +2200,11 @@ bool PipeWireBackendFactory::init()
const char *version{pw_get_library_version()}; const char *version{pw_get_library_version()};
if(!check_version(version)) if(!check_version(version))
{ {
WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version, WARN("PipeWire version \"{}\" too old ({} or newer required)", version,
pw_get_headers_version()); pw_get_headers_version());
return false; return false;
} }
TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version()); TRACE("Found PipeWire version \"{}\" ({} or newer)", version, pw_get_headers_version());
pw_init(nullptr, nullptr); pw_init(nullptr, nullptr);
if(!gEventHandler.init()) if(!gEventHandler.init())
@ -2217,7 +2217,7 @@ bool PipeWireBackendFactory::init()
/* TODO: Temporary warning, until PipeWire gets a proper way to report /* TODO: Temporary warning, until PipeWire gets a proper way to report
* audio support. * audio support.
*/ */
WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n"); WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.");
return false; return false;
} }
return true; return true;

View file

@ -20,27 +20,25 @@
#include "config.h" #include "config.h"
#include "portaudio.h" #include "portaudio.hpp"
#include <cmath>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include "albit.h"
#include "alc/alconfig.h" #include "alc/alconfig.h"
#include "alnumeric.h"
#include "alstring.h"
#include "core/device.h" #include "core/device.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include <portaudio.h> /* NOLINT(*-duplicate-include) Not the same header. */ #include <portaudio.h>
namespace { namespace {
#ifdef HAVE_DYNLOAD #if HAVE_DYNLOAD
void *pa_handle; void *pa_handle;
#define MAKE_FUNC(x) decltype(x) * p##x #define MAKE_FUNC(x) decltype(x) * p##x
MAKE_FUNC(Pa_Initialize); MAKE_FUNC(Pa_Initialize);
@ -75,8 +73,8 @@ MAKE_FUNC(Pa_GetStreamInfo);
struct DeviceEntry { struct DeviceEntry {
std::string mName; std::string mName;
bool mIsPlayback{}; uint mPlaybackChannels{};
bool mIsCapture{}; uint mCaptureChannels{};
}; };
std::vector<DeviceEntry> DeviceNames; std::vector<DeviceEntry> DeviceNames;
@ -85,7 +83,7 @@ void EnumerateDevices()
const auto devcount = Pa_GetDeviceCount(); const auto devcount = Pa_GetDeviceCount();
if(devcount < 0) if(devcount < 0)
{ {
ERR("Error getting device count: %s\n", Pa_GetErrorText(devcount)); ERR("Error getting device count: {}", Pa_GetErrorText(devcount));
return; return;
} }
@ -95,44 +93,42 @@ void EnumerateDevices()
{ {
if(auto info = Pa_GetDeviceInfo(idx); info && info->name) if(auto info = Pa_GetDeviceInfo(idx); info && info->name)
{ {
#ifdef _WIN32
entry.mName = "OpenAL Soft on "+std::string{info->name};
#else
entry.mName = info->name; entry.mName = info->name;
#endif entry.mPlaybackChannels = static_cast<uint>(std::max(info->maxOutputChannels, 0));
entry.mIsPlayback = (info->maxOutputChannels > 0); entry.mCaptureChannels = static_cast<uint>(std::max(info->maxInputChannels, 0));
entry.mIsCapture = (info->maxInputChannels > 0); TRACE("Device {} \"{}\": {} playback, {} capture channels", idx, entry.mName,
TRACE("Device %d \"%s\": %d playback, %d capture channels\n", idx, entry.mName.c_str(),
info->maxOutputChannels, info->maxInputChannels); info->maxOutputChannels, info->maxInputChannels);
} }
++idx; ++idx;
} }
} }
struct StreamParamsExt : public PaStreamParameters { uint updateSize; };
struct PortPlayback final : public BackendBase { struct PortPlayback final : public BackendBase {
PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~PortPlayback() override; ~PortPlayback() override;
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
void createStream(PaDeviceIndex deviceid);
void open(std::string_view name) override; void open(std::string_view name) override;
bool reset() override; bool reset() override;
void start() override; void start() override;
void stop() override; void stop() override;
PaStream *mStream{nullptr}; PaStream *mStream{nullptr};
PaStreamParameters mParams{}; StreamParamsExt mParams{};
DevFmtChannels mChannelConfig{}; PaDeviceIndex mDeviceIdx{-1};
uint mAmbiOrder{};
uint mUpdateSize{0u};
}; };
PortPlayback::~PortPlayback() PortPlayback::~PortPlayback()
{ {
PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
if(err != paNoError) if(err != paNoError)
ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); ERR("Error closing stream: {}", Pa_GetErrorText(err));
mStream = nullptr; mStream = nullptr;
} }
@ -146,12 +142,62 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f
} }
void PortPlayback::createStream(PaDeviceIndex deviceid)
{
auto &devinfo = DeviceNames.at(static_cast<uint>(deviceid));
auto params = StreamParamsExt{};
params.device = deviceid;
params.suggestedLatency = mDevice->mBufferSize / static_cast<double>(mDevice->mSampleRate);
params.hostApiSpecificStreamInfo = nullptr;
params.channelCount = static_cast<int>(std::min(devinfo.mPlaybackChannels,
mDevice->channelsFromFmt()));
switch(mDevice->FmtType)
{
case DevFmtByte: params.sampleFormat = paInt8; break;
case DevFmtUByte: params.sampleFormat = paUInt8; break;
case DevFmtUShort: [[fallthrough]];
case DevFmtShort: params.sampleFormat = paInt16; break;
case DevFmtUInt: [[fallthrough]];
case DevFmtInt: params.sampleFormat = paInt32; break;
case DevFmtFloat: params.sampleFormat = paFloat32; break;
}
params.updateSize = mDevice->mUpdateSize;
auto srate = uint{mDevice->mSampleRate};
static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
};
while(PaError err{Pa_OpenStream(&mStream, nullptr, &params, srate, params.updateSize, paNoFlag,
writeCallback, this)})
{
if(params.updateSize != DefaultUpdateSize)
params.updateSize = DefaultUpdateSize;
else if(srate != 48000u)
srate = (srate != 44100u) ? 44100u : 48000u;
else if(params.sampleFormat != paInt16)
params.sampleFormat = paInt16;
else if(params.channelCount != 2)
params.channelCount = 2;
else
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: {}",
Pa_GetErrorText(err)};
}
mParams = params;
}
void PortPlayback::open(std::string_view name) void PortPlayback::open(std::string_view name)
{ {
if(DeviceNames.empty()) if(DeviceNames.empty())
EnumerateDevices(); EnumerateDevices();
int deviceid{-1}; PaDeviceIndex deviceid{-1};
if(name.empty()) if(name.empty())
{ {
if(auto devidopt = ConfigValueInt({}, "port", "device")) if(auto devidopt = ConfigValueInt({}, "port", "device"))
@ -163,101 +209,65 @@ void PortPlayback::open(std::string_view name)
else else
{ {
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
[name](const DeviceEntry &entry) { return entry.mIsPlayback && name == entry.mName; }); [name](const DeviceEntry &entry)
{ return entry.mPlaybackChannels > 0 && name == entry.mName; });
if(iter == DeviceNames.cend()) if(iter == DeviceNames.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter)); deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
} }
PaStreamParameters params{}; createStream(deviceid);
params.device = deviceid; mDeviceIdx = deviceid;
params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
params.hostApiSpecificStreamInfo = nullptr;
mChannelConfig = mDevice->FmtChans; mDeviceName = name;
mAmbiOrder = mDevice->mAmbiOrder;
params.channelCount = static_cast<int>(mDevice->channelsFromFmt());
switch(mDevice->FmtType)
{
case DevFmtByte:
params.sampleFormat = paInt8;
break;
case DevFmtUByte:
params.sampleFormat = paUInt8;
break;
case DevFmtUShort:
/* fall-through */
case DevFmtShort:
params.sampleFormat = paInt16;
break;
case DevFmtUInt:
/* fall-through */
case DevFmtInt:
params.sampleFormat = paInt32;
break;
case DevFmtFloat:
params.sampleFormat = paFloat32;
break;
}
static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
};
PaStream *stream{};
while(PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency,
mDevice->UpdateSize, paNoFlag, writeCallback, this)})
{
if(params.sampleFormat != paFloat32)
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
params.sampleFormat = paInt16;
}
Pa_CloseStream(mStream);
mStream = stream;
mParams = params;
mUpdateSize = mDevice->UpdateSize;
mDevice->DeviceName = name;
} }
bool PortPlayback::reset() bool PortPlayback::reset()
{ {
if(mStream)
{
auto err = Pa_CloseStream(mStream);
if(err != paNoError)
ERR("Error closing stream: {}", Pa_GetErrorText(err));
mStream = nullptr;
}
createStream(mDeviceIdx);
switch(mParams.sampleFormat)
{
case paFloat32: mDevice->FmtType = DevFmtFloat; break;
case paInt32: mDevice->FmtType = DevFmtInt; break;
case paInt16: mDevice->FmtType = DevFmtShort; break;
case paInt8: mDevice->FmtType = DevFmtByte; break;
case paUInt8: mDevice->FmtType = DevFmtUByte; break;
default:
ERR("Unexpected PortAudio sample format: {}", mParams.sampleFormat);
throw al::backend_exception{al::backend_error::NoDevice, "Invalid sample format: {}",
mParams.sampleFormat};
}
if(mParams.channelCount != static_cast<int>(mDevice->channelsFromFmt()))
{
if(mParams.channelCount >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(mParams.channelCount == 1)
mDevice->FmtChans = DevFmtMono;
mDevice->mAmbiOrder = 0;
}
const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)}; const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
mDevice->Frequency = static_cast<uint>(streamInfo->sampleRate); mDevice->mSampleRate = static_cast<uint>(std::lround(streamInfo->sampleRate));
mDevice->FmtChans = mChannelConfig; mDevice->mUpdateSize = mParams.updateSize;
mDevice->mAmbiOrder = mAmbiOrder; mDevice->mBufferSize = mDevice->mUpdateSize * 2;
mDevice->UpdateSize = mUpdateSize;
mDevice->BufferSize = mUpdateSize * 2;
if(streamInfo->outputLatency > 0.0f) if(streamInfo->outputLatency > 0.0f)
{ {
const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate}; const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate};
TRACE("Reported stream latency: %f sec (%f samples)\n", streamInfo->outputLatency, TRACE("Reported stream latency: {:f} sec ({:f} samples)", streamInfo->outputLatency,
sampleLatency); sampleLatency);
mDevice->BufferSize = static_cast<uint>(std::clamp(sampleLatency, mDevice->mBufferSize = static_cast<uint>(std::clamp(sampleLatency,
double(mDevice->BufferSize), double{std::numeric_limits<int>::max()})); double(mDevice->mBufferSize), double{std::numeric_limits<int>::max()}));
}
if(mParams.sampleFormat == paInt8)
mDevice->FmtType = DevFmtByte;
else if(mParams.sampleFormat == paUInt8)
mDevice->FmtType = DevFmtUByte;
else if(mParams.sampleFormat == paInt16)
mDevice->FmtType = DevFmtShort;
else if(mParams.sampleFormat == paInt32)
mDevice->FmtType = DevFmtInt;
else if(mParams.sampleFormat == paFloat32)
mDevice->FmtType = DevFmtFloat;
else
{
ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
return false;
} }
setDefaultChannelOrder(); setDefaultChannelOrder();
@ -268,19 +278,19 @@ bool PortPlayback::reset()
void PortPlayback::start() void PortPlayback::start()
{ {
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s", throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: {}",
Pa_GetErrorText(err)}; Pa_GetErrorText(err)};
} }
void PortPlayback::stop() void PortPlayback::stop()
{ {
if(PaError err{Pa_StopStream(mStream)}; err != paNoError) if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); ERR("Error stopping stream: {}", Pa_GetErrorText(err));
} }
struct PortCapture final : public BackendBase { struct PortCapture final : public BackendBase {
PortCapture(DeviceBase *device) noexcept : BackendBase{device} { } explicit PortCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~PortCapture() override; ~PortCapture() override;
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
@ -302,7 +312,7 @@ PortCapture::~PortCapture()
{ {
PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
if(err != paNoError) if(err != paNoError)
ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); ERR("Error closing stream: {}", Pa_GetErrorText(err));
mStream = nullptr; mStream = nullptr;
} }
@ -332,14 +342,15 @@ void PortCapture::open(std::string_view name)
else else
{ {
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
[name](const DeviceEntry &entry) { return entry.mIsCapture && name == entry.mName; }); [name](const DeviceEntry &entry)
{ return entry.mCaptureChannels > 0 && name == entry.mName; });
if(iter == DeviceNames.cend()) if(iter == DeviceNames.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter)); deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
} }
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)};
const uint frame_size{mDevice->frameSizeFromFmt()}; const uint frame_size{mDevice->frameSizeFromFmt()};
mRing = RingBuffer::Create(samples, frame_size, false); mRing = RingBuffer::Create(samples, frame_size, false);
@ -367,7 +378,7 @@ void PortCapture::open(std::string_view name)
break; break;
case DevFmtUInt: case DevFmtUInt:
case DevFmtUShort: case DevFmtUShort:
throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported", throw al::backend_exception{al::backend_error::DeviceError, "{} samples not supported",
DevFmtTypeString(mDevice->FmtType)}; DevFmtTypeString(mDevice->FmtType)};
} }
mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt()); mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
@ -379,13 +390,13 @@ void PortCapture::open(std::string_view name)
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer, return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags); framesPerBuffer, timeInfo, statusFlags);
}; };
PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency, PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->mSampleRate,
paFramesPerBufferUnspecified, paNoFlag, readCallback, this)}; paFramesPerBufferUnspecified, paNoFlag, readCallback, this)};
if(err != paNoError) if(err != paNoError)
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: {}",
Pa_GetErrorText(err)}; Pa_GetErrorText(err)};
mDevice->DeviceName = name; mDeviceName = name;
} }
@ -393,13 +404,13 @@ void PortCapture::start()
{ {
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording: %s", Pa_GetErrorText(err)}; "Failed to start recording: {}", Pa_GetErrorText(err)};
} }
void PortCapture::stop() void PortCapture::stop()
{ {
if(PaError err{Pa_StopStream(mStream)}; err != paNoError) if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); ERR("Error stopping stream: {}", Pa_GetErrorText(err));
} }
@ -414,7 +425,7 @@ void PortCapture::captureSamples(std::byte *buffer, uint samples)
bool PortBackendFactory::init() bool PortBackendFactory::init()
{ {
#ifdef HAVE_DYNLOAD #if HAVE_DYNLOAD
if(!pa_handle) if(!pa_handle)
{ {
#ifdef _WIN32 #ifdef _WIN32
@ -457,7 +468,7 @@ bool PortBackendFactory::init()
const PaError err{Pa_Initialize()}; const PaError err{Pa_Initialize()};
if(err != paNoError) if(err != paNoError)
{ {
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err));
CloseLib(pa_handle); CloseLib(pa_handle);
pa_handle = nullptr; pa_handle = nullptr;
return false; return false;
@ -467,7 +478,7 @@ bool PortBackendFactory::init()
const PaError err{Pa_Initialize()}; const PaError err{Pa_Initialize()};
if(err != paNoError) if(err != paNoError)
{ {
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err));
return false; return false;
} }
#endif #endif
@ -493,7 +504,7 @@ auto PortBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
for(size_t i{0};i < DeviceNames.size();++i) for(size_t i{0};i < DeviceNames.size();++i)
{ {
if(DeviceNames[i].mIsPlayback) if(DeviceNames[i].mPlaybackChannels > 0)
{ {
if(defaultid >= 0 && static_cast<uint>(defaultid) == i) if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
devices.emplace(devices.cbegin(), DeviceNames[i].mName); devices.emplace(devices.cbegin(), DeviceNames[i].mName);
@ -511,7 +522,7 @@ auto PortBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
for(size_t i{0};i < DeviceNames.size();++i) for(size_t i{0};i < DeviceNames.size();++i)
{ {
if(DeviceNames[i].mIsCapture) if(DeviceNames[i].mCaptureChannels > 0)
{ {
if(defaultid >= 0 && static_cast<uint>(defaultid) == i) if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
devices.emplace(devices.cbegin(), DeviceNames[i].mName); devices.emplace(devices.cbegin(), DeviceNames[i].mName);

View file

@ -1,5 +1,5 @@
#ifndef BACKENDS_PORTAUDIO_H #ifndef BACKENDS_PORTAUDIO_HPP
#define BACKENDS_PORTAUDIO_H #define BACKENDS_PORTAUDIO_HPP
#include "base.h" #include "base.h"
@ -16,4 +16,4 @@ public:
static auto getFactory() -> BackendFactory&; static auto getFactory() -> BackendFactory&;
}; };
#endif /* BACKENDS_PORTAUDIO_H */ #endif /* BACKENDS_PORTAUDIO_HPP */

View file

@ -32,7 +32,6 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <exception>
#include <limits> #include <limits>
#include <mutex> #include <mutex>
#include <optional> #include <optional>
@ -43,13 +42,14 @@
#include <vector> #include <vector>
#include "alc/alconfig.h" #include "alc/alconfig.h"
#include "alnumeric.h"
#include "alspan.h" #include "alspan.h"
#include "alstring.h"
#include "base.h" #include "base.h"
#include "core/devformat.h" #include "core/devformat.h"
#include "core/device.h" #include "core/device.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "fmt/core.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "strutils.h" #include "strutils.h"
@ -58,9 +58,10 @@
namespace { namespace {
using namespace std::string_view_literals;
using uint = unsigned int; using uint = unsigned int;
#ifdef HAVE_DYNLOAD #if HAVE_DYNLOAD
#define PULSE_FUNCS(MAGIC) \ #define PULSE_FUNCS(MAGIC) \
MAGIC(pa_context_new); \ MAGIC(pa_context_new); \
MAGIC(pa_context_unref); \ MAGIC(pa_context_unref); \
@ -72,8 +73,10 @@ using uint = unsigned int;
MAGIC(pa_context_errno); \ MAGIC(pa_context_errno); \
MAGIC(pa_context_connect); \ MAGIC(pa_context_connect); \
MAGIC(pa_context_get_server_info); \ MAGIC(pa_context_get_server_info); \
MAGIC(pa_context_get_sink_info_by_index); \
MAGIC(pa_context_get_sink_info_by_name); \ MAGIC(pa_context_get_sink_info_by_name); \
MAGIC(pa_context_get_sink_info_list); \ MAGIC(pa_context_get_sink_info_list); \
MAGIC(pa_context_get_source_info_by_index); \
MAGIC(pa_context_get_source_info_by_name); \ MAGIC(pa_context_get_source_info_by_name); \
MAGIC(pa_context_get_source_info_list); \ MAGIC(pa_context_get_source_info_list); \
MAGIC(pa_stream_new); \ MAGIC(pa_stream_new); \
@ -145,8 +148,10 @@ PULSE_FUNCS(MAKE_FUNC)
#define pa_context_errno ppa_context_errno #define pa_context_errno ppa_context_errno
#define pa_context_connect ppa_context_connect #define pa_context_connect ppa_context_connect
#define pa_context_get_server_info ppa_context_get_server_info #define pa_context_get_server_info ppa_context_get_server_info
#define pa_context_get_sink_info_by_index ppa_context_get_sink_info_by_index
#define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name #define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name
#define pa_context_get_sink_info_list ppa_context_get_sink_info_list #define pa_context_get_sink_info_list ppa_context_get_sink_info_list
#define pa_context_get_source_info_by_index ppa_context_get_source_info_by_index
#define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name #define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name
#define pa_context_get_source_info_list ppa_context_get_source_info_list #define pa_context_get_source_info_list ppa_context_get_source_info_list
#define pa_stream_new ppa_stream_new #define pa_stream_new ppa_stream_new
@ -252,7 +257,7 @@ constexpr pa_channel_map MonoChanMap{
}; };
/* *grumble* Don't use enums for bitflags. */ /* NOLINTBEGIN(*EnumCastOutOfRange) *grumble* Don't use enums for bitflags. */
constexpr pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) constexpr pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs)
{ return pa_stream_flags_t(lhs | al::to_underlying(rhs)); } { return pa_stream_flags_t(lhs | al::to_underlying(rhs)); }
constexpr pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) constexpr pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs)
@ -278,11 +283,13 @@ constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_fla
constexpr pa_subscription_mask_t operator|(pa_subscription_mask_t lhs, pa_subscription_mask_t rhs) constexpr pa_subscription_mask_t operator|(pa_subscription_mask_t lhs, pa_subscription_mask_t rhs)
{ return pa_subscription_mask_t(lhs | al::to_underlying(rhs)); } { return pa_subscription_mask_t(lhs | al::to_underlying(rhs)); }
/* NOLINTEND(*EnumCastOutOfRange) */
struct DevMap { struct DevMap {
std::string name; std::string name;
std::string device_name; std::string device_name;
uint32_t index{};
}; };
bool checkName(const al::span<const DevMap> list, const std::string &name) bool checkName(const al::span<const DevMap> list, const std::string &name)
@ -294,6 +301,9 @@ bool checkName(const al::span<const DevMap> list, const std::string &name)
std::vector<DevMap> PlaybackDevices; std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices; std::vector<DevMap> CaptureDevices;
std::string DefaultPlaybackDevName;
std::string DefaultCaptureDevName;
/* Global flags and properties */ /* Global flags and properties */
pa_context_flags_t pulse_ctx_flags; pa_context_flags_t pulse_ctx_flags;
@ -344,6 +354,32 @@ public:
void close(pa_stream *stream=nullptr); void close(pa_stream *stream=nullptr);
void updateDefaultDevice(pa_context*, const pa_server_info *info) const
{
auto default_sink = info->default_sink_name ? std::string_view{info->default_sink_name}
: std::string_view{};
auto default_src = info->default_source_name ? std::string_view{info->default_source_name}
: std::string_view{};
if(default_sink != DefaultPlaybackDevName)
{
TRACE("Default playback device: {}", default_sink);
DefaultPlaybackDevName = default_sink;
const auto msg = fmt::format("Default playback device changed: {}", default_sink);
alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg);
}
if(default_src != DefaultCaptureDevName)
{
TRACE("Default capture device: {}", default_src);
DefaultCaptureDevName = default_src;
const auto msg = fmt::format("Default capture device changed: {}", default_src);
alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg);
}
signal();
}
void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) const noexcept void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) const noexcept
{ {
if(eol) if(eol)
@ -361,18 +397,18 @@ public:
/* Make sure the display name (description) is unique. Append a number /* Make sure the display name (description) is unique. Append a number
* counter as needed. * counter as needed.
*/ */
int count{1}; auto count = 1;
std::string newname{info->description}; auto newname = std::string{info->description};
while(checkName(PlaybackDevices, newname)) while(checkName(PlaybackDevices, newname))
{ newname = fmt::format("{} #{}", info->description, ++count);
newname = info->description;
newname += " #";
newname += std::to_string(++count);
}
PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name});
DevMap &newentry = PlaybackDevices.back();
TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); const auto &newentry = PlaybackDevices.emplace_back(DevMap{std::move(newname),
info->name, info->index});
TRACE("Got device \"{}\", \"{}\" ({})", newentry.name, newentry.device_name,
newentry.index);
const auto msg = fmt::format("Device added: {}", newentry.device_name);
alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Playback, msg);
} }
void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) const noexcept void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) const noexcept
@ -392,22 +428,77 @@ public:
/* Make sure the display name (description) is unique. Append a number /* Make sure the display name (description) is unique. Append a number
* counter as needed. * counter as needed.
*/ */
int count{1}; auto count = 1;
std::string newname{info->description}; auto newname = std::string{info->description};
while(checkName(CaptureDevices, newname)) while(checkName(CaptureDevices, newname))
{ newname = fmt::format("{} #{}", info->description, ++count);
newname = info->description;
newname += " #";
newname += std::to_string(++count);
}
CaptureDevices.emplace_back(DevMap{std::move(newname), info->name});
DevMap &newentry = CaptureDevices.back();
TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); const auto &newentry = CaptureDevices.emplace_back(DevMap{std::move(newname), info->name,
info->index});
TRACE("Got device \"{}\", \"{}\" ({})", newentry.name, newentry.device_name,
newentry.index);
const auto msg = fmt::format("Device added: {}", newentry.device_name);
alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Capture, msg);
} }
void probePlaybackDevices(); void eventCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t idx) noexcept
void probeCaptureDevices(); {
const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK);
const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
if(eventFacility == PA_SUBSCRIPTION_EVENT_SERVER
&& eventType == PA_SUBSCRIPTION_EVENT_CHANGE)
{
static constexpr auto server_cb = [](pa_context *ctx, const pa_server_info *info,
void *pdata) noexcept
{ return static_cast<PulseMainloop*>(pdata)->updateDefaultDevice(ctx, info); };
auto *op = pa_context_get_server_info(context, server_cb, this);
if(op) pa_operation_unref(op);
}
if(eventFacility != PA_SUBSCRIPTION_EVENT_SINK
&& eventFacility != PA_SUBSCRIPTION_EVENT_SOURCE)
return;
const auto devtype = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK)
? alc::DeviceType::Playback : alc::DeviceType::Capture;
if(eventType == PA_SUBSCRIPTION_EVENT_NEW)
{
if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK)
{
static constexpr auto devcallback = [](pa_context *ctx, const pa_sink_info *info,
int eol, void *pdata) noexcept
{ return static_cast<PulseMainloop*>(pdata)->deviceSinkCallback(ctx, info, eol); };
auto *op = pa_context_get_sink_info_by_index(context, idx, devcallback, this);
if(op) pa_operation_unref(op);
}
else
{
static constexpr auto devcallback = [](pa_context *ctx, const pa_source_info *info,
int eol, void *pdata) noexcept
{ return static_cast<PulseMainloop*>(pdata)->deviceSourceCallback(ctx,info,eol); };
auto *op = pa_context_get_source_info_by_index(context, idx, devcallback, this);
if(op) pa_operation_unref(op);
}
}
else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE)
{
auto find_index = [idx](const DevMap &entry) noexcept { return entry.index == idx; };
auto &devlist = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK)
? PlaybackDevices : CaptureDevices;
auto iter = std::find_if(devlist.cbegin(), devlist.cend(), find_index);
if(iter != devlist.cend())
{
devlist.erase(iter);
const auto msg = fmt::format("Device removed: {}", idx);
alc::Event(alc::EventType::DeviceRemoved, devtype, msg);
}
}
}
friend struct MainloopUniqueLock; friend struct MainloopUniqueLock;
}; };
@ -434,36 +525,38 @@ struct MainloopUniqueLock : public std::unique_lock<PulseMainloop> {
void setEventHandler() void setEventHandler()
{ {
pa_operation *op{pa_context_subscribe(mutex()->mContext, auto *context = mutex()->mContext;
PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE,
[](pa_context*, int, void *pdata) noexcept /* Watch for device added/removed and server changed events. */
{ static_cast<PulseMainloop*>(pdata)->signal(); }, static constexpr auto submask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE
mutex())}; | PA_SUBSCRIPTION_MASK_SERVER;
static constexpr auto do_signal = [](pa_context*, int, void *pdata) noexcept
{ static_cast<PulseMainloop*>(pdata)->signal(); };
auto *op = pa_context_subscribe(context, submask, do_signal, mutex());
waitForOperation(op); waitForOperation(op);
/* Watch for device added/removed events. static constexpr auto handler = [](pa_context *ctx, pa_subscription_event_type_t t,
* uint32_t index, void *pdata) noexcept
* TODO: Also track the "default" device, in as much as PulseAudio has { return static_cast<PulseMainloop*>(pdata)->eventCallback(ctx, t, index); };
* the concept of a default device (whatever device is opened when not pa_context_set_subscribe_callback(context, handler, mutex());
* specifying a specific sink or source name). There doesn't seem to be
* an event for this. /* Fill in the initial device lists, and get the defaults. */
*/ auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept
auto handler = [](pa_context*, pa_subscription_event_type_t t, uint32_t, void*) noexcept { return static_cast<PulseMainloop*>(pdata)->deviceSinkCallback(ctx, info, eol); };
{
const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept
if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK { return static_cast<PulseMainloop*>(pdata)->deviceSourceCallback(ctx, info, eol); };
|| eventFacility == PA_SUBSCRIPTION_EVENT_SOURCE)
{ auto server_callback = [](pa_context *ctx, const pa_server_info *info, void *pdata) noexcept
const auto deviceType = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) { return static_cast<PulseMainloop*>(pdata)->updateDefaultDevice(ctx, info); };
? alc::DeviceType::Playback : alc::DeviceType::Capture;
const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); auto *sinkop = pa_context_get_sink_info_list(context, sink_callback, mutex());
if(eventType == PA_SUBSCRIPTION_EVENT_NEW) auto *srcop = pa_context_get_source_info_list(context, src_callback, mutex());
alc::Event(alc::EventType::DeviceAdded, deviceType, "Device added"); auto *serverop = pa_context_get_server_info(context, server_callback, mutex());
else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE)
alc::Event(alc::EventType::DeviceRemoved, deviceType, "Device removed"); waitForOperation(sinkop);
} waitForOperation(srcop);
}; waitForOperation(serverop);
pa_context_set_subscribe_callback(mutex()->mContext, handler, nullptr);
} }
@ -484,6 +577,13 @@ struct MainloopUniqueLock : public std::unique_lock<PulseMainloop> {
void connectContext(); void connectContext();
pa_stream *connectStream(const char *device_name, pa_stream_flags_t flags, pa_stream *connectStream(const char *device_name, pa_stream_flags_t flags,
pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type); pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type);
pa_stream *connectStream(const std::string &device_name, pa_stream_flags_t flags,
pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type)
{
return connectStream(device_name.empty() ? nullptr : device_name.c_str(), flags, attr,
spec, chanmap, type);
}
}; };
using MainloopLockGuard = std::lock_guard<PulseMainloop>; using MainloopLockGuard = std::lock_guard<PulseMainloop>;
@ -533,7 +633,7 @@ void MainloopUniqueLock::connectContext()
{ {
pa_context_unref(mutex()->mContext); pa_context_unref(mutex()->mContext);
mutex()->mContext = nullptr; mutex()->mContext = nullptr;
throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)", throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect ({})",
pa_strerror(err)}; pa_strerror(err)};
} }
} }
@ -544,7 +644,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_
const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"}; const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"};
pa_stream *stream{pa_stream_new(mutex()->mContext, stream_id, spec, chanmap)}; pa_stream *stream{pa_stream_new(mutex()->mContext, stream_id, spec, chanmap)};
if(!stream) if(!stream)
throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)", throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed ({})",
pa_strerror(pa_context_errno(mutex()->mContext))}; pa_strerror(pa_context_errno(mutex()->mContext))};
pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept
@ -556,7 +656,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_
if(err < 0) if(err < 0)
{ {
pa_stream_unref(stream); pa_stream_unref(stream);
throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect (%s)", throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect ({})",
stream_id, pa_strerror(err)}; stream_id, pa_strerror(err)};
} }
@ -568,7 +668,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_
err = pa_context_errno(mutex()->mContext); err = pa_context_errno(mutex()->mContext);
pa_stream_unref(stream); pa_stream_unref(stream);
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"%s did not get ready (%s)", stream_id, pa_strerror(err)}; "{} did not get ready ({})", stream_id, pa_strerror(err)};
} }
return state == PA_STREAM_READY; return state == PA_STREAM_READY;
}); });
@ -593,56 +693,12 @@ void PulseMainloop::close(pa_stream *stream)
} }
void PulseMainloop::probePlaybackDevices()
{
PlaybackDevices.clear();
try {
MainloopUniqueLock plock{*this};
plock.connectContext();
auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept
{ return static_cast<PulseMainloop*>(pdata)->deviceSinkCallback(ctx, info, eol); };
pa_operation *op{pa_context_get_sink_info_by_name(mContext, nullptr, sink_callback, this)};
plock.waitForOperation(op);
op = pa_context_get_sink_info_list(mContext, sink_callback, this);
plock.waitForOperation(op);
}
catch(std::exception &e) {
ERR("Error enumerating devices: %s\n", e.what());
}
}
void PulseMainloop::probeCaptureDevices()
{
CaptureDevices.clear();
try {
MainloopUniqueLock plock{*this};
plock.connectContext();
auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept
{ return static_cast<PulseMainloop*>(pdata)->deviceSourceCallback(ctx, info, eol); };
pa_operation *op{pa_context_get_source_info_by_name(mContext, nullptr, src_callback,
this)};
plock.waitForOperation(op);
op = pa_context_get_source_info_list(mContext, src_callback, this);
plock.waitForOperation(op);
}
catch(std::exception &e) {
ERR("Error enumerating devices: %s\n", e.what());
}
}
/* Used for initial connection test and enumeration. */ /* Used for initial connection test and enumeration. */
PulseMainloop gGlobalMainloop; PulseMainloop gGlobalMainloop;
struct PulsePlayback final : public BackendBase { struct PulsePlayback final : public BackendBase {
PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~PulsePlayback() override; ~PulsePlayback() override;
void bufferAttrCallback(pa_stream *stream) noexcept; void bufferAttrCallback(pa_stream *stream) noexcept;
@ -660,7 +716,7 @@ struct PulsePlayback final : public BackendBase {
PulseMainloop mMainloop; PulseMainloop mMainloop;
std::optional<std::string> mDeviceName{std::nullopt}; std::optional<std::string> mDeviceId{std::nullopt};
bool mIs51Rear{false}; bool mIs51Rear{false};
pa_buffer_attr mAttr{}; pa_buffer_attr mAttr{};
@ -683,14 +739,14 @@ void PulsePlayback::bufferAttrCallback(pa_stream *stream) noexcept
* leaving it alone means ALC_REFRESH will be off. * leaving it alone means ALC_REFRESH will be off.
*/ */
mAttr = *(pa_stream_get_buffer_attr(stream)); mAttr = *(pa_stream_get_buffer_attr(stream));
TRACE("minreq=%d, tlength=%d, prebuf=%d\n", mAttr.minreq, mAttr.tlength, mAttr.prebuf); TRACE("minreq={}, tlength={}, prebuf={}", mAttr.minreq, mAttr.tlength, mAttr.prebuf);
} }
void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept
{ {
if(pa_stream_get_state(stream) == PA_STREAM_FAILED) if(pa_stream_get_state(stream) == PA_STREAM_FAILED)
{ {
ERR("Received stream failure!\n"); ERR("Received stream failure!");
mDevice->handleDisconnect("Playback stream failure"); mDevice->handleDisconnect("Playback stream failure");
} }
mMainloop.signal(); mMainloop.signal();
@ -716,7 +772,7 @@ void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexce
int ret{pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE)}; int ret{pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE)};
if(ret != PA_OK) UNLIKELY if(ret != PA_OK) UNLIKELY
ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret)); ERR("Failed to write to stream: {}, {}", ret, pa_strerror(ret));
} while(nbytes > 0); } while(nbytes > 0);
} }
@ -759,11 +815,11 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
mIs51Rear = false; mIs51Rear = false;
std::array<char,PA_CHANNEL_MAP_SNPRINT_MAX> chanmap_str{}; std::array<char,PA_CHANNEL_MAP_SNPRINT_MAX> chanmap_str{};
pa_channel_map_snprint(chanmap_str.data(), chanmap_str.size(), &info->channel_map); pa_channel_map_snprint(chanmap_str.data(), chanmap_str.size(), &info->channel_map);
WARN("Failed to find format for channel map:\n %s\n", chanmap_str.data()); WARN("Failed to find format for channel map:\n {}", chanmap_str.data());
} }
if(info->active_port) if(info->active_port)
TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description); TRACE("Active port: {} ({})", info->active_port->name, info->active_port->description);
mDevice->Flags.set(DirectEar, (info->active_port mDevice->Flags.set(DirectEar, (info->active_port
&& strcmp(info->active_port->name, "analog-output-headphones") == 0)); && strcmp(info->active_port->name, "analog-output-headphones") == 0));
} }
@ -775,13 +831,13 @@ void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int
mMainloop.signal(); mMainloop.signal();
return; return;
} }
mDevice->DeviceName = info->description; mDeviceName = info->description;
} }
void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept
{ {
mDeviceName = pa_stream_get_device_name(stream); mDeviceId = pa_stream_get_device_name(stream);
TRACE("Stream moved to %s\n", mDeviceName->c_str()); TRACE("Stream moved to {}", *mDeviceId);
} }
@ -792,22 +848,20 @@ void PulsePlayback::open(std::string_view name)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start device mainloop"}; "Failed to start device mainloop"};
const char *pulse_name{nullptr}; auto pulse_name = std::string{};
std::string_view display_name;
if(!name.empty()) if(!name.empty())
{ {
if(PlaybackDevices.empty())
mMainloop.probePlaybackDevices();
auto match_name = [name](const DevMap &entry) -> bool auto match_name = [name](const DevMap &entry) -> bool
{ return entry.name == name || entry.device_name == name; }; { return entry.name == name || entry.device_name == name; };
auto plock = MainloopUniqueLock{gGlobalMainloop};
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_name); auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_name);
if(iter == PlaybackDevices.cend()) if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
pulse_name = iter->device_name.c_str(); pulse_name = iter->device_name;
display_name = iter->name; mDeviceName = iter->name;
} }
MainloopUniqueLock plock{mMainloop}; MainloopUniqueLock plock{mMainloop};
@ -823,38 +877,38 @@ void PulsePlayback::open(std::string_view name)
spec.rate = 44100; spec.rate = 44100;
spec.channels = 2; spec.channels = 2;
if(!pulse_name) if(pulse_name.empty())
{ {
static const auto defname = al::getenv("ALSOFT_PULSE_DEFAULT"); static const auto defname = al::getenv("ALSOFT_PULSE_DEFAULT");
if(defname) pulse_name = defname->c_str(); if(defname) pulse_name = *defname;
} }
TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); TRACE("Connecting to \"{}\"", pulse_name.empty() ? "(default)"sv:std::string_view{pulse_name});
mStream = plock.connectStream(pulse_name, flags, nullptr, &spec, nullptr, mStream = plock.connectStream(pulse_name, flags, nullptr, &spec, nullptr,
BackendType::Playback); BackendType::Playback);
constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept static constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept
{ return static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); }; { return static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); };
pa_stream_set_moved_callback(mStream, move_callback, this); pa_stream_set_moved_callback(mStream, move_callback, this);
mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream))); mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream)));
if(pulse_name) mDeviceName.emplace(pulse_name); if(!pulse_name.empty())
else mDeviceName.reset(); mDeviceId.emplace(std::move(pulse_name));
if(display_name.empty())
if(mDeviceName.empty())
{ {
auto name_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept static constexpr auto name_callback = [](pa_context *context, const pa_sink_info *info,
int eol, void *pdata) noexcept
{ return static_cast<PulsePlayback*>(pdata)->sinkNameCallback(context, info, eol); }; { return static_cast<PulsePlayback*>(pdata)->sinkNameCallback(context, info, eol); };
pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(),
pa_stream_get_device_name(mStream), name_callback, this)}; pa_stream_get_device_name(mStream), name_callback, this)};
plock.waitForOperation(op); plock.waitForOperation(op);
} }
else
mDevice->DeviceName = display_name;
} }
bool PulsePlayback::reset() bool PulsePlayback::reset()
{ {
MainloopUniqueLock plock{mMainloop}; MainloopUniqueLock plock{mMainloop};
const auto deviceName = mDeviceName ? mDeviceName->c_str() : nullptr; const auto deviceName = mDeviceId ? mDeviceId->c_str() : nullptr;
if(mStream) if(mStream)
{ {
@ -877,7 +931,7 @@ bool PulsePlayback::reset()
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS}; PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS};
if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) if(!GetConfigValueBool({}, "pulse", "allow-moves", true))
flags |= PA_STREAM_DONT_MOVE; flags |= PA_STREAM_DONT_MOVE;
if(GetConfigValueBool(mDevice->DeviceName, "pulse", "adjust-latency", false)) if(GetConfigValueBool(mDevice->mDeviceName, "pulse", "adjust-latency", false))
{ {
/* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some /* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some
* reason. So if the user wants to adjust the overall device latency, * reason. So if the user wants to adjust the overall device latency,
@ -886,7 +940,7 @@ bool PulsePlayback::reset()
flags &= ~PA_STREAM_EARLY_REQUESTS; flags &= ~PA_STREAM_EARLY_REQUESTS;
flags |= PA_STREAM_ADJUST_LATENCY; flags |= PA_STREAM_ADJUST_LATENCY;
} }
if(GetConfigValueBool(mDevice->DeviceName, "pulse", "fix-rate", false) if(GetConfigValueBool(mDevice->mDeviceName, "pulse", "fix-rate", false)
|| !mDevice->Flags.test(FrequencyRequest)) || !mDevice->Flags.test(FrequencyRequest))
flags |= PA_STREAM_FIX_RATE; flags |= PA_STREAM_FIX_RATE;
@ -948,16 +1002,16 @@ bool PulsePlayback::reset()
mSpec.format = PA_SAMPLE_FLOAT32NE; mSpec.format = PA_SAMPLE_FLOAT32NE;
break; break;
} }
mSpec.rate = mDevice->Frequency; mSpec.rate = mDevice->mSampleRate;
mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt()); mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt());
if(pa_sample_spec_valid(&mSpec) == 0) if(pa_sample_spec_valid(&mSpec) == 0)
throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample spec"}; throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample spec"};
const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec)); const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec));
mAttr.maxlength = ~0u; mAttr.maxlength = ~0u;
mAttr.tlength = mDevice->BufferSize * frame_size; mAttr.tlength = mDevice->mBufferSize * frame_size;
mAttr.prebuf = 0u; mAttr.prebuf = 0u;
mAttr.minreq = mDevice->UpdateSize * frame_size; mAttr.minreq = mDevice->mUpdateSize * frame_size;
mAttr.fragsize = ~0u; mAttr.fragsize = ~0u;
mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap, mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap,
@ -974,15 +1028,15 @@ bool PulsePlayback::reset()
mSpec = *(pa_stream_get_sample_spec(mStream)); mSpec = *(pa_stream_get_sample_spec(mStream));
mFrameSize = static_cast<uint>(pa_frame_size(&mSpec)); mFrameSize = static_cast<uint>(pa_frame_size(&mSpec));
if(mDevice->Frequency != mSpec.rate) if(mDevice->mSampleRate != mSpec.rate)
{ {
/* Server updated our playback rate, so modify the buffer attribs /* Server updated our playback rate, so modify the buffer attribs
* accordingly. * accordingly.
*/ */
const auto scale = static_cast<double>(mSpec.rate) / mDevice->Frequency; const auto scale = static_cast<double>(mSpec.rate) / mDevice->mSampleRate;
const auto perlen = std::clamp(std::round(scale*mDevice->UpdateSize), 64.0, 8192.0); const auto perlen = std::clamp(std::round(scale*mDevice->mUpdateSize), 64.0, 8192.0);
const auto bufmax = uint{std::numeric_limits<int>::max()} / mFrameSize; const auto bufmax = uint{std::numeric_limits<int>::max()} / mFrameSize;
const auto buflen = std::clamp(std::round(scale*mDevice->BufferSize), perlen*2.0, const auto buflen = std::clamp(std::round(scale*mDevice->mBufferSize), perlen*2.0,
static_cast<double>(bufmax)); static_cast<double>(bufmax));
mAttr.maxlength = ~0u; mAttr.maxlength = ~0u;
@ -994,7 +1048,7 @@ bool PulsePlayback::reset()
&mMainloop); &mMainloop);
plock.waitForOperation(op); plock.waitForOperation(op);
mDevice->Frequency = mSpec.rate; mDevice->mSampleRate = mSpec.rate;
} }
constexpr auto attr_callback = [](pa_stream *stream, void *pdata) noexcept constexpr auto attr_callback = [](pa_stream *stream, void *pdata) noexcept
@ -1002,8 +1056,8 @@ bool PulsePlayback::reset()
pa_stream_set_buffer_attr_callback(mStream, attr_callback, this); pa_stream_set_buffer_attr_callback(mStream, attr_callback, this);
bufferAttrCallback(mStream); bufferAttrCallback(mStream);
mDevice->BufferSize = mAttr.tlength / mFrameSize; mDevice->mBufferSize = mAttr.tlength / mFrameSize;
mDevice->UpdateSize = mAttr.minreq / mFrameSize; mDevice->mUpdateSize = mAttr.minreq / mFrameSize;
return true; return true;
} }
@ -1061,8 +1115,8 @@ ClockLatency PulsePlayback::getClockLatency()
* server yet. Give a generic value since nothing better is available. * server yet. Give a generic value since nothing better is available.
*/ */
if(err != -PA_ERR_NODATA) if(err != -PA_ERR_NODATA)
ERR("Failed to get stream latency: 0x%x\n", err); ERR("Failed to get stream latency: {:#x}", as_unsigned(err));
latency = mDevice->BufferSize - mDevice->UpdateSize; latency = mDevice->mBufferSize - mDevice->mUpdateSize;
neg = 0; neg = 0;
} }
else if(neg) UNLIKELY else if(neg) UNLIKELY
@ -1074,7 +1128,7 @@ ClockLatency PulsePlayback::getClockLatency()
struct PulseCapture final : public BackendBase { struct PulseCapture final : public BackendBase {
PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { } explicit PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~PulseCapture() override; ~PulseCapture() override;
void streamStateCallback(pa_stream *stream) noexcept; void streamStateCallback(pa_stream *stream) noexcept;
@ -1090,7 +1144,7 @@ struct PulseCapture final : public BackendBase {
PulseMainloop mMainloop; PulseMainloop mMainloop;
std::optional<std::string> mDeviceName{std::nullopt}; std::optional<std::string> mDeviceId{std::nullopt};
al::span<const std::byte> mCapBuffer; al::span<const std::byte> mCapBuffer;
size_t mHoleLength{0}; size_t mHoleLength{0};
@ -1113,7 +1167,7 @@ void PulseCapture::streamStateCallback(pa_stream *stream) noexcept
{ {
if(pa_stream_get_state(stream) == PA_STREAM_FAILED) if(pa_stream_get_state(stream) == PA_STREAM_FAILED)
{ {
ERR("Received stream failure!\n"); ERR("Received stream failure!");
mDevice->handleDisconnect("Capture stream failure"); mDevice->handleDisconnect("Capture stream failure");
} }
mMainloop.signal(); mMainloop.signal();
@ -1126,13 +1180,13 @@ void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, i
mMainloop.signal(); mMainloop.signal();
return; return;
} }
mDevice->DeviceName = info->description; mDeviceName = info->description;
} }
void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept
{ {
mDeviceName = pa_stream_get_device_name(stream); mDeviceId = pa_stream_get_device_name(stream);
TRACE("Stream moved to %s\n", mDeviceName->c_str()); TRACE("Stream moved to {}", *mDeviceId);
} }
@ -1146,21 +1200,20 @@ void PulseCapture::open(std::string_view name)
"Failed to start device mainloop"}; "Failed to start device mainloop"};
} }
const char *pulse_name{nullptr}; auto pulse_name = std::string{};
if(!name.empty()) if(!name.empty())
{ {
if(CaptureDevices.empty())
mMainloop.probeCaptureDevices();
auto match_name = [name](const DevMap &entry) -> bool auto match_name = [name](const DevMap &entry) -> bool
{ return entry.name == name || entry.device_name == name; }; { return entry.name == name || entry.device_name == name; };
auto plock = MainloopUniqueLock{gGlobalMainloop};
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_name); auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_name);
if(iter == CaptureDevices.cend()) if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()}; "Device name \"{}\" not found", name};
pulse_name = iter->device_name.c_str(); pulse_name = iter->device_name;
mDevice->DeviceName = iter->name; mDeviceName = iter->name;
} }
MainloopUniqueLock plock{mMainloop}; MainloopUniqueLock plock{mMainloop};
@ -1179,7 +1232,7 @@ void PulseCapture::open(std::string_view name)
case DevFmtX7144: case DevFmtX7144:
case DevFmtX3D71: case DevFmtX3D71:
case DevFmtAmbi3D: case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported",
DevFmtChannelsString(mDevice->FmtChans)}; DevFmtChannelsString(mDevice->FmtChans)};
} }
setDefaultWFXChannelOrder(); setDefaultWFXChannelOrder();
@ -1203,26 +1256,26 @@ void PulseCapture::open(std::string_view name)
case DevFmtUShort: case DevFmtUShort:
case DevFmtUInt: case DevFmtUInt:
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
} }
mSpec.rate = mDevice->Frequency; mSpec.rate = mDevice->mSampleRate;
mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt()); mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt());
if(pa_sample_spec_valid(&mSpec) == 0) if(pa_sample_spec_valid(&mSpec) == 0)
throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"}; throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"};
const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec)); const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec));
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency*100u/1000u)}; const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate*100u/1000u)};
mAttr.minreq = ~0u; mAttr.minreq = ~0u;
mAttr.prebuf = ~0u; mAttr.prebuf = ~0u;
mAttr.maxlength = samples * frame_size; mAttr.maxlength = samples * frame_size;
mAttr.tlength = ~0u; mAttr.tlength = ~0u;
mAttr.fragsize = std::min(samples, mDevice->Frequency*50u/1000u) * frame_size; mAttr.fragsize = std::min(samples, mDevice->mSampleRate*50u/1000u) * frame_size;
pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY};
if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) if(!GetConfigValueBool({}, "pulse", "allow-moves", true))
flags |= PA_STREAM_DONT_MOVE; flags |= PA_STREAM_DONT_MOVE;
TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); TRACE("Connecting to \"{}\"", pulse_name.empty() ? "(default)"sv:std::string_view{pulse_name});
mStream = plock.connectStream(pulse_name, flags, &mAttr, &mSpec, &chanmap, mStream = plock.connectStream(pulse_name, flags, &mAttr, &mSpec, &chanmap,
BackendType::Capture); BackendType::Capture);
@ -1234,9 +1287,10 @@ void PulseCapture::open(std::string_view name)
{ return static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); }; { return static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); };
pa_stream_set_state_callback(mStream, state_callback, this); pa_stream_set_state_callback(mStream, state_callback, this);
if(pulse_name) mDeviceName.emplace(pulse_name); if(!pulse_name.empty())
else mDeviceName.reset(); mDeviceId.emplace(std::move(pulse_name));
if(mDevice->DeviceName.empty())
if(mDeviceName.empty())
{ {
constexpr auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, constexpr auto name_callback = [](pa_context *context, const pa_source_info *info, int eol,
void *pdata) noexcept void *pdata) noexcept
@ -1305,7 +1359,7 @@ void PulseCapture::captureSamples(std::byte *buffer, uint samples)
const pa_stream_state_t state{pa_stream_get_state(mStream)}; const pa_stream_state_t state{pa_stream_get_state(mStream)};
if(!PA_STREAM_IS_GOOD(state)) UNLIKELY if(!PA_STREAM_IS_GOOD(state)) UNLIKELY
{ {
mDevice->handleDisconnect("Bad capture state: %u", state); mDevice->handleDisconnect("Bad capture state: {}", al::to_underlying(state));
break; break;
} }
@ -1313,7 +1367,7 @@ void PulseCapture::captureSamples(std::byte *buffer, uint samples)
size_t caplen{}; size_t caplen{};
if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY
{ {
mDevice->handleDisconnect("Failed retrieving capture samples: %s", mDevice->handleDisconnect("Failed retrieving capture samples: {}",
pa_strerror(pa_context_errno(mMainloop.getContext()))); pa_strerror(pa_context_errno(mMainloop.getContext())));
break; break;
} }
@ -1341,8 +1395,8 @@ uint PulseCapture::availableSamples()
if(static_cast<ssize_t>(got) < 0) UNLIKELY if(static_cast<ssize_t>(got) < 0) UNLIKELY
{ {
const char *err{pa_strerror(static_cast<int>(got))}; const char *err{pa_strerror(static_cast<int>(got))};
ERR("pa_stream_readable_size() failed: %s\n", err); ERR("pa_stream_readable_size() failed: {}", err);
mDevice->handleDisconnect("Failed getting readable size: %s", err); mDevice->handleDisconnect("Failed getting readable size: {}", err);
} }
else else
{ {
@ -1377,7 +1431,7 @@ ClockLatency PulseCapture::getClockLatency()
if(err != 0) UNLIKELY if(err != 0) UNLIKELY
{ {
ERR("Failed to get stream latency: 0x%x\n", err); ERR("Failed to get stream latency: {:#x}", as_unsigned(err));
latency = 0; latency = 0;
neg = 0; neg = 0;
} }
@ -1393,7 +1447,7 @@ ClockLatency PulseCapture::getClockLatency()
bool PulseBackendFactory::init() bool PulseBackendFactory::init()
{ {
#ifdef HAVE_DYNLOAD #if HAVE_DYNLOAD
if(!pulse_handle) if(!pulse_handle)
{ {
#ifdef _WIN32 #ifdef _WIN32
@ -1406,7 +1460,7 @@ bool PulseBackendFactory::init()
pulse_handle = LoadLib(PALIB); pulse_handle = LoadLib(PALIB);
if(!pulse_handle) if(!pulse_handle)
{ {
WARN("Failed to load %s\n", PALIB); WARN("Failed to load {}", PALIB);
return false; return false;
} }
@ -1420,13 +1474,13 @@ bool PulseBackendFactory::init()
if(!missing_funcs.empty()) if(!missing_funcs.empty())
{ {
WARN("Missing expected functions:%s\n", missing_funcs.c_str()); WARN("Missing expected functions:{}", missing_funcs);
CloseLib(pulse_handle); CloseLib(pulse_handle);
pulse_handle = nullptr; pulse_handle = nullptr;
return false; return false;
} }
} }
#endif /* HAVE_DYNLOAD */ #endif
pulse_ctx_flags = PA_CONTEXT_NOFLAGS; pulse_ctx_flags = PA_CONTEXT_NOFLAGS;
if(!GetConfigValueBool({}, "pulse", "spawn-server", false)) if(!GetConfigValueBool({}, "pulse", "spawn-server", false))
@ -1460,21 +1514,32 @@ auto PulseBackendFactory::enumerate(BackendType type) -> std::vector<std::string
{ {
std::vector<std::string> outnames; std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void auto add_playback_device = [&outnames](const DevMap &entry) -> void
{ outnames.push_back(entry.name); }; {
if(entry.device_name == DefaultPlaybackDevName)
outnames.emplace(outnames.cbegin(), entry.name);
else
outnames.push_back(entry.name);
};
auto add_capture_device = [&outnames](const DevMap &entry) -> void
{
if(entry.device_name == DefaultCaptureDevName)
outnames.emplace(outnames.cbegin(), entry.name);
else
outnames.push_back(entry.name);
};
auto plock = MainloopUniqueLock{gGlobalMainloop};
switch(type) switch(type)
{ {
case BackendType::Playback: case BackendType::Playback:
gGlobalMainloop.probePlaybackDevices();
outnames.reserve(PlaybackDevices.size()); outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_playback_device);
break; break;
case BackendType::Capture: case BackendType::Capture:
gGlobalMainloop.probeCaptureDevices();
outnames.reserve(CaptureDevices.size()); outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_capture_device);
break; break;
} }
@ -1502,9 +1567,9 @@ alc::EventSupport PulseBackendFactory::queryEventSupport(alc::EventType eventTyp
{ {
case alc::EventType::DeviceAdded: case alc::EventType::DeviceAdded:
case alc::EventType::DeviceRemoved: case alc::EventType::DeviceRemoved:
case alc::EventType::DefaultDeviceChanged:
return alc::EventSupport::FullSupport; return alc::EventSupport::FullSupport;
case alc::EventType::DefaultDeviceChanged:
case alc::EventType::Count: case alc::EventType::Count:
break; break;
} }

View file

@ -28,10 +28,8 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "core/device.h" #include "core/device.h"
#include "core/logging.h"
_Pragma("GCC diagnostic push") _Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
@ -41,18 +39,13 @@ _Pragma("GCC diagnostic pop")
namespace { namespace {
#ifdef _WIN32 using namespace std::string_view_literals;
#define DEVNAME_PREFIX "OpenAL Soft on "
#else
#define DEVNAME_PREFIX ""
#endif
constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; } [[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view
constexpr auto getDefaultDeviceName() noexcept -> std::string_view { return "Default Device"sv; }
{ return DEVNAME_PREFIX "Default Device"; }
struct Sdl2Backend final : public BackendBase { struct Sdl2Backend final : public BackendBase {
Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } explicit Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
~Sdl2Backend() override; ~Sdl2Backend() override;
void audioCallback(Uint8 *stream, int len) noexcept; void audioCallback(Uint8 *stream, int len) noexcept;
@ -62,13 +55,9 @@ struct Sdl2Backend final : public BackendBase {
void start() override; void start() override;
void stop() override; void stop() override;
std::string mSDLName;
SDL_AudioDeviceID mDeviceID{0u}; SDL_AudioDeviceID mDeviceID{0u};
uint mFrameSize{0}; uint mFrameSize{0};
uint mFrequency{0u};
DevFmtChannels mFmtChans{};
DevFmtType mFmtType{};
uint mUpdateSize{0u};
}; };
Sdl2Backend::~Sdl2Backend() Sdl2Backend::~Sdl2Backend()
@ -89,7 +78,7 @@ void Sdl2Backend::open(std::string_view name)
{ {
SDL_AudioSpec want{}, have{}; SDL_AudioSpec want{}, have{};
want.freq = static_cast<int>(mDevice->Frequency); want.freq = static_cast<int>(mDevice->mSampleRate);
switch(mDevice->FmtType) switch(mDevice->FmtType)
{ {
case DevFmtUByte: want.format = AUDIO_U8; break; case DevFmtUByte: want.format = AUDIO_U8; break;
@ -100,8 +89,9 @@ void Sdl2Backend::open(std::string_view name)
case DevFmtInt: want.format = AUDIO_S32SYS; break; case DevFmtInt: want.format = AUDIO_S32SYS; break;
case DevFmtFloat: want.format = AUDIO_F32; break; case DevFmtFloat: want.format = AUDIO_F32; break;
} }
want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; want.channels = static_cast<Uint8>(std::min<uint>(mDevice->channelsFromFmt(),
want.samples = static_cast<Uint16>(std::min(mDevice->UpdateSize, 8192u)); std::numeric_limits<Uint8>::max()));
want.samples = static_cast<Uint16>(std::min(mDevice->mUpdateSize, 8192u));
want.callback = [](void *ptr, Uint8 *stream, int len) noexcept want.callback = [](void *ptr, Uint8 *stream, int len) noexcept
{ return static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }; { return static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); };
want.userdata = this; want.userdata = this;
@ -110,45 +100,21 @@ void Sdl2Backend::open(std::string_view name)
* necessarily the first in the list. * necessarily the first in the list.
*/ */
const auto defaultDeviceName = getDefaultDeviceName(); const auto defaultDeviceName = getDefaultDeviceName();
SDL_AudioDeviceID devid;
if(name.empty() || name == defaultDeviceName) if(name.empty() || name == defaultDeviceName)
{ {
name = defaultDeviceName; name = defaultDeviceName;
devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); mSDLName.clear();
mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
} }
else else
{ {
const auto namePrefix = getDevicePrefix(); mSDLName = name;
if(name.size() >= namePrefix.size() && name.substr(0, namePrefix.size()) == namePrefix) mDeviceID = SDL_OpenAudioDevice(mSDLName.c_str(), SDL_FALSE, &want, &have,
{ SDL_AUDIO_ALLOW_ANY_CHANGE);
/* Copy the string_view to a string to ensure it's null terminated
* for this call.
*/
const std::string devname{name.substr(namePrefix.size())};
devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
}
else
{
const std::string devname{name};
devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
}
}
if(!devid)
throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()};
DevFmtChannels devchans{};
if(have.channels >= 2)
devchans = DevFmtStereo;
else if(have.channels == 1)
devchans = DevFmtMono;
else
{
SDL_CloseAudioDevice(devid);
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled SDL channel count: %d", int{have.channels}};
} }
if(!mDeviceID)
throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()};
DevFmtType devtype{}; DevFmtType devtype{};
switch(have.format) switch(have.format)
@ -160,32 +126,100 @@ void Sdl2Backend::open(std::string_view name)
case AUDIO_S32SYS: devtype = DevFmtInt; break; case AUDIO_S32SYS: devtype = DevFmtInt; break;
case AUDIO_F32SYS: devtype = DevFmtFloat; break; case AUDIO_F32SYS: devtype = DevFmtFloat; break;
default: default:
SDL_CloseAudioDevice(devid); throw al::backend_exception{al::backend_error::DeviceError,
throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x", "Unhandled SDL format: {:#04x}", have.format};
have.format};
} }
if(mDeviceID)
SDL_CloseAudioDevice(mDeviceID);
mDeviceID = devid;
mFrameSize = BytesFromDevFmt(devtype) * have.channels; mFrameSize = BytesFromDevFmt(devtype) * have.channels;
mFrequency = static_cast<uint>(have.freq);
mFmtChans = devchans;
mFmtType = devtype;
mUpdateSize = have.samples;
mDevice->DeviceName = name; mDeviceName = name;
} }
bool Sdl2Backend::reset() bool Sdl2Backend::reset()
{ {
mDevice->Frequency = mFrequency; if(mDeviceID)
mDevice->FmtChans = mFmtChans; SDL_CloseAudioDevice(mDeviceID);
mDevice->FmtType = mFmtType; mDeviceID = 0;
mDevice->UpdateSize = mUpdateSize;
mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */ auto want = SDL_AudioSpec{};
want.freq = static_cast<int>(mDevice->mSampleRate);
switch(mDevice->FmtType)
{
case DevFmtUByte: want.format = AUDIO_U8; break;
case DevFmtByte: want.format = AUDIO_S8; break;
case DevFmtUShort: want.format = AUDIO_U16SYS; break;
case DevFmtShort: want.format = AUDIO_S16SYS; break;
case DevFmtUInt: [[fallthrough]];
case DevFmtInt: want.format = AUDIO_S32SYS; break;
case DevFmtFloat: want.format = AUDIO_F32; break;
}
want.channels = static_cast<Uint8>(std::min<uint>(mDevice->channelsFromFmt(),
std::numeric_limits<Uint8>::max()));
want.samples = static_cast<Uint16>(std::min(mDevice->mUpdateSize, 8192u));
want.callback = [](void *ptr, Uint8 *stream, int len) noexcept
{ return static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); };
want.userdata = this;
auto have = SDL_AudioSpec{};
if(mSDLName.empty())
{
mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
}
else
{
mDeviceID = SDL_OpenAudioDevice(mSDLName.c_str(), SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
}
if(!mDeviceID)
throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()};
if(have.channels != mDevice->channelsFromFmt())
{
/* SDL guarantees these layouts for the given channel count. */
if(have.channels == 8)
mDevice->FmtChans = DevFmtX71;
else if(have.channels == 7)
mDevice->FmtChans = DevFmtX61;
else if(have.channels == 6)
mDevice->FmtChans = DevFmtX51;
else if(have.channels == 4)
mDevice->FmtChans = DevFmtQuad;
else if(have.channels >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(have.channels == 1)
mDevice->FmtChans = DevFmtMono;
else
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled SDL channel count: {}", int{have.channels}};
mDevice->mAmbiOrder = 0;
}
switch(have.format)
{
case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break;
case AUDIO_S8: mDevice->FmtType = DevFmtByte; break;
case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break;
case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break;
case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break;
case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break;
default:
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled SDL format: {:#04x}", have.format};
}
mFrameSize = BytesFromDevFmt(mDevice->FmtType) * have.channels;
if(have.freq < int{MinOutputRate})
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled SDL sample rate: {}", have.freq};
mDevice->mSampleRate = static_cast<uint>(have.freq);
mDevice->mUpdateSize = have.samples;
mDevice->mBufferSize = std::max(have.size/mFrameSize, mDevice->mUpdateSize*2u);
setDefaultWFXChannelOrder(); setDefaultWFXChannelOrder();
return true; return true;
} }
@ -220,16 +254,14 @@ auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector<std::string>
if(num_devices <= 0) if(num_devices <= 0)
return outnames; return outnames;
outnames.reserve(static_cast<unsigned int>(num_devices)); outnames.reserve(static_cast<unsigned int>(num_devices)+1_uz);
outnames.emplace_back(getDefaultDeviceName()); outnames.emplace_back(getDefaultDeviceName());
for(int i{0};i < num_devices;++i) for(int i{0};i < num_devices;++i)
{ {
std::string outname{getDevicePrefix()};
if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE)) if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE))
outname += name; outnames.emplace_back(name);
else else
outname += "Unknown Device Name #"+std::to_string(i); outnames.emplace_back("Unknown Device Name #"+std::to_string(i));
outnames.emplace_back(std::move(outname));
} }
return outnames; return outnames;
} }

View file

@ -0,0 +1,393 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2024 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "sdl3.h"
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <string>
#include <string_view>
#include "almalloc.h"
#include "core/device.h"
#include "core/logging.h"
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
#include "SDL3/SDL_audio.h"
#include "SDL3/SDL_init.h"
#include "SDL3/SDL_stdinc.h"
_Pragma("GCC diagnostic pop")
namespace {
using namespace std::string_view_literals;
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
constexpr auto DefaultPlaybackDeviceID = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
_Pragma("GCC diagnostic pop")
template<typename T>
struct SdlDeleter {
/* NOLINTNEXTLINE(cppcoreguidelines-no-malloc) */
void operator()(gsl::owner<T*> ptr) const { SDL_free(ptr); }
};
template<typename T>
using unique_sdl_ptr = std::unique_ptr<T,SdlDeleter<T>>;
struct DeviceEntry {
std::string mName;
SDL_AudioDeviceID mPhysDeviceID{};
};
std::vector<DeviceEntry> gPlaybackDevices;
void EnumeratePlaybackDevices()
{
auto numdevs = int{};
auto devicelist = unique_sdl_ptr<SDL_AudioDeviceID>{SDL_GetAudioPlaybackDevices(&numdevs)};
if(!devicelist || numdevs < 0)
{
ERR("Failed to get playback devices: {}", SDL_GetError());
return;
}
auto devids = al::span{devicelist.get(), static_cast<uint>(numdevs)};
auto newlist = std::vector<DeviceEntry>{};
newlist.reserve(devids.size());
std::transform(devids.begin(), devids.end(), std::back_inserter(newlist),
[](SDL_AudioDeviceID id)
{
auto *name = SDL_GetAudioDeviceName(id);
if(!name) return DeviceEntry{};
TRACE("Got device \"{}\", ID {}", name, id);
return DeviceEntry{name, id};
});
gPlaybackDevices.swap(newlist);
}
[[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view
{ return "Default Device"sv; }
struct Sdl3Backend final : public BackendBase {
explicit Sdl3Backend(DeviceBase *device) noexcept : BackendBase{device} { }
~Sdl3Backend() final;
void audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount) noexcept;
void open(std::string_view name) final;
auto reset() -> bool final;
void start() final;
void stop() final;
SDL_AudioDeviceID mDeviceID{0};
SDL_AudioStream *mStream{nullptr};
uint mNumChannels{0};
uint mFrameSize{0};
std::vector<std::byte> mBuffer;
};
Sdl3Backend::~Sdl3Backend()
{
if(mStream)
SDL_DestroyAudioStream(mStream);
mStream = nullptr;
}
void Sdl3Backend::audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount)
noexcept
{
if(additional_amount < 0)
additional_amount = total_amount;
if(additional_amount <= 0)
return;
const auto ulen = static_cast<unsigned int>(additional_amount);
assert((ulen % mFrameSize) == 0);
if(ulen > mBuffer.size())
{
mBuffer.resize(ulen);
std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte)
? std::byte{0x80} : std::byte{});
}
mDevice->renderSamples(mBuffer.data(), ulen / mFrameSize, mNumChannels);
SDL_PutAudioStreamData(stream, mBuffer.data(), additional_amount);
}
void Sdl3Backend::open(std::string_view name)
{
const auto defaultDeviceName = getDefaultDeviceName();
if(name.empty() || name == defaultDeviceName)
{
name = defaultDeviceName;
mDeviceID = DefaultPlaybackDeviceID;
}
else
{
if(gPlaybackDevices.empty())
EnumeratePlaybackDevices();
const auto iter = std::find_if(gPlaybackDevices.cbegin(), gPlaybackDevices.cend(),
[name](const DeviceEntry &entry) { return name == entry.mName; });
if(iter == gPlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "No device named {}", name};
mDeviceID = iter->mPhysDeviceID;
}
mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, nullptr, nullptr);
if(!mStream)
throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()};
auto have = SDL_AudioSpec{};
auto update_size = int{};
if(SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size))
{
auto devtype = mDevice->FmtType;
switch(have.format)
{
case SDL_AUDIO_U8: devtype = DevFmtUByte; break;
case SDL_AUDIO_S8: devtype = DevFmtByte; break;
case SDL_AUDIO_S16: devtype = DevFmtShort; break;
case SDL_AUDIO_S32: devtype = DevFmtInt; break;
case SDL_AUDIO_F32: devtype = DevFmtFloat; break;
default: break;
}
mDevice->FmtType = devtype;
if(have.freq >= int{MinOutputRate} && have.freq <= int{MaxOutputRate})
mDevice->mSampleRate = static_cast<uint>(have.freq);
/* SDL guarantees these layouts for the given channel count. */
if(have.channels == 8)
mDevice->FmtChans = DevFmtX71;
else if(have.channels == 7)
mDevice->FmtChans = DevFmtX61;
else if(have.channels == 6)
mDevice->FmtChans = DevFmtX51;
else if(have.channels == 4)
mDevice->FmtChans = DevFmtQuad;
else if(have.channels >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(have.channels == 1)
mDevice->FmtChans = DevFmtMono;
mDevice->mAmbiOrder = 0;
mNumChannels = static_cast<uint>(have.channels);
mFrameSize = mDevice->bytesFromFmt() * mNumChannels;
if(update_size >= 64)
{
/* We have to assume the total buffer size is just twice the update
* size. SDL doesn't tell us the full end-to-end buffer latency.
*/
mDevice->mUpdateSize = static_cast<uint>(update_size);
mDevice->mBufferSize = mDevice->mUpdateSize*2u;
}
else
ERR("Invalid update size from SDL stream: {}", update_size);
}
else
ERR("Failed to get format from SDL stream: {}", SDL_GetError());
mDeviceName = name;
}
auto Sdl3Backend::reset() -> bool
{
static constexpr auto callback = [](void *ptr, SDL_AudioStream *stream, int additional_amount,
int total_amount) noexcept
{
return static_cast<Sdl3Backend*>(ptr)->audioCallback(stream, additional_amount,
total_amount);
};
if(mStream)
SDL_DestroyAudioStream(mStream);
mStream = nullptr;
mBuffer.clear();
mBuffer.shrink_to_fit();
auto want = SDL_AudioSpec{};
if(!SDL_GetAudioDeviceFormat(mDeviceID, &want, nullptr))
ERR("Failed to get device format: {}", SDL_GetError());
if(mDevice->Flags.test(FrequencyRequest) || want.freq < int{MinOutputRate})
want.freq = static_cast<int>(mDevice->mSampleRate);
if(mDevice->Flags.test(SampleTypeRequest)
|| !(want.format == SDL_AUDIO_U8 || want.format == SDL_AUDIO_S8
|| want.format == SDL_AUDIO_S16 || want.format == SDL_AUDIO_S32
|| want.format == SDL_AUDIO_F32))
{
switch(mDevice->FmtType)
{
case DevFmtUByte: want.format = SDL_AUDIO_U8; break;
case DevFmtByte: want.format = SDL_AUDIO_S8; break;
case DevFmtUShort: [[fallthrough]];
case DevFmtShort: want.format = SDL_AUDIO_S16; break;
case DevFmtUInt: [[fallthrough]];
case DevFmtInt: want.format = SDL_AUDIO_S32; break;
case DevFmtFloat: want.format = SDL_AUDIO_F32; break;
}
}
if(mDevice->Flags.test(ChannelsRequest) || want.channels < 1)
want.channels = static_cast<int>(std::min<uint>(mDevice->channelsFromFmt(),
std::numeric_limits<int>::max()));
mStream = SDL_OpenAudioDeviceStream(mDeviceID, &want, callback, this);
if(!mStream)
{
/* If creating the stream failed, try again without a specific format. */
mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, callback, this);
if(!mStream)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to recreate stream: {}", SDL_GetError()};
}
auto update_size = int{};
auto have = SDL_AudioSpec{};
SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size);
have = SDL_AudioSpec{};
if(!SDL_GetAudioStreamFormat(mStream, &have, nullptr))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get stream format: {}", SDL_GetError()};
if(!mDevice->Flags.test(ChannelsRequest)
|| (static_cast<uint>(have.channels) != mDevice->channelsFromFmt()
&& !(mDevice->FmtChans == DevFmtStereo && have.channels >= 2)))
{
/* SDL guarantees these layouts for the given channel count. */
if(have.channels == 8)
mDevice->FmtChans = DevFmtX71;
else if(have.channels == 7)
mDevice->FmtChans = DevFmtX61;
else if(have.channels == 6)
mDevice->FmtChans = DevFmtX51;
else if(have.channels == 4)
mDevice->FmtChans = DevFmtQuad;
else if(have.channels >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(have.channels == 1)
mDevice->FmtChans = DevFmtMono;
else
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled SDL channel count: {}", have.channels};
mDevice->mAmbiOrder = 0;
}
mNumChannels = static_cast<uint>(have.channels);
switch(have.format)
{
case SDL_AUDIO_U8: mDevice->FmtType = DevFmtUByte; break;
case SDL_AUDIO_S8: mDevice->FmtType = DevFmtByte; break;
case SDL_AUDIO_S16: mDevice->FmtType = DevFmtShort; break;
case SDL_AUDIO_S32: mDevice->FmtType = DevFmtInt; break;
case SDL_AUDIO_F32: mDevice->FmtType = DevFmtFloat; break;
default:
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled SDL format: {:#04x}", al::to_underlying(have.format)};
}
mFrameSize = mDevice->bytesFromFmt() * mNumChannels;
if(have.freq < int{MinOutputRate})
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled SDL sample rate: {}", have.freq};
mDevice->mSampleRate = static_cast<uint>(have.freq);
if(update_size >= 64)
{
mDevice->mUpdateSize = static_cast<uint>(update_size);
mDevice->mBufferSize = mDevice->mUpdateSize*2u;
mBuffer.resize(size_t{mDevice->mUpdateSize} * mFrameSize);
std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte)
? std::byte{0x80} : std::byte{});
}
else
ERR("Invalid update size from SDL stream: {}", update_size);
setDefaultWFXChannelOrder();
return true;
}
void Sdl3Backend::start()
{ SDL_ResumeAudioStreamDevice(mStream); }
void Sdl3Backend::stop()
{ SDL_PauseAudioStreamDevice(mStream); }
} // namespace
auto SDL3BackendFactory::getFactory() -> BackendFactory&
{
static SDL3BackendFactory factory{};
return factory;
}
auto SDL3BackendFactory::init() -> bool
{
if(!SDL_InitSubSystem(SDL_INIT_AUDIO))
return false;
TRACE("Current SDL3 audio driver: \"{}\"", SDL_GetCurrentAudioDriver());
return true;
}
auto SDL3BackendFactory::querySupport(BackendType type) -> bool
{ return type == BackendType::Playback; }
auto SDL3BackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
auto outnames = std::vector<std::string>{};
if(type != BackendType::Playback)
return outnames;
EnumeratePlaybackDevices();
outnames.reserve(gPlaybackDevices.size()+1);
outnames.emplace_back(getDefaultDeviceName());
std::transform(gPlaybackDevices.begin(), gPlaybackDevices.end(), std::back_inserter(outnames),
std::mem_fn(&DeviceEntry::mName));
return outnames;
}
auto SDL3BackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr
{
if(type == BackendType::Playback)
return BackendPtr{new Sdl3Backend{device}};
return nullptr;
}

View file

@ -0,0 +1,19 @@
#ifndef BACKENDS_SDL3_H
#define BACKENDS_SDL3_H
#include "base.h"
struct SDL3BackendFactory final : public BackendFactory {
public:
auto init() -> bool final;
auto querySupport(BackendType type) -> bool final;
auto enumerate(BackendType type) -> std::vector<std::string> final;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SDL3_H */

View file

@ -20,27 +20,23 @@
#include "config.h" #include "config.h"
#include "sndio.h" #include "sndio.hpp"
#include <cinttypes>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <functional>
#include <poll.h> #include <poll.h>
#include <system_error> #include <system_error>
#include <thread> #include <thread>
#include <vector> #include <vector>
#include "alnumeric.h"
#include "alstring.h"
#include "althrd_setname.h" #include "althrd_setname.h"
#include "core/device.h" #include "core/device.h"
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include <sndio.h> /* NOLINT(*-duplicate-include) Not the same header. */ #include <sndio.h>
namespace { namespace {
@ -56,7 +52,7 @@ struct SioPar : public sio_par {
}; };
struct SndioPlayback final : public BackendBase { struct SndioPlayback final : public BackendBase {
SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioPlayback() override; ~SndioPlayback() override;
int mixerProc(); int mixerProc();
@ -102,7 +98,7 @@ int SndioPlayback::mixerProc()
size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())}; size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
if(wrote > buffer.size() || wrote == 0) if(wrote > buffer.size() || wrote == 0)
{ {
ERR("sio_write failed: 0x%" PRIx64 "\n", wrote); ERR("sio_write failed: {:#x}", wrote);
mDevice->handleDisconnect("Failed to write playback samples"); mDevice->handleDisconnect("Failed to write playback samples");
break; break;
} }
@ -119,8 +115,8 @@ void SndioPlayback::open(std::string_view name)
if(name.empty()) if(name.empty())
name = GetDefaultName(); name = GetDefaultName();
else if(name != GetDefaultName()) else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)}; sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
if(!sndHandle) if(!sndHandle)
@ -130,7 +126,7 @@ void SndioPlayback::open(std::string_view name)
sio_close(mSndHandle); sio_close(mSndHandle);
mSndHandle = sndHandle; mSndHandle = sndHandle;
mDevice->DeviceName = name; mDeviceName = name;
} }
bool SndioPlayback::reset() bool SndioPlayback::reset()
@ -172,12 +168,12 @@ bool SndioPlayback::reset()
par.le = SIO_LE_NATIVE; par.le = SIO_LE_NATIVE;
par.msb = 1; par.msb = 1;
par.rate = mDevice->Frequency; par.rate = mDevice->mSampleRate;
par.pchan = mDevice->channelsFromFmt(); par.pchan = mDevice->channelsFromFmt();
par.round = mDevice->UpdateSize; par.round = mDevice->mUpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; par.appbufsz = mDevice->mBufferSize - mDevice->mUpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; if(!par.appbufsz) par.appbufsz = mDevice->mUpdateSize;
try { try {
if(!sio_setpar(mSndHandle, &par)) if(!sio_setpar(mSndHandle, &par))
@ -191,10 +187,10 @@ bool SndioPlayback::reset()
if(par.bps > 1 && par.le != SIO_LE_NATIVE) if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"}; "{}-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb) if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8}; "MSB-padded samples not supported ({} of {} bits)", par.bits, par.bps*8};
if(par.pchan < 1) if(par.pchan < 1)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"No playback channels on device"}; "No playback channels on device"};
@ -217,24 +213,24 @@ bool SndioPlayback::reset()
mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt; mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt;
else else
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8}; "Unhandled sample format: {} {}-bit", (par.sig?"signed":"unsigned"), par.bps*8};
mFrameStep = par.pchan; mFrameStep = par.pchan;
if(par.pchan != mDevice->channelsFromFmt()) if(par.pchan != mDevice->channelsFromFmt())
{ {
WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s", WARN("Got {} channel{} for {}", par.pchan, (par.pchan==1)?"":"s",
DevFmtChannelsString(mDevice->FmtChans)); DevFmtChannelsString(mDevice->FmtChans));
if(par.pchan < 2) mDevice->FmtChans = DevFmtMono; if(par.pchan < 2) mDevice->FmtChans = DevFmtMono;
else mDevice->FmtChans = DevFmtStereo; else mDevice->FmtChans = DevFmtStereo;
} }
mDevice->Frequency = par.rate; mDevice->mSampleRate = par.rate;
setDefaultChannelOrder(); setDefaultChannelOrder();
mDevice->UpdateSize = par.round; mDevice->mUpdateSize = par.round;
mDevice->BufferSize = par.bufsz + par.round; mDevice->mBufferSize = par.bufsz + par.round;
mBuffer.resize(size_t{mDevice->UpdateSize} * par.pchan*par.bps); mBuffer.resize(size_t{mDevice->mUpdateSize} * par.pchan*par.bps);
if(par.sig == 1) if(par.sig == 1)
std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
else if(par.bits == 8) else if(par.bits == 8)
@ -254,12 +250,12 @@ void SndioPlayback::start()
try { try {
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this}; mThread = std::thread{&SndioPlayback::mixerProc, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
sio_stop(mSndHandle); sio_stop(mSndHandle);
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()}; "Failed to start mixing thread: {}", e.what()};
} }
} }
@ -270,7 +266,7 @@ void SndioPlayback::stop()
mThread.join(); mThread.join();
if(!sio_stop(mSndHandle)) if(!sio_stop(mSndHandle))
ERR("Error stopping device\n"); ERR("Error stopping device");
} }
@ -280,7 +276,7 @@ void SndioPlayback::stop()
* capture buffer sizes apps may request. * capture buffer sizes apps may request.
*/ */
struct SndioCapture final : public BackendBase { struct SndioCapture final : public BackendBase {
SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { } explicit SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioCapture() override; ~SndioCapture() override;
int recordProc(); int recordProc();
@ -316,7 +312,7 @@ int SndioCapture::recordProc()
int nfds_pre{sio_nfds(mSndHandle)}; int nfds_pre{sio_nfds(mSndHandle)};
if(nfds_pre <= 0) if(nfds_pre <= 0)
{ {
mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre); mDevice->handleDisconnect("Incorrect return value from sio_nfds(): {}", nfds_pre);
return 1; return 1;
} }
@ -329,15 +325,14 @@ int SndioCapture::recordProc()
const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)}; const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)};
if(nfds <= 0) if(nfds <= 0)
{ {
mDevice->handleDisconnect("Failed to get polling fds: %d", nfds); mDevice->handleDisconnect("Failed to get polling fds: {}", nfds);
break; break;
} }
int pollres{::poll(fds.data(), fds.size(), 2000)}; int pollres{::poll(fds.data(), fds.size(), 2000)};
if(pollres < 0) if(pollres < 0)
{ {
if(errno == EINTR) continue; if(errno == EINTR) continue;
mDevice->handleDisconnect("Poll error: %s", mDevice->handleDisconnect("Poll error: {}", std::generic_category().message(errno));
std::generic_category().message(errno).c_str());
break; break;
} }
if(pollres == 0) if(pollres == 0)
@ -353,7 +348,7 @@ int SndioCapture::recordProc()
continue; continue;
auto data = mRing->getWriteVector(); auto data = mRing->getWriteVector();
al::span<std::byte> buffer{data.first.buf, data.first.len*frameSize}; al::span<std::byte> buffer{data[0].buf, data[0].len*frameSize};
while(!buffer.empty()) while(!buffer.empty())
{ {
size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())}; size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
@ -361,8 +356,8 @@ int SndioCapture::recordProc()
break; break;
if(got > buffer.size()) if(got > buffer.size())
{ {
ERR("sio_read failed: 0x%" PRIx64 "\n", got); ERR("sio_read failed: {:#x}", got);
mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got); mDevice->handleDisconnect("sio_read failed: {:#x}", got);
break; break;
} }
@ -371,7 +366,7 @@ int SndioCapture::recordProc()
if(buffer.empty()) if(buffer.empty())
{ {
data = mRing->getWriteVector(); data = mRing->getWriteVector();
buffer = {data.first.buf, data.first.len*frameSize}; buffer = {data[0].buf, data[0].len*frameSize};
} }
} }
if(buffer.empty()) if(buffer.empty())
@ -391,8 +386,8 @@ void SndioCapture::open(std::string_view name)
if(name.empty()) if(name.empty())
name = GetDefaultName(); name = GetDefaultName();
else if(name != GetDefaultName()) else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
mSndHandle = sio_open(nullptr, SIO_REC, true); mSndHandle = sio_open(nullptr, SIO_REC, true);
if(mSndHandle == nullptr) if(mSndHandle == nullptr)
@ -427,16 +422,16 @@ void SndioCapture::open(std::string_view name)
break; break;
case DevFmtFloat: case DevFmtFloat:
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
} }
par.bps = SIO_BPS(par.bits); par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE; par.le = SIO_LE_NATIVE;
par.msb = 1; par.msb = 1;
par.rchan = mDevice->channelsFromFmt(); par.rchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency; par.rate = mDevice->mSampleRate;
par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u); par.appbufsz = std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u);
par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u); par.round = std::min(par.appbufsz/2u, mDevice->mSampleRate/40u);
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
@ -444,10 +439,10 @@ void SndioCapture::open(std::string_view name)
if(par.bps > 1 && par.le != SIO_LE_NATIVE) if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"}; "{}-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb) if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8}; "Padded samples not supported (got {} of {} bits)", par.bits, par.bps*8};
auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool
{ {
@ -459,19 +454,19 @@ void SndioCapture::open(std::string_view name)
|| (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0); || (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0);
}; };
if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan
|| mDevice->Frequency != par.rate) || mDevice->mSampleRate != par.rate)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead", "Failed to set format {} {} {}hz, got {}{} {}-channel {}hz instead",
DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; mDevice->mSampleRate, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false); mRing = RingBuffer::Create(mDevice->mBufferSize, size_t{par.bps}*par.rchan, false);
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace()); mDevice->mBufferSize = static_cast<uint>(mRing->writeSpace());
mDevice->UpdateSize = par.round; mDevice->mUpdateSize = par.round;
setDefaultChannelOrder(); setDefaultChannelOrder();
mDevice->DeviceName = name; mDeviceName = name;
} }
void SndioCapture::start() void SndioCapture::start()
@ -481,12 +476,12 @@ void SndioCapture::start()
try { try {
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this}; mThread = std::thread{&SndioCapture::recordProc, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
sio_stop(mSndHandle); sio_stop(mSndHandle);
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start capture thread: %s", e.what()}; "Failed to start capture thread: {}", e.what()};
} }
} }
@ -497,7 +492,7 @@ void SndioCapture::stop()
mThread.join(); mThread.join();
if(!sio_stop(mSndHandle)) if(!sio_stop(mSndHandle))
ERR("Error stopping device\n"); ERR("Error stopping device");
} }
void SndioCapture::captureSamples(std::byte *buffer, uint samples) void SndioCapture::captureSamples(std::byte *buffer, uint samples)

View file

@ -1,5 +1,5 @@
#ifndef BACKENDS_SNDIO_H #ifndef BACKENDS_SNDIO_HPP
#define BACKENDS_SNDIO_H #define BACKENDS_SNDIO_HPP
#include "base.h" #include "base.h"
@ -16,4 +16,4 @@ public:
static auto getFactory() -> BackendFactory&; static auto getFactory() -> BackendFactory&;
}; };
#endif /* BACKENDS_SNDIO_H */ #endif /* BACKENDS_SNDIO_HPP */

View file

@ -60,7 +60,7 @@ std::string solaris_driver{"/dev/audio"};
struct SolarisBackend final : public BackendBase { struct SolarisBackend final : public BackendBase {
SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { } explicit SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { }
~SolarisBackend() override; ~SolarisBackend() override;
int mixerProc(); int mixerProc();
@ -106,13 +106,13 @@ int SolarisBackend::mixerProc()
{ {
if(errno == EINTR || errno == EAGAIN) if(errno == EINTR || errno == EAGAIN)
continue; continue;
ERR("poll failed: %s\n", strerror(errno)); ERR("poll failed: {}", strerror(errno));
mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno)); mDevice->handleDisconnect("Failed to wait for playback buffer: {}", strerror(errno));
break; break;
} }
else if(pret == 0) else if(pret == 0)
{ {
WARN("poll timeout\n"); WARN("poll timeout");
continue; continue;
} }
@ -126,8 +126,8 @@ int SolarisBackend::mixerProc()
{ {
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue; continue;
ERR("write failed: %s\n", strerror(errno)); ERR("write failed: {}", strerror(errno));
mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno)); mDevice->handleDisconnect("Failed to write playback samples: {}", strerror(errno));
break; break;
} }
@ -144,19 +144,19 @@ void SolarisBackend::open(std::string_view name)
if(name.empty()) if(name.empty())
name = GetDefaultName(); name = GetDefaultName();
else if(name != GetDefaultName()) else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
int fd{::open(solaris_driver.c_str(), O_WRONLY)}; int fd{::open(solaris_driver.c_str(), O_WRONLY)};
if(fd == -1) if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}",
solaris_driver.c_str(), strerror(errno)}; solaris_driver, strerror(errno)};
if(mFd != -1) if(mFd != -1)
::close(mFd); ::close(mFd);
mFd = fd; mFd = fd;
mDevice->DeviceName = name; mDeviceName = name;
} }
bool SolarisBackend::reset() bool SolarisBackend::reset()
@ -164,7 +164,7 @@ bool SolarisBackend::reset()
audio_info_t info; audio_info_t info;
AUDIO_INITINFO(&info); AUDIO_INITINFO(&info);
info.play.sample_rate = mDevice->Frequency; info.play.sample_rate = mDevice->mSampleRate;
info.play.channels = mDevice->channelsFromFmt(); info.play.channels = mDevice->channelsFromFmt();
switch(mDevice->FmtType) switch(mDevice->FmtType)
{ {
@ -187,11 +187,11 @@ bool SolarisBackend::reset()
info.play.encoding = AUDIO_ENCODING_LINEAR; info.play.encoding = AUDIO_ENCODING_LINEAR;
break; break;
} }
info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt(); info.play.buffer_size = mDevice->mBufferSize * mDevice->frameSizeFromFmt();
if(ioctl(mFd, AUDIO_SETINFO, &info) < 0) if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
{ {
ERR("ioctl failed: %s\n", strerror(errno)); ERR("ioctl failed: {}", strerror(errno));
return false; return false;
} }
@ -203,7 +203,7 @@ bool SolarisBackend::reset()
mDevice->FmtChans = DevFmtMono; mDevice->FmtChans = DevFmtMono;
else else
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Got %u device channels", info.play.channels}; "Got {} device channels", info.play.channels};
} }
if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8) if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8)
@ -216,20 +216,20 @@ bool SolarisBackend::reset()
mDevice->FmtType = DevFmtInt; mDevice->FmtType = DevFmtInt;
else else
{ {
ERR("Got unhandled sample type: %d (0x%x)\n", info.play.precision, info.play.encoding); ERR("Got unhandled sample type: {} ({:#x})", info.play.precision, info.play.encoding);
return false; return false;
} }
uint frame_size{mDevice->bytesFromFmt() * info.play.channels}; uint frame_size{mDevice->bytesFromFmt() * info.play.channels};
mFrameStep = info.play.channels; mFrameStep = info.play.channels;
mDevice->Frequency = info.play.sample_rate; mDevice->mSampleRate = info.play.sample_rate;
mDevice->BufferSize = info.play.buffer_size / frame_size; mDevice->mBufferSize = info.play.buffer_size / frame_size;
/* How to get the actual period size/count? */ /* How to get the actual period size/count? */
mDevice->UpdateSize = mDevice->BufferSize / 2; mDevice->mUpdateSize = mDevice->mBufferSize / 2;
setDefaultChannelOrder(); setDefaultChannelOrder();
mBuffer.resize(mDevice->UpdateSize * size_t{frame_size}); mBuffer.resize(mDevice->mUpdateSize * size_t{frame_size});
std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
return true; return true;
@ -239,11 +239,11 @@ void SolarisBackend::start()
{ {
try { try {
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this}; mThread = std::thread{&SolarisBackend::mixerProc, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()}; "Failed to start mixing thread: {}", e.what()};
} }
} }
@ -254,7 +254,7 @@ void SolarisBackend::stop()
mThread.join(); mThread.join();
if(ioctl(mFd, AUDIO_DRAIN) < 0) if(ioctl(mFd, AUDIO_DRAIN) < 0)
ERR("Error draining device: %s\n", strerror(errno)); ERR("Error draining device: {}", strerror(errno));
} }
} // namespace } // namespace

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,6 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <exception> #include <exception>
#include <functional>
#include <system_error> #include <system_error>
#include <thread> #include <thread>
#include <vector> #include <vector>
@ -39,12 +38,9 @@
#include "alc/alconfig.h" #include "alc/alconfig.h"
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alstring.h"
#include "althrd_setname.h" #include "althrd_setname.h"
#include "core/device.h" #include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "opthelpers.h"
#include "strutils.h" #include "strutils.h"
@ -99,7 +95,7 @@ void fwrite32le(uint val, FILE *f)
struct WaveBackend final : public BackendBase { struct WaveBackend final : public BackendBase {
WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { } explicit WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { }
~WaveBackend() override; ~WaveBackend() override;
int mixerProc(); int mixerProc();
@ -122,7 +118,7 @@ WaveBackend::~WaveBackend() = default;
int WaveBackend::mixerProc() int WaveBackend::mixerProc()
{ {
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; const milliseconds restTime{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2};
althrd_setname(GetMixerThreadName()); althrd_setname(GetMixerThreadName());
@ -137,17 +133,17 @@ int WaveBackend::mixerProc()
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
/* This converts from nanoseconds to nanosamples, then to samples. */ /* This converts from nanoseconds to nanosamples, then to samples. */
int64_t avail{std::chrono::duration_cast<seconds>((now-start) * const auto avail = int64_t{std::chrono::duration_cast<seconds>((now-start) *
mDevice->Frequency).count()}; mDevice->mSampleRate).count()};
if(avail-done < mDevice->UpdateSize) if(avail-done < mDevice->mUpdateSize)
{ {
std::this_thread::sleep_for(restTime); std::this_thread::sleep_for(restTime);
continue; continue;
} }
while(avail-done >= mDevice->UpdateSize) while(avail-done >= mDevice->mUpdateSize)
{ {
mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep); mDevice->renderSamples(mBuffer.data(), mDevice->mUpdateSize, frameStep);
done += mDevice->UpdateSize; done += mDevice->mUpdateSize;
if(al::endian::native != al::endian::little) if(al::endian::native != al::endian::little)
{ {
@ -170,10 +166,10 @@ int WaveBackend::mixerProc()
} }
} }
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile.get())}; const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->mUpdateSize, mFile.get())};
if(fs < mDevice->UpdateSize || ferror(mFile.get())) if(fs < mDevice->mUpdateSize || ferror(mFile.get()))
{ {
ERR("Error writing to file\n"); ERR("Error writing to file");
mDevice->handleDisconnect("Failed to write playback samples"); mDevice->handleDisconnect("Failed to write playback samples");
break; break;
} }
@ -184,10 +180,10 @@ int WaveBackend::mixerProc()
* and current time from growing too large, while maintaining the * and current time from growing too large, while maintaining the
* correct number of samples to render. * correct number of samples to render.
*/ */
if(done >= mDevice->Frequency) if(done >= mDevice->mSampleRate)
{ {
seconds s{done/mDevice->Frequency}; seconds s{done/mDevice->mSampleRate};
done %= mDevice->Frequency; done %= mDevice->mSampleRate;
start += s; start += s;
} }
} }
@ -204,8 +200,8 @@ void WaveBackend::open(std::string_view name)
if(name.empty()) if(name.empty())
name = GetDeviceName(); name = GetDeviceName();
else if(name != GetDeviceName()) else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
/* There's only one "device", so if it's already open, we're done. */ /* There's only one "device", so if it's already open, we're done. */
if(mFile) return; if(mFile) return;
@ -219,20 +215,14 @@ void WaveBackend::open(std::string_view name)
mFile = FilePtr{fopen(fname->c_str(), "wb")}; mFile = FilePtr{fopen(fname->c_str(), "wb")};
#endif #endif
if(!mFile) if(!mFile)
throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s", throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '{}': {}",
fname->c_str(), std::generic_category().message(errno).c_str()}; *fname, std::generic_category().message(errno)};
mDevice->DeviceName = name; mDeviceName = name;
} }
bool WaveBackend::reset() bool WaveBackend::reset()
{ {
uint channels{0}, bytes{0}, chanmask{0};
bool isbformat{false};
fseek(mFile.get(), 0, SEEK_SET);
clearerr(mFile.get());
if(GetConfigValueBool({}, "wave", "bformat", false)) if(GetConfigValueBool({}, "wave", "bformat", false))
{ {
mDevice->FmtChans = DevFmtAmbi3D; mDevice->FmtChans = DevFmtAmbi3D;
@ -256,6 +246,8 @@ bool WaveBackend::reset()
case DevFmtFloat: case DevFmtFloat:
break; break;
} }
auto chanmask = 0u;
auto isbformat = false;
switch(mDevice->FmtChans) switch(mDevice->FmtChans)
{ {
case DevFmtMono: chanmask = 0x04; break; case DevFmtMono: chanmask = 0x04; break;
@ -282,13 +274,24 @@ bool WaveBackend::reset()
chanmask = 0; chanmask = 0;
break; break;
} }
bytes = mDevice->bytesFromFmt(); const auto bytes = mDevice->bytesFromFmt();
channels = mDevice->channelsFromFmt(); const auto channels = mDevice->channelsFromFmt();
rewind(mFile.get()); if(fseek(mFile.get(), 0, SEEK_CUR) != 0)
{
/* ESPIPE means the underlying file isn't seekable, which is fine for
* piped output.
*/
if(auto errcode = errno; errcode != ESPIPE)
{
ERR("Failed to reset file offset: {} ({})", std::generic_category().message(errcode),
errcode);
}
}
clearerr(mFile.get());
fputs("RIFF", mFile.get()); fputs("RIFF", mFile.get());
fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at stop
fputs("WAVE", mFile.get()); fputs("WAVE", mFile.get());
@ -300,9 +303,9 @@ bool WaveBackend::reset()
// 16-bit val, channel count // 16-bit val, channel count
fwrite16le(static_cast<ushort>(channels), mFile.get()); fwrite16le(static_cast<ushort>(channels), mFile.get());
// 32-bit val, frequency // 32-bit val, frequency
fwrite32le(mDevice->Frequency, mFile.get()); fwrite32le(mDevice->mSampleRate, mFile.get());
// 32-bit val, bytes per second // 32-bit val, bytes per second
fwrite32le(mDevice->Frequency * channels * bytes, mFile.get()); fwrite32le(mDevice->mSampleRate * channels * bytes, mFile.get());
// 16-bit val, frame size // 16-bit val, frame size
fwrite16le(static_cast<ushort>(channels * bytes), mFile.get()); fwrite16le(static_cast<ushort>(channels * bytes), mFile.get());
// 16-bit val, bits per sample // 16-bit val, bits per sample
@ -319,18 +322,18 @@ bool WaveBackend::reset()
(isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get()); (isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get());
fputs("data", mFile.get()); fputs("data", mFile.get());
fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at close fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at stop
if(ferror(mFile.get())) if(ferror(mFile.get()))
{ {
ERR("Error writing header: %s\n", std::generic_category().message(errno).c_str()); ERR("Error writing header: {}", std::generic_category().message(errno));
return false; return false;
} }
mDataStart = ftell(mFile.get()); mDataStart = ftell(mFile.get());
setDefaultWFXChannelOrder(); setDefaultWFXChannelOrder();
const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize}; const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->mUpdateSize};
mBuffer.resize(bufsize); mBuffer.resize(bufsize);
return true; return true;
@ -339,14 +342,14 @@ bool WaveBackend::reset()
void WaveBackend::start() void WaveBackend::start()
{ {
if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0) if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0)
WARN("Failed to seek on output file\n"); WARN("Failed to seek on output file");
try { try {
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this}; mThread = std::thread{&WaveBackend::mixerProc, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()}; "Failed to start mixing thread: {}", e.what()};
} }
} }

View file

@ -36,14 +36,14 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <algorithm> #include <algorithm>
#include <functional>
#include "alnumeric.h"
#include "alsem.h" #include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h" #include "althrd_setname.h"
#include "core/device.h" #include "core/device.h"
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "fmt/core.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include "strutils.h" #include "strutils.h"
#include "vector.h" #include "vector.h"
@ -54,9 +54,6 @@
namespace { namespace {
#define DEVNAME_HEAD "OpenAL Soft on "
std::vector<std::string> PlaybackDevices; std::vector<std::string> PlaybackDevices;
std::vector<std::string> CaptureDevices; std::vector<std::string> CaptureDevices;
@ -76,19 +73,15 @@ void ProbePlaybackDevices()
WAVEOUTCAPSW WaveCaps{}; WAVEOUTCAPSW WaveCaps{};
if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{ {
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))}; const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname));
int count{1}; auto count = 1;
std::string newname{basename}; auto newname = basename;
while(checkName(PlaybackDevices, newname)) while(checkName(PlaybackDevices, newname))
{ newname = fmt::format("{} #{}", basename, ++count);
newname = basename;
newname += " #";
newname += std::to_string(++count);
}
dname = std::move(newname); dname = std::move(newname);
TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i); TRACE("Got device \"{}\", ID {}", dname, i);
} }
PlaybackDevices.emplace_back(std::move(dname)); PlaybackDevices.emplace_back(std::move(dname));
} }
@ -107,19 +100,15 @@ void ProbeCaptureDevices()
WAVEINCAPSW WaveCaps{}; WAVEINCAPSW WaveCaps{};
if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{ {
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))}; const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname));
int count{1}; auto count = 1;
std::string newname{basename}; auto newname = basename;
while(checkName(CaptureDevices, newname)) while(checkName(CaptureDevices, newname))
{ newname = fmt::format("{} #{}", basename, ++count);
newname = basename;
newname += " #";
newname += std::to_string(++count);
}
dname = std::move(newname); dname = std::move(newname);
TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i); TRACE("Got device \"{}\", ID {}", dname, i);
} }
CaptureDevices.emplace_back(std::move(dname)); CaptureDevices.emplace_back(std::move(dname));
} }
@ -127,7 +116,7 @@ void ProbeCaptureDevices()
struct WinMMPlayback final : public BackendBase { struct WinMMPlayback final : public BackendBase {
WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { } explicit WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~WinMMPlayback() override; ~WinMMPlayback() override;
void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
@ -194,7 +183,7 @@ FORCE_ALIGN int WinMMPlayback::mixerProc()
WAVEHDR &waveHdr = mWaveBuffer[widx]; WAVEHDR &waveHdr = mWaveBuffer[widx];
if(++widx == mWaveBuffer.size()) widx = 0; if(++widx == mWaveBuffer.size()) widx = 0;
mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels); mDevice->renderSamples(waveHdr.lpData, mDevice->mUpdateSize, mFormat.nChannels);
mWritable.fetch_sub(1, std::memory_order_acq_rel); mWritable.fetch_sub(1, std::memory_order_acq_rel);
waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR)); waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
} while(--todo); } while(--todo);
@ -215,8 +204,8 @@ void WinMMPlayback::open(std::string_view name)
std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
PlaybackDevices.cbegin(); PlaybackDevices.cbegin();
if(iter == PlaybackDevices.cend()) if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter)); auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
DevFmtType fmttype{mDevice->FmtType}; DevFmtType fmttype{mDevice->FmtType};
@ -238,7 +227,7 @@ void WinMMPlayback::open(std::string_view name)
} }
format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8); format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
format.nSamplesPerSec = mDevice->Frequency; format.nSamplesPerSec = mDevice->mSampleRate;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0; format.cbSize = 0;
@ -248,7 +237,7 @@ void WinMMPlayback::open(std::string_view name)
if(res == MMSYSERR_NOERROR) break; if(res == MMSYSERR_NOERROR) break;
if(fmttype != DevFmtFloat) if(fmttype != DevFmtFloat)
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: {}",
res}; res};
fmttype = DevFmtShort; fmttype = DevFmtShort;
@ -256,16 +245,16 @@ void WinMMPlayback::open(std::string_view name)
mFormat = format; mFormat = format;
mDevice->DeviceName = PlaybackDevices[DeviceID]; mDeviceName = PlaybackDevices[DeviceID];
} }
bool WinMMPlayback::reset() bool WinMMPlayback::reset()
{ {
mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} * mDevice->mBufferSize = static_cast<uint>(uint64_t{mDevice->mBufferSize} *
mFormat.nSamplesPerSec / mDevice->Frequency); mFormat.nSamplesPerSec / mDevice->mSampleRate);
mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u; mDevice->mBufferSize = (mDevice->mBufferSize+3) & ~0x3u;
mDevice->UpdateSize = mDevice->BufferSize / 4; mDevice->mUpdateSize = mDevice->mBufferSize / 4;
mDevice->Frequency = mFormat.nSamplesPerSec; mDevice->mSampleRate = mFormat.nSamplesPerSec;
if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
{ {
@ -273,7 +262,7 @@ bool WinMMPlayback::reset()
mDevice->FmtType = DevFmtFloat; mDevice->FmtType = DevFmtFloat;
else else
{ {
ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample); ERR("Unhandled IEEE float sample depth: {}", mFormat.wBitsPerSample);
return false; return false;
} }
} }
@ -285,13 +274,13 @@ bool WinMMPlayback::reset()
mDevice->FmtType = DevFmtUByte; mDevice->FmtType = DevFmtUByte;
else else
{ {
ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample); ERR("Unhandled PCM sample depth: {}", mFormat.wBitsPerSample);
return false; return false;
} }
} }
else else
{ {
ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag); ERR("Unhandled format tag: {:#04x}", as_unsigned(mFormat.wFormatTag));
return false; return false;
} }
@ -301,12 +290,12 @@ bool WinMMPlayback::reset()
mDevice->FmtChans = DevFmtMono; mDevice->FmtChans = DevFmtMono;
else else
{ {
ERR("Unhandled channel count: %d\n", mFormat.nChannels); ERR("Unhandled channel count: {}", mFormat.nChannels);
return false; return false;
} }
setDefaultWFXChannelOrder(); setDefaultWFXChannelOrder();
const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; const uint BufferSize{mDevice->mUpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer); decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
mWaveBuffer[0] = WAVEHDR{}; mWaveBuffer[0] = WAVEHDR{};
@ -331,11 +320,11 @@ void WinMMPlayback::start()
mWritable.store(static_cast<uint>(mWaveBuffer.size()), std::memory_order_release); mWritable.store(static_cast<uint>(mWaveBuffer.size()), std::memory_order_release);
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this}; mThread = std::thread{&WinMMPlayback::mixerProc, this};
} }
catch(std::exception& e) { catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()}; "Failed to start mixing thread: {}", e.what()};
} }
} }
@ -354,7 +343,7 @@ void WinMMPlayback::stop()
struct WinMMCapture final : public BackendBase { struct WinMMCapture final : public BackendBase {
WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { } explicit WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~WinMMCapture() override; ~WinMMCapture() override;
void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
@ -446,8 +435,8 @@ void WinMMCapture::open(std::string_view name)
std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
CaptureDevices.cbegin(); CaptureDevices.cbegin();
if(iter == CaptureDevices.cend()) if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
al::sizei(name), name.data()}; name};
auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter)); auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
switch(mDevice->FmtChans) switch(mDevice->FmtChans)
@ -464,7 +453,7 @@ void WinMMCapture::open(std::string_view name)
case DevFmtX7144: case DevFmtX7144:
case DevFmtX3D71: case DevFmtX3D71:
case DevFmtAmbi3D: case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported",
DevFmtChannelsString(mDevice->FmtChans)}; DevFmtChannelsString(mDevice->FmtChans)};
} }
@ -479,7 +468,7 @@ void WinMMCapture::open(std::string_view name)
case DevFmtByte: case DevFmtByte:
case DevFmtUShort: case DevFmtUShort:
case DevFmtUInt: case DevFmtUInt:
throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported", throw al::backend_exception{al::backend_error::DeviceError, "{} samples not supported",
DevFmtTypeString(mDevice->FmtType)}; DevFmtTypeString(mDevice->FmtType)};
} }
@ -489,7 +478,7 @@ void WinMMCapture::open(std::string_view name)
mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt()); mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8); mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8); mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
mFormat.nSamplesPerSec = mDevice->Frequency; mFormat.nSamplesPerSec = mDevice->mSampleRate;
mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
mFormat.cbSize = 0; mFormat.cbSize = 0;
@ -497,7 +486,7 @@ void WinMMCapture::open(std::string_view name)
reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC), reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)}; reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res != MMSYSERR_NOERROR) if(res != MMSYSERR_NOERROR)
throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res}; throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: {}", res};
// Ensure each buffer is 50ms each // Ensure each buffer is 50ms each
DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u}; DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
@ -505,7 +494,7 @@ void WinMMCapture::open(std::string_view name)
// Allocate circular memory buffer for the captured audio // Allocate circular memory buffer for the captured audio
// Make sure circular buffer is at least 100ms in size // Make sure circular buffer is at least 100ms in size
const auto CapturedDataSize = std::max<size_t>(mDevice->BufferSize, const auto CapturedDataSize = std::max<size_t>(mDevice->mBufferSize,
BufferSize*mWaveBuffer.size()); BufferSize*mWaveBuffer.size());
mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false); mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
@ -521,7 +510,7 @@ void WinMMCapture::open(std::string_view name)
mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength; mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
} }
mDevice->DeviceName = CaptureDevices[DeviceID]; mDeviceName = CaptureDevices[DeviceID];
} }
void WinMMCapture::start() void WinMMCapture::start()
@ -534,13 +523,13 @@ void WinMMCapture::start()
} }
mKillNow.store(false, std::memory_order_release); mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this}; mThread = std::thread{&WinMMCapture::captureProc, this};
waveInStart(mInHdl); waveInStart(mInHdl);
} }
catch(std::exception& e) { catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording thread: %s", e.what()}; "Failed to start recording thread: {}", e.what()};
} }
} }

View file

@ -6,12 +6,12 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstring>
#include <functional> #include <functional>
#include <limits> #include <iterator>
#include <numeric> #include <numeric>
#include <stdexcept> #include <optional>
#include <string_view> #include <string_view>
#include <tuple>
#include <utility> #include <utility>
#include "AL/efx.h" #include "AL/efx.h"
@ -25,19 +25,22 @@
#include "albit.h" #include "albit.h"
#include "alc/alu.h" #include "alc/alu.h"
#include "alc/backends/base.h" #include "alc/backends/base.h"
#include "alnumeric.h"
#include "alspan.h" #include "alspan.h"
#include "atomic.h"
#include "core/async_event.h" #include "core/async_event.h"
#include "core/devformat.h"
#include "core/device.h" #include "core/device.h"
#include "core/effectslot.h" #include "core/effectslot.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/voice.h"
#include "core/voice_change.h" #include "core/voice_change.h"
#include "device.h" #include "device.h"
#include "flexarray.h" #include "flexarray.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include "vecmat.h" #include "vecmat.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "al/eax/call.h"
#include "al/eax/globals.h" #include "al/eax/globals.h"
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -70,7 +73,7 @@ std::vector<std::string_view> getContextExtensions() noexcept
"AL_EXT_STEREO_ANGLES"sv, "AL_EXT_STEREO_ANGLES"sv,
"AL_LOKI_quadriphonic"sv, "AL_LOKI_quadriphonic"sv,
"AL_SOFT_bformat_ex"sv, "AL_SOFT_bformat_ex"sv,
"AL_SOFTX_bformat_hoa"sv, "AL_SOFT_bformat_hoa"sv,
"AL_SOFT_block_alignment"sv, "AL_SOFT_block_alignment"sv,
"AL_SOFT_buffer_length_query"sv, "AL_SOFT_buffer_length_query"sv,
"AL_SOFT_callback_buffer"sv, "AL_SOFT_callback_buffer"sv,
@ -107,7 +110,7 @@ ALCcontext::ThreadCtx::~ThreadCtx()
if(ALCcontext *ctx{std::exchange(ALCcontext::sLocalContext, nullptr)}) if(ALCcontext *ctx{std::exchange(ALCcontext::sLocalContext, nullptr)})
{ {
const bool result{ctx->releaseIfNoDelete()}; const bool result{ctx->releaseIfNoDelete()};
ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx}, ERR("Context {} current for thread being destroyed{}!", voidp{ctx},
result ? "" : ", leak detected"); result ? "" : ", leak detected");
} }
} }
@ -116,26 +119,30 @@ thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext;
ALeffect ALCcontext::sDefaultEffect; ALeffect ALCcontext::sDefaultEffect;
ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> device, ContextFlagBitset flags) ALCcontext::ALCcontext(al::intrusive_ptr<al::Device> device, ContextFlagBitset flags)
: ContextBase{device.get()}, mALDevice{std::move(device)}, mContextFlags{flags} : ContextBase{device.get()}, mALDevice{std::move(device)}, mContextFlags{flags}
{ {
mDebugGroups.emplace_back(DebugSource::Other, 0, std::string{}); mDebugGroups.emplace_back(DebugSource::Other, 0, std::string{});
mDebugEnabled.store(mContextFlags.test(ContextFlags::DebugBit), std::memory_order_relaxed); mDebugEnabled.store(mContextFlags.test(ContextFlags::DebugBit), std::memory_order_relaxed);
/* Low-severity debug messages are disabled by default. */
alDebugMessageControlDirectEXT(this, AL_DONT_CARE_EXT, AL_DONT_CARE_EXT,
AL_DEBUG_SEVERITY_LOW_EXT, 0, nullptr, AL_FALSE);
} }
ALCcontext::~ALCcontext() ALCcontext::~ALCcontext()
{ {
TRACE("Freeing context %p\n", voidp{this}); TRACE("Freeing context {}", voidp{this});
size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), 0_uz, size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), 0_uz,
[](size_t cur, const SourceSubList &sublist) noexcept -> size_t [](size_t cur, const SourceSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })}; { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
if(count > 0) if(count > 0)
WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s"); WARN("{} Source{} not deleted", count, (count==1)?"":"s");
mSourceList.clear(); mSourceList.clear();
mNumSources = 0; mNumSources = 0;
#ifdef ALSOFT_EAX #if ALSOFT_EAX
eaxUninitialize(); eaxUninitialize();
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -144,7 +151,7 @@ ALCcontext::~ALCcontext()
[](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); }); { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0) if(count > 0)
WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s"); WARN("{} AuxiliaryEffectSlot{} not deleted", count, (count==1)?"":"s");
mEffectSlotList.clear(); mEffectSlotList.clear();
mNumEffectSlots = 0; mNumEffectSlots = 0;
} }
@ -183,13 +190,15 @@ void ALCcontext::init()
{ {
auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"sv); auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"sv);
if(iter != mExtensions.end()) mExtensions.erase(iter); if(iter != mExtensions.end()) mExtensions.erase(iter);
/* TODO: Would be nice to sort this alphabetically. Needs case-
* insensitive searching. /* Insert the AL_SOFT_buffer_sub_data extension string between
* AL_SOFT_buffer_length_query and AL_SOFT_callback_buffer.
*/ */
mExtensions.emplace_back("AL_SOFT_buffer_sub_data"sv); iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_SOFT_callback_buffer"sv);
mExtensions.emplace(iter, "AL_SOFT_buffer_sub_data"sv);
} }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
eax_initialize_extensions(); eax_initialize_extensions();
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -212,14 +221,26 @@ void ALCcontext::init()
mExtensionsString = std::move(extensions); mExtensionsString = std::move(extensions);
} }
#if ALSOFT_EAX
eax_set_defaults();
#endif
mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f}; mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f};
mParams.Matrix = alu::Matrix::Identity(); mParams.Matrix = alu::Matrix::Identity();
mParams.Velocity = alu::Vector{}; mParams.Velocity = alu::Vector{};
mParams.Gain = mListener.Gain; mParams.Gain = mListener.Gain;
mParams.MetersPerUnit = mListener.mMetersPerUnit; mParams.MetersPerUnit = mListener.mMetersPerUnit
#if ALSOFT_EAX
* eaxGetDistanceFactor()
#endif
;
mParams.AirAbsorptionGainHF = mAirAbsorptionGainHF; mParams.AirAbsorptionGainHF = mAirAbsorptionGainHF;
mParams.DopplerFactor = mDopplerFactor; mParams.DopplerFactor = mDopplerFactor;
mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity; mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity
#if ALSOFT_EAX
/ eaxGetDistanceFactor()
#endif
;
mParams.SourceDistanceModel = mSourceDistanceModel; mParams.SourceDistanceModel = mSourceDistanceModel;
mParams.mDistanceModel = mDistanceModel; mParams.mDistanceModel = mDistanceModel;
@ -236,35 +257,34 @@ void ALCcontext::deinit()
{ {
if(sLocalContext == this) if(sLocalContext == this)
{ {
WARN("%p released while current on thread\n", voidp{this}); WARN("{} released while current on thread", voidp{this});
auto _ = ContextRef{sLocalContext};
sThreadContext.set(nullptr); sThreadContext.set(nullptr);
dec_ref();
} }
ALCcontext *origctx{this}; if(ALCcontext *origctx{this}; sGlobalContext.compare_exchange_strong(origctx, nullptr))
if(sGlobalContext.compare_exchange_strong(origctx, nullptr))
{ {
auto _ = ContextRef{origctx};
while(sGlobalContextLock.load()) { while(sGlobalContextLock.load()) {
/* Wait to make sure another thread didn't get the context and is /* Wait to make sure another thread didn't get the context and is
* trying to increment its refcount. * trying to increment its refcount.
*/ */
} }
dec_ref();
} }
bool stopPlayback{}; bool stopPlayback{};
/* First make sure this context exists in the device's list. */ /* First make sure this context exists in the device's list. */
auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire); auto oldarray = al::span{*mDevice->mContexts.load(std::memory_order_acquire)};
if(auto toremove = static_cast<size_t>(std::count(oldarray->begin(), oldarray->end(), this))) if(auto toremove = static_cast<size_t>(std::count(oldarray.begin(), oldarray.end(), this)))
{ {
using ContextArray = al::FlexArray<ContextBase*>; using ContextArray = al::FlexArray<ContextBase*>;
const size_t newsize{oldarray->size() - toremove}; const auto newsize = size_t{oldarray.size() - toremove};
auto newarray = ContextArray::Create(newsize); auto newarray = ContextArray::Create(newsize);
/* Copy the current/old context handles to the new array, excluding the /* Copy the current/old context handles to the new array, excluding the
* given context. * given context.
*/ */
std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(), std::copy_if(oldarray.begin(), oldarray.end(), newarray->begin(),
[this](ContextBase *ctx) { return ctx != this; }); [this](ContextBase *ctx) { return ctx != this; });
/* Store the new context array in the device. Wait for any current mix /* Store the new context array in the device. Wait for any current mix
@ -276,7 +296,7 @@ void ALCcontext::deinit()
stopPlayback = (newsize == 0); stopPlayback = (newsize == 0);
} }
else else
stopPlayback = oldarray->empty(); stopPlayback = oldarray.empty();
StopEventThrd(this); StopEventThrd(this);
@ -297,7 +317,7 @@ void ALCcontext::applyAllUpdates()
/* busy-wait */ /* busy-wait */
} }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
if(mEaxNeedsCommit) if(mEaxNeedsCommit)
eaxCommit(); eaxCommit();
#endif #endif
@ -314,7 +334,7 @@ void ALCcontext::applyAllUpdates()
} }
#ifdef ALSOFT_EAX #if ALSOFT_EAX
namespace { namespace {
template<typename F> template<typename F>
@ -492,7 +512,7 @@ void ALCcontext::eax_initialize()
eax_ensure_compatibility(); eax_ensure_compatibility();
eax_set_defaults(); eax_set_defaults();
eax_context_commit_air_absorbtion_hf(); eax_context_commit_air_absorption_hf();
eax_update_speaker_configuration(); eax_update_speaker_configuration();
eax_initialize_fx_slots(); eax_initialize_fx_slots();
@ -561,7 +581,8 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const
*/ */
case DevFmtAmbi3D: return SPEAKERS_7; case DevFmtAmbi3D: return SPEAKERS_7;
} }
ERR(EAX_PREFIX "Unexpected device channel format 0x%x.\n", mDevice->FmtChans); ERR(EAX_PREFIX "Unexpected device channel format {:#x}.",
uint{al::to_underlying(mDevice->FmtChans)});
return HEADPHONES; return HEADPHONES;
#undef EAX_PREFIX #undef EAX_PREFIX
@ -619,7 +640,7 @@ void ALCcontext::eax_context_set_defaults()
eax5_context_set_defaults(mEax5); eax5_context_set_defaults(mEax5);
mEax = mEax5.i; mEax = mEax5.i;
mEaxVersion = 5; mEaxVersion = 5;
mEaxDf = EaxDirtyFlags{}; mEaxDf.reset();
} }
void ALCcontext::eax_set_defaults() void ALCcontext::eax_set_defaults()
@ -746,14 +767,11 @@ void ALCcontext::eax_context_commit_primary_fx_slot_id()
void ALCcontext::eax_context_commit_distance_factor() void ALCcontext::eax_context_commit_distance_factor()
{ {
if(mListener.mMetersPerUnit == mEax.flDistanceFactor) /* mEax.flDistanceFactor was changed, so the context props are dirty. */
return;
mListener.mMetersPerUnit = mEax.flDistanceFactor;
mPropsDirty = true; mPropsDirty = true;
} }
void ALCcontext::eax_context_commit_air_absorbtion_hf() void ALCcontext::eax_context_commit_air_absorption_hf()
{ {
const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF); const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF);
@ -814,16 +832,16 @@ void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state)
dst_d = src; dst_d = src;
if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID)
mEaxDf |= eax_primary_fx_slot_id_dirty_bit; mEaxDf.set(eax_primary_fx_slot_id_dirty_bit);
if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) if(dst_i.flDistanceFactor != dst_d.flDistanceFactor)
mEaxDf |= eax_distance_factor_dirty_bit; mEaxDf.set(eax_distance_factor_dirty_bit);
if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF)
mEaxDf |= eax_air_absorption_hf_dirty_bit; mEaxDf.set(eax_air_absorption_hf_dirty_bit);
if(dst_i.flHFReference != dst_d.flHFReference) if(dst_i.flHFReference != dst_d.flHFReference)
mEaxDf |= eax_hf_reference_dirty_bit; mEaxDf.set(eax_hf_reference_dirty_bit);
} }
void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state) void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state)
@ -834,20 +852,20 @@ void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state)
eax4_defer_all(call, state); eax4_defer_all(call, state);
break; break;
case EAXCONTEXT_PRIMARYFXSLOTID: case EAXCONTEXT_PRIMARYFXSLOTID:
eax_defer<Eax4PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>( eax_defer<Eax4PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(call, state,
call, state, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
break; break;
case EAXCONTEXT_DISTANCEFACTOR: case EAXCONTEXT_DISTANCEFACTOR:
eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>( eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(call, state,
call, state, &EAX40CONTEXTPROPERTIES::flDistanceFactor); &EAX40CONTEXTPROPERTIES::flDistanceFactor);
break; break;
case EAXCONTEXT_AIRABSORPTIONHF: case EAXCONTEXT_AIRABSORPTIONHF:
eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>( eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(call, state,
call, state, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
break; break;
case EAXCONTEXT_HFREFERENCE: case EAXCONTEXT_HFREFERENCE:
eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>( eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(call, state,
call, state, &EAX40CONTEXTPROPERTIES::flHFReference); &EAX40CONTEXTPROPERTIES::flHFReference);
break; break;
default: default:
eax_set_misc(call); eax_set_misc(call);
@ -864,19 +882,19 @@ void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state)
dst_d = src; dst_d = src;
if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID)
mEaxDf |= eax_primary_fx_slot_id_dirty_bit; mEaxDf.set(eax_primary_fx_slot_id_dirty_bit);
if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) if(dst_i.flDistanceFactor != dst_d.flDistanceFactor)
mEaxDf |= eax_distance_factor_dirty_bit; mEaxDf.set(eax_distance_factor_dirty_bit);
if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF)
mEaxDf |= eax_air_absorption_hf_dirty_bit; mEaxDf.set(eax_air_absorption_hf_dirty_bit);
if(dst_i.flHFReference != dst_d.flHFReference) if(dst_i.flHFReference != dst_d.flHFReference)
mEaxDf |= eax_hf_reference_dirty_bit; mEaxDf.set(eax_hf_reference_dirty_bit);
if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor) if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor)
mEaxDf |= eax_macro_fx_factor_dirty_bit; mEaxDf.set(eax_macro_fx_factor_dirty_bit);
} }
void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state) void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state)
@ -887,24 +905,24 @@ void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state)
eax5_defer_all(call, state); eax5_defer_all(call, state);
break; break;
case EAXCONTEXT_PRIMARYFXSLOTID: case EAXCONTEXT_PRIMARYFXSLOTID:
eax_defer<Eax5PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>( eax_defer<Eax5PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(call, state,
call, state, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
break; break;
case EAXCONTEXT_DISTANCEFACTOR: case EAXCONTEXT_DISTANCEFACTOR:
eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>( eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(call, state,
call, state, &EAX50CONTEXTPROPERTIES::flDistanceFactor); &EAX50CONTEXTPROPERTIES::flDistanceFactor);
break; break;
case EAXCONTEXT_AIRABSORPTIONHF: case EAXCONTEXT_AIRABSORPTIONHF:
eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>( eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(call, state,
call, state, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
break; break;
case EAXCONTEXT_HFREFERENCE: case EAXCONTEXT_HFREFERENCE:
eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>( eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(call, state,
call, state, &EAX50CONTEXTPROPERTIES::flHFReference); &EAX50CONTEXTPROPERTIES::flHFReference);
break; break;
case EAXCONTEXT_MACROFXFACTOR: case EAXCONTEXT_MACROFXFACTOR:
eax_defer<Eax5MacroFxFactorValidator, eax_macro_fx_factor_dirty_bit>( eax_defer<Eax5MacroFxFactorValidator, eax_macro_fx_factor_dirty_bit>(call, state,
call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
break; break;
default: default:
eax_set_misc(call); eax_set_misc(call);
@ -922,49 +940,49 @@ void ALCcontext::eax_set(const EaxCall& call)
default: eax_fail_unknown_version(); default: eax_fail_unknown_version();
} }
if(version != mEaxVersion) if(version != mEaxVersion)
mEaxDf = ~EaxDirtyFlags(); mEaxDf.set();
mEaxVersion = version; mEaxVersion = version;
} }
void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df) void ALCcontext::eax4_context_commit(Eax4State& state, std::bitset<eax_dirty_bit_count>& dst_df)
{ {
if(mEaxDf == EaxDirtyFlags{}) if(mEaxDf.none())
return; return;
eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>( eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(state, dst_df,
state, dst_df, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
eax_context_commit_property<eax_distance_factor_dirty_bit>( eax_context_commit_property<eax_distance_factor_dirty_bit>(state, dst_df,
state, dst_df, &EAX40CONTEXTPROPERTIES::flDistanceFactor); &EAX40CONTEXTPROPERTIES::flDistanceFactor);
eax_context_commit_property<eax_air_absorption_hf_dirty_bit>( eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(state, dst_df,
state, dst_df, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
eax_context_commit_property<eax_hf_reference_dirty_bit>( eax_context_commit_property<eax_hf_reference_dirty_bit>(state, dst_df,
state, dst_df, &EAX40CONTEXTPROPERTIES::flHFReference); &EAX40CONTEXTPROPERTIES::flHFReference);
mEaxDf = EaxDirtyFlags{}; mEaxDf.reset();
} }
void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df) void ALCcontext::eax5_context_commit(Eax5State& state, std::bitset<eax_dirty_bit_count>& dst_df)
{ {
if(mEaxDf == EaxDirtyFlags{}) if(mEaxDf.none())
return; return;
eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>( eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(state, dst_df,
state, dst_df, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
eax_context_commit_property<eax_distance_factor_dirty_bit>( eax_context_commit_property<eax_distance_factor_dirty_bit>(state, dst_df,
state, dst_df, &EAX50CONTEXTPROPERTIES::flDistanceFactor); &EAX50CONTEXTPROPERTIES::flDistanceFactor);
eax_context_commit_property<eax_air_absorption_hf_dirty_bit>( eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(state, dst_df,
state, dst_df, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
eax_context_commit_property<eax_hf_reference_dirty_bit>( eax_context_commit_property<eax_hf_reference_dirty_bit>(state, dst_df,
state, dst_df, &EAX50CONTEXTPROPERTIES::flHFReference); &EAX50CONTEXTPROPERTIES::flHFReference);
eax_context_commit_property<eax_macro_fx_factor_dirty_bit>( eax_context_commit_property<eax_macro_fx_factor_dirty_bit>(state, dst_df,
state, dst_df, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
mEaxDf = EaxDirtyFlags{}; mEaxDf.reset();
} }
void ALCcontext::eax_context_commit() void ALCcontext::eax_context_commit()
{ {
auto dst_df = EaxDirtyFlags{}; auto dst_df = std::bitset<eax_dirty_bit_count>{};
switch(mEaxVersion) switch(mEaxVersion)
{ {
@ -981,25 +999,25 @@ void ALCcontext::eax_context_commit()
break; break;
} }
if(dst_df == EaxDirtyFlags{}) if(dst_df.none())
return; return;
if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) if(dst_df.test(eax_primary_fx_slot_id_dirty_bit))
eax_context_commit_primary_fx_slot_id(); eax_context_commit_primary_fx_slot_id();
if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{}) if(dst_df.test(eax_distance_factor_dirty_bit))
eax_context_commit_distance_factor(); eax_context_commit_distance_factor();
if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{}) if(dst_df.test(eax_air_absorption_hf_dirty_bit))
eax_context_commit_air_absorbtion_hf(); eax_context_commit_air_absorption_hf();
if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{}) if(dst_df.test(eax_hf_reference_dirty_bit))
eax_context_commit_hf_reference(); eax_context_commit_hf_reference();
if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{}) if(dst_df.test(eax_macro_fx_factor_dirty_bit))
eax_context_commit_macro_fx_factor(); eax_context_commit_macro_fx_factor();
if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) if(dst_df.test(eax_primary_fx_slot_id_dirty_bit))
eax_update_sources(); eax_update_sources();
} }

View file

@ -1,8 +1,10 @@
#ifndef ALC_CONTEXT_H #ifndef ALC_CONTEXT_H
#define ALC_CONTEXT_H #define ALC_CONTEXT_H
#include <array> #include "config.h"
#include <atomic> #include <atomic>
#include <bitset>
#include <cstdint> #include <cstdint>
#include <deque> #include <deque>
#include <memory> #include <memory>
@ -18,25 +20,24 @@
#include "AL/alext.h" #include "AL/alext.h"
#include "al/listener.h" #include "al/listener.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "althreads.h" #include "althreads.h"
#include "atomic.h"
#include "core/context.h" #include "core/context.h"
#include "inprogext.h" #include "fmt/core.h"
#include "intrusive_ptr.h" #include "intrusive_ptr.h"
#include "opthelpers.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "al/eax/call.h" #include "al/eax/api.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/fx_slot_index.h" #include "al/eax/fx_slot_index.h"
#include "al/eax/fx_slots.h" #include "al/eax/fx_slots.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
class EaxCall;
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
struct ALeffect; struct ALeffect;
struct ALeffectslot; struct ALeffectslot;
struct ALsource;
struct DebugGroup; struct DebugGroup;
struct EffectSlotSubList; struct EffectSlotSubList;
struct SourceSubList; struct SourceSubList;
@ -72,9 +73,12 @@ struct DebugLogEntry {
}; };
struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase { namespace al {
const al::intrusive_ptr<ALCdevice> mALDevice; struct Device;
} // namespace al
struct ALCcontext final : public al::intrusive_ref<ALCcontext>, ContextBase {
const al::intrusive_ptr<al::Device> mALDevice;
bool mPropsDirty{true}; bool mPropsDirty{true};
bool mDeferUpdates{false}; bool mDeferUpdates{false};
@ -118,15 +122,15 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
std::unique_ptr<ALeffectslot> mDefaultSlot; std::unique_ptr<ALeffectslot> mDefaultSlot;
std::vector<std::string_view> mExtensions; std::vector<std::string_view> mExtensions;
std::string mExtensionsString{}; std::string mExtensionsString;
std::unordered_map<ALuint,std::string> mSourceNames; std::unordered_map<ALuint,std::string> mSourceNames;
std::unordered_map<ALuint,std::string> mEffectSlotNames; std::unordered_map<ALuint,std::string> mEffectSlotNames;
ALCcontext(al::intrusive_ptr<ALCdevice> device, ContextFlagBitset flags); ALCcontext(al::intrusive_ptr<al::Device> device, ContextFlagBitset flags);
ALCcontext(const ALCcontext&) = delete; ALCcontext(const ALCcontext&) = delete;
ALCcontext& operator=(const ALCcontext&) = delete; ALCcontext& operator=(const ALCcontext&) = delete;
~ALCcontext(); ~ALCcontext() final;
void init(); void init();
/** /**
@ -159,12 +163,18 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
*/ */
void applyAllUpdates(); void applyAllUpdates();
#ifdef __MINGW32__ void setErrorImpl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args);
[[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
#else template<typename ...Args>
[[gnu::format(printf, 3, 4)]] void setError(ALenum errorCode, fmt::format_string<Args...> msg, Args&& ...args)
#endif { setErrorImpl(errorCode, msg, fmt::make_format_args(args...)); }
void setError(ALenum errorCode, const char *msg, ...);
[[noreturn]]
void throw_error_impl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args);
template<typename ...Args> [[noreturn]]
void throw_error(ALenum errorCode, fmt::format_string<Args...> fmt, Args&&... args)
{ throw_error_impl(errorCode, fmt, fmt::make_format_args(args...)); }
void sendDebugMessage(std::unique_lock<std::mutex> &debuglock, DebugSource source, void sendDebugMessage(std::unique_lock<std::mutex> &debuglock, DebugSource source,
DebugType type, ALuint id, DebugSeverity severity, std::string_view message); DebugType type, ALuint id, DebugSeverity severity, std::string_view message);
@ -191,6 +201,10 @@ private:
*/ */
class ThreadCtx { class ThreadCtx {
public: public:
ThreadCtx() = default;
ThreadCtx(const ThreadCtx&) = delete;
auto operator=(const ThreadCtx&) -> ThreadCtx& = delete;
~ThreadCtx(); ~ThreadCtx();
/* NOLINTBEGIN(readability-convert-member-functions-to-static) /* NOLINTBEGIN(readability-convert-member-functions-to-static)
* This should be non-static to invoke construction of the thread-local * This should be non-static to invoke construction of the thread-local
@ -210,7 +224,7 @@ public:
/* Default effect that applies to sources that don't have an effect on send 0. */ /* Default effect that applies to sources that don't have an effect on send 0. */
static ALeffect sDefaultEffect; static ALeffect sDefaultEffect;
#ifdef ALSOFT_EAX #if ALSOFT_EAX
bool hasEax() const noexcept { return mEaxIsInitialized; } bool hasEax() const noexcept { return mEaxIsInitialized; }
bool eaxIsCapable() const noexcept; bool eaxIsCapable() const noexcept;
@ -232,7 +246,11 @@ public:
void eaxSetLastError() noexcept; void eaxSetLastError() noexcept;
EaxFxSlotIndex eaxGetPrimaryFxSlotIndex() const noexcept [[nodiscard]]
auto eaxGetDistanceFactor() const noexcept -> float { return mEax.flDistanceFactor; }
[[nodiscard]]
auto eaxGetPrimaryFxSlotIndex() const noexcept -> EaxFxSlotIndex
{ return mEaxPrimaryFxSlotIndex; } { return mEaxPrimaryFxSlotIndex; }
const ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) const const ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) const
@ -247,11 +265,14 @@ public:
{ mEaxFxSlots.commit(); } { mEaxFxSlots.commit(); }
private: private:
static constexpr auto eax_primary_fx_slot_id_dirty_bit = EaxDirtyFlags{1} << 0; enum {
static constexpr auto eax_distance_factor_dirty_bit = EaxDirtyFlags{1} << 1; eax_primary_fx_slot_id_dirty_bit,
static constexpr auto eax_air_absorption_hf_dirty_bit = EaxDirtyFlags{1} << 2; eax_distance_factor_dirty_bit,
static constexpr auto eax_hf_reference_dirty_bit = EaxDirtyFlags{1} << 3; eax_air_absorption_hf_dirty_bit,
static constexpr auto eax_macro_fx_factor_dirty_bit = EaxDirtyFlags{1} << 4; eax_hf_reference_dirty_bit,
eax_macro_fx_factor_dirty_bit,
eax_dirty_bit_count
};
using Eax4Props = EAX40CONTEXTPROPERTIES; using Eax4Props = EAX40CONTEXTPROPERTIES;
@ -267,12 +288,11 @@ private:
Eax5Props d; // Deferred. Eax5Props d; // Deferred.
}; };
class ContextException : public EaxException class ContextException final : public EaxException {
{
public: public:
explicit ContextException(const char* message) explicit ContextException(const char *message)
: EaxException{"EAX_CONTEXT", message} : EaxException{"EAX_CONTEXT", message}
{} { }
}; };
struct Eax4PrimaryFxSlotIdValidator { struct Eax4PrimaryFxSlotIdValidator {
@ -420,7 +440,7 @@ private:
int mEaxVersion{}; // Current EAX version. int mEaxVersion{}; // Current EAX version.
bool mEaxNeedsCommit{}; bool mEaxNeedsCommit{};
EaxDirtyFlags mEaxDf{}; // Dirty flags for the current EAX version. std::bitset<eax_dirty_bit_count> mEaxDf; // Dirty flags for the current EAX version.
Eax5State mEax123{}; // EAX1/EAX2/EAX3 state. Eax5State mEax123{}; // EAX1/EAX2/EAX3 state.
Eax4State mEax4{}; // EAX4 state. Eax4State mEax4{}; // EAX4 state.
Eax5State mEax5{}; // EAX5 state. Eax5State mEax5{}; // EAX5 state.
@ -450,7 +470,7 @@ private:
// updates a dirty flag. // updates a dirty flag.
template< template<
typename TValidator, typename TValidator,
EaxDirtyFlags TDirtyBit, size_t DirtyBit,
typename TMemberResult, typename TMemberResult,
typename TProps, typename TProps,
typename TState> typename TState>
@ -463,20 +483,20 @@ private:
dst_d = src; dst_d = src;
if(dst_i != dst_d) if(dst_i != dst_d)
mEaxDf |= TDirtyBit; mEaxDf.set(DirtyBit);
} }
template< template<
EaxDirtyFlags TDirtyBit, size_t DirtyBit,
typename TMemberResult, typename TMemberResult,
typename TProps, typename TProps,
typename TState> typename TState>
void eax_context_commit_property(TState& state, EaxDirtyFlags& dst_df, void eax_context_commit_property(TState& state, std::bitset<eax_dirty_bit_count>& dst_df,
TMemberResult TProps::*member) noexcept TMemberResult TProps::*member) noexcept
{ {
if((mEaxDf & TDirtyBit) != EaxDirtyFlags{}) if(mEaxDf.test(DirtyBit))
{ {
dst_df |= TDirtyBit; dst_df.set(DirtyBit);
const auto& src_d = state.d.*member; const auto& src_d = state.d.*member;
state.i.*member = src_d; state.i.*member = src_d;
mEax.*member = src_d; mEax.*member = src_d;
@ -514,7 +534,7 @@ private:
void eax_context_commit_primary_fx_slot_id(); void eax_context_commit_primary_fx_slot_id();
void eax_context_commit_distance_factor(); void eax_context_commit_distance_factor();
void eax_context_commit_air_absorbtion_hf(); void eax_context_commit_air_absorption_hf();
void eax_context_commit_hf_reference(); void eax_context_commit_hf_reference();
void eax_context_commit_macro_fx_factor(); void eax_context_commit_macro_fx_factor();
@ -529,8 +549,8 @@ private:
void eax5_defer(const EaxCall& call, Eax5State& state); void eax5_defer(const EaxCall& call, Eax5State& state);
void eax_set(const EaxCall& call); void eax_set(const EaxCall& call);
void eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df); void eax4_context_commit(Eax4State& state, std::bitset<eax_dirty_bit_count>& dst_df);
void eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df); void eax5_context_commit(Eax5State& state, std::bitset<eax_dirty_bit_count>& dst_df);
void eax_context_commit(); void eax_context_commit();
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
}; };
@ -545,7 +565,7 @@ void UpdateContextProps(ALCcontext *context);
inline bool TrapALError{false}; inline bool TrapALError{false};
#ifdef ALSOFT_EAX #if ALSOFT_EAX
auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id, auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id,
ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum; ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum;

View file

@ -27,13 +27,14 @@ using voidp = void*;
} // namespace } // namespace
namespace al {
ALCdevice::ALCdevice(DeviceType type) : DeviceBase{type} Device::Device(DeviceType type) : DeviceBase{type}
{ } { }
ALCdevice::~ALCdevice() Device::~Device()
{ {
TRACE("Freeing device %p\n", voidp{this}); TRACE("Freeing device {}", voidp{this});
Backend = nullptr; Backend = nullptr;
@ -41,35 +42,35 @@ ALCdevice::~ALCdevice()
[](size_t cur, const BufferSubList &sublist) noexcept -> size_t [](size_t cur, const BufferSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })}; { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
if(count > 0) if(count > 0)
WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); WARN("{} Buffer{} not deleted", count, (count==1)?"":"s");
count = std::accumulate(EffectList.cbegin(), EffectList.cend(), 0_uz, count = std::accumulate(EffectList.cbegin(), EffectList.cend(), 0_uz,
[](size_t cur, const EffectSubList &sublist) noexcept -> size_t [](size_t cur, const EffectSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); }); { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0) if(count > 0)
WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); WARN("{} Effect{} not deleted", count, (count==1)?"":"s");
count = std::accumulate(FilterList.cbegin(), FilterList.cend(), 0_uz, count = std::accumulate(FilterList.cbegin(), FilterList.cend(), 0_uz,
[](size_t cur, const FilterSubList &sublist) noexcept -> size_t [](size_t cur, const FilterSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); }); { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0) if(count > 0)
WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s"); WARN("{} Filter{} not deleted", count, (count==1)?"":"s");
} }
void ALCdevice::enumerateHrtfs() void Device::enumerateHrtfs()
{ {
mHrtfList = EnumerateHrtf(configValue<std::string>({}, "hrtf-paths")); mHrtfList = EnumerateHrtf(configValue<std::string>({}, "hrtf-paths"));
if(auto defhrtfopt = configValue<std::string>({}, "default-hrtf")) if(auto defhrtfopt = configValue<std::string>({}, "default-hrtf"))
{ {
auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt); auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt);
if(iter == mHrtfList.end()) if(iter == mHrtfList.end())
WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str()); WARN("Failed to find default HRTF \"{}\"", *defhrtfopt);
else if(iter != mHrtfList.begin()) else if(iter != mHrtfList.begin())
std::rotate(mHrtfList.begin(), iter, iter+1); std::rotate(mHrtfList.begin(), iter, iter+1);
} }
} }
auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 auto Device::getOutputMode1() const noexcept -> OutputMode1
{ {
if(mContexts.load(std::memory_order_relaxed)->empty()) if(mContexts.load(std::memory_order_relaxed)->empty())
return OutputMode1::Any; return OutputMode1::Any;
@ -95,3 +96,5 @@ auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1
} }
return OutputMode1::Any; return OutputMode1::Any;
} }
} // namespace al

View file

@ -1,6 +1,8 @@
#ifndef ALC_DEVICE_H #ifndef ALC_DEVICE_H
#define ALC_DEVICE_H #define ALC_DEVICE_H
#include "config.h"
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -18,7 +20,7 @@
#include "core/device.h" #include "core/device.h"
#include "intrusive_ptr.h" #include "intrusive_ptr.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "al/eax/x_ram.h" #include "al/eax/x_ram.h"
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -30,7 +32,11 @@ struct FilterSubList;
using uint = unsigned int; using uint = unsigned int;
struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase { struct ALCdevice { virtual ~ALCdevice() = default; };
namespace al {
struct Device final : public ALCdevice, al::intrusive_ref<al::Device>, DeviceBase {
/* This lock protects the device state (format, update size, etc) from /* This lock protects the device state (format, update size, etc) from
* being from being changed in multiple threads, or being accessed while * being from being changed in multiple threads, or being accessed while
* being changed. It's also used to serialize calls to the backend. * being changed. It's also used to serialize calls to the backend.
@ -80,7 +86,7 @@ struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
std::mutex FilterLock; std::mutex FilterLock;
std::vector<FilterSubList> FilterList; std::vector<FilterSubList> FilterList;
#ifdef ALSOFT_EAX #if ALSOFT_EAX
ALuint eax_x_ram_free_size{eax_x_ram_max_size}; ALuint eax_x_ram_free_size{eax_x_ram_max_size};
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -89,35 +95,41 @@ struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
std::unordered_map<ALuint,std::string> mEffectNames; std::unordered_map<ALuint,std::string> mEffectNames;
std::unordered_map<ALuint,std::string> mFilterNames; std::unordered_map<ALuint,std::string> mFilterNames;
ALCdevice(DeviceType type); std::string mVendorOverride;
~ALCdevice(); std::string mVersionOverride;
std::string mRendererOverride;
explicit Device(DeviceType type);
~Device() final;
void enumerateHrtfs(); void enumerateHrtfs();
bool getConfigValueBool(const std::string_view block, const std::string_view key, bool def) bool getConfigValueBool(const std::string_view block, const std::string_view key, bool def)
{ return GetConfigValueBool(DeviceName, block, key, def); } { return GetConfigValueBool(mDeviceName, block, key, def); }
template<typename T> template<typename T>
inline std::optional<T> configValue(const std::string_view block, const std::string_view key) = delete; auto configValue(const std::string_view block, const std::string_view key) -> std::optional<T> = delete;
}; };
template<> template<> inline
inline std::optional<std::string> ALCdevice::configValue(const std::string_view block, const std::string_view key) auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<std::string>
{ return ConfigValueStr(DeviceName, block, key); } { return ConfigValueStr(mDeviceName, block, key); }
template<> template<> inline
inline std::optional<int> ALCdevice::configValue(const std::string_view block, const std::string_view key) auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<int>
{ return ConfigValueInt(DeviceName, block, key); } { return ConfigValueInt(mDeviceName, block, key); }
template<> template<> inline
inline std::optional<uint> ALCdevice::configValue(const std::string_view block, const std::string_view key) auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<uint>
{ return ConfigValueUInt(DeviceName, block, key); } { return ConfigValueUInt(mDeviceName, block, key); }
template<> template<> inline
inline std::optional<float> ALCdevice::configValue(const std::string_view block, const std::string_view key) auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<float>
{ return ConfigValueFloat(DeviceName, block, key); } { return ConfigValueFloat(mDeviceName, block, key); }
template<> template<> inline
inline std::optional<bool> ALCdevice::configValue(const std::string_view block, const std::string_view key) auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<bool>
{ return ConfigValueBool(DeviceName, block, key); } { return ConfigValueBool(mDeviceName, block, key); }
} // namespace al
/** Stores the latest ALC device error. */ /** Stores the latest ALC device error. */
void alcSetError(ALCdevice *device, ALCenum errorCode); void alcSetError(al::Device *device, ALCenum errorCode);
#endif #endif

View file

@ -122,7 +122,7 @@ void AutowahState::update(const ContextBase *context, const EffectSlot *slot,
{ {
auto &props = std::get<AutowahProps>(*props_); auto &props = std::get<AutowahProps>(*props_);
const DeviceBase *device{context->mDevice}; const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency); const auto frequency = static_cast<float>(device->mSampleRate);
const float ReleaseTime{std::clamp(props.ReleaseTime, 0.001f, 1.0f)}; const float ReleaseTime{std::clamp(props.ReleaseTime, 0.001f, 1.0f)};

View file

@ -105,7 +105,7 @@ struct ChorusState final : public EffectState {
void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
{ {
constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay); constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay);
const auto frequency = static_cast<float>(Device->Frequency); const auto frequency = static_cast<float>(Device->mSampleRate);
const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)}; const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)};
if(maxlen != mDelayBuffer.size()) if(maxlen != mDelayBuffer.size())
decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer); decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer);
@ -128,7 +128,7 @@ void ChorusState::update(const ContextBase *context, const EffectSlot *slot,
* delay and depth to allow enough padding for resampling. * delay and depth to allow enough padding for resampling.
*/ */
const DeviceBase *device{context->mDevice}; const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency); const auto frequency = static_cast<float>(device->mSampleRate);
mWaveform = props.Waveform; mWaveform = props.Waveform;

View file

@ -89,8 +89,8 @@ void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage
/* Number of samples to do a full attack and release (non-integer sample /* Number of samples to do a full attack and release (non-integer sample
* counts are okay). * counts are okay).
*/ */
const float attackCount{static_cast<float>(device->Frequency) * AttackTime}; const float attackCount{static_cast<float>(device->mSampleRate) * AttackTime};
const float releaseCount{static_cast<float>(device->Frequency) * ReleaseTime}; const float releaseCount{static_cast<float>(device->mSampleRate) * ReleaseTime};
/* Calculate per-sample multipliers to attack and release at the desired /* Calculate per-sample multipliers to attack and release at the desired
* rates. * rates.

View file

@ -1,5 +1,6 @@
#include "config.h" #include "config.h"
#include "config_simd.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@ -11,11 +12,10 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <variant>
#ifdef HAVE_SSE_INTRINSICS #if HAVE_SSE_INTRINSICS
#include <xmmintrin.h> #include <xmmintrin.h>
#elif defined(HAVE_NEON) #elif HAVE_NEON
#include <arm_neon.h> #include <arm_neon.h>
#endif #endif
@ -171,7 +171,7 @@ constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2};
void apply_fir(al::span<float> dst, const al::span<const float> input, const al::span<const float,ConvolveUpdateSamples> filter) void apply_fir(al::span<float> dst, const al::span<const float> input, const al::span<const float,ConvolveUpdateSamples> filter)
{ {
auto src = input.begin(); auto src = input.begin();
#ifdef HAVE_SSE_INTRINSICS #if HAVE_SSE_INTRINSICS
std::generate(dst.begin(), dst.end(), [&src,filter] std::generate(dst.begin(), dst.end(), [&src,filter]
{ {
__m128 r4{_mm_setzero_ps()}; __m128 r4{_mm_setzero_ps()};
@ -189,7 +189,7 @@ void apply_fir(al::span<float> dst, const al::span<const float> input, const al:
return _mm_cvtss_f32(r4); return _mm_cvtss_f32(r4);
}); });
#elif defined(HAVE_NEON) #elif HAVE_NEON
std::generate(dst.begin(), dst.end(), [&src,filter] std::generate(dst.begin(), dst.end(), [&src,filter]
{ {
@ -227,7 +227,7 @@ struct ConvolutionState final : public EffectState {
al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter; al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter;
al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput; al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput;
PFFFTSetup mFft{}; PFFFTSetup mFft;
alignas(16) std::array<float,ConvolveUpdateSize> mFftBuffer{}; alignas(16) std::array<float,ConvolveUpdateSize> mFftBuffer{};
alignas(16) std::array<float,ConvolveUpdateSize> mFftWorkBuffer{}; alignas(16) std::array<float,ConvolveUpdateSize> mFftWorkBuffer{};
@ -237,7 +237,7 @@ struct ConvolutionState final : public EffectState {
struct ChannelData { struct ChannelData {
alignas(16) FloatBufferLine mBuffer{}; alignas(16) FloatBufferLine mBuffer{};
float mHfScale{}, mLfScale{}; float mHfScale{}, mLfScale{};
BandSplitter mFilter{}; BandSplitter mFilter;
std::array<float,MaxOutputChannels> Current{}; std::array<float,MaxOutputChannels> Current{};
std::array<float,MaxOutputChannels> Target{}; std::array<float,MaxOutputChannels> Target{};
}; };
@ -322,13 +322,13 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag
* called very infrequently, go ahead and use the polyphase resampler. * called very infrequently, go ahead and use the polyphase resampler.
*/ */
PPhaseResampler resampler; PPhaseResampler resampler;
if(device->Frequency != buffer->mSampleRate) if(device->mSampleRate != buffer->mSampleRate)
resampler.init(buffer->mSampleRate, device->Frequency); resampler.init(buffer->mSampleRate, device->mSampleRate);
const auto resampledCount = static_cast<uint>( const auto resampledCount = static_cast<uint>(
(uint64_t{buffer->mSampleLen}*device->Frequency+(buffer->mSampleRate-1)) / (uint64_t{buffer->mSampleLen}*device->mSampleRate+(buffer->mSampleRate-1)) /
buffer->mSampleRate); buffer->mSampleRate);
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)}; const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->mSampleRate)};
for(auto &e : mChans) for(auto &e : mChans)
e.mFilter = splitter; e.mFilter = splitter;

View file

@ -87,7 +87,7 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
/* Divide normalized frequency by the amount of oversampling done during /* Divide normalized frequency by the amount of oversampling done during
* processing. * processing.
*/ */
auto frequency = static_cast<float>(device->Frequency); auto frequency = static_cast<float>(device->mSampleRate);
mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth); mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
cutoff = props.EQCenter; cutoff = props.EQCenter;

View file

@ -78,7 +78,7 @@ struct EchoState final : public EffectState {
void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
{ {
const auto frequency = static_cast<float>(Device->Frequency); const auto frequency = static_cast<float>(Device->mSampleRate);
// Use the next power of 2 for the buffer length, so the tap offsets can be // Use the next power of 2 for the buffer length, so the tap offsets can be
// wrapped using a mask instead of a modulo // wrapped using a mask instead of a modulo
@ -100,7 +100,7 @@ void EchoState::update(const ContextBase *context, const EffectSlot *slot,
{ {
auto &props = std::get<EchoProps>(*props_); auto &props = std::get<EchoProps>(*props_);
const DeviceBase *device{context->mDevice}; const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency); const auto frequency = static_cast<float>(device->mSampleRate);
mDelayTap[0] = std::max(float2uint(std::round(props.Delay*frequency)), 1u); mDelayTap[0] = std::max(float2uint(std::round(props.Delay*frequency)), 1u);
mDelayTap[1] = float2uint(std::round(props.LRDelay*frequency)) + mDelayTap[0]; mDelayTap[1] = float2uint(std::round(props.LRDelay*frequency)) + mDelayTap[0];

View file

@ -123,7 +123,7 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
{ {
auto &props = std::get<EqualizerProps>(*props_); auto &props = std::get<EqualizerProps>(*props_);
const DeviceBase *device{context->mDevice}; const DeviceBase *device{context->mDevice};
auto frequency = static_cast<float>(device->Frequency); auto frequency = static_cast<float>(device->mSampleRate);
/* Calculate coefficients for the each type of filter. Note that the shelf /* Calculate coefficients for the each type of filter. Note that the shelf
* and peaking filters' gain is for the centerpoint of the transition band, * and peaking filters' gain is for the centerpoint of the transition band,

View file

@ -134,7 +134,7 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
auto &props = std::get<FshifterProps>(*props_); auto &props = std::get<FshifterProps>(*props_);
const DeviceBase *device{context->mDevice}; const DeviceBase *device{context->mDevice};
const float step{props.Frequency / static_cast<float>(device->Frequency)}; const float step{props.Frequency / static_cast<float>(device->mSampleRate)};
mPhaseStep[0] = mPhaseStep[1] = fastf2u(std::min(step, 1.0f) * MixerFracOne); mPhaseStep[0] = mPhaseStep[1] = fastf2u(std::min(step, 1.0f) * MixerFracOne);
switch(props.LeftDirection) switch(props.LeftDirection)

View file

@ -122,10 +122,10 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
* many iterations per sample. * many iterations per sample.
*/ */
const float samplesPerCycle{props.Frequency > 0.0f const float samplesPerCycle{props.Frequency > 0.0f
? static_cast<float>(device->Frequency)/props.Frequency + 0.5f ? static_cast<float>(device->mSampleRate)/props.Frequency + 0.5f
: 1.0f}; : 1.0f};
const uint range{static_cast<uint>(std::clamp(samplesPerCycle, 1.0f, const uint range{static_cast<uint>(std::clamp(samplesPerCycle, 1.0f,
static_cast<float>(device->Frequency)))}; static_cast<float>(device->mSampleRate)))};
mIndex = static_cast<uint>(uint64_t{mIndex} * range / mRange); mIndex = static_cast<uint>(uint64_t{mIndex} * range / mRange);
mRange = range; mRange = range;
@ -155,7 +155,7 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
mSampleGen.emplace<SquareFunc>(); mSampleGen.emplace<SquareFunc>();
} }
float f0norm{props.HighPassCutoff / static_cast<float>(device->Frequency)}; float f0norm{props.HighPassCutoff / static_cast<float>(device->mSampleRate)};
f0norm = std::clamp(f0norm, 1.0f/512.0f, 0.49f); f0norm = std::clamp(f0norm, 1.0f/512.0f, 0.49f);
/* Bandwidth value is constant in octaves. */ /* Bandwidth value is constant in octaves. */
mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);

View file

@ -28,8 +28,6 @@
#include <cstdio> #include <cstdio>
#include <functional> #include <functional>
#include <numeric> #include <numeric>
#include <utility>
#include <variant>
#include "alc/effects/base.h" #include "alc/effects/base.h"
#include "alnumbers.h" #include "alnumbers.h"
@ -403,7 +401,7 @@ struct EarlyReflections {
*/ */
DelayLineU Delay; DelayLineU Delay;
std::array<size_t,NUM_LINES> Offset{}; std::array<size_t,NUM_LINES> Offset{};
std::array<float,NUM_LINES> Coeff{}; float Coeff{};
/* The gain for each output channel based on 3D panning. */ /* The gain for each output channel based on 3D panning. */
struct OutGains { struct OutGains {
@ -502,7 +500,7 @@ struct ReverbPipeline {
/* Tap points for early reflection input delay. */ /* Tap points for early reflection input delay. */
std::array<std::array<size_t,2>,NUM_LINES> mEarlyDelayTap{}; std::array<std::array<size_t,2>,NUM_LINES> mEarlyDelayTap{};
std::array<std::array<float,2>,NUM_LINES> mEarlyDelayCoeff{}; std::array<float,2> mEarlyDelayCoeff{};
/* Tap points for late reverb feed and delay. */ /* Tap points for late reverb feed and delay. */
std::array<std::array<size_t,2>,NUM_LINES> mLateDelayTap{}; std::array<std::array<size_t,2>,NUM_LINES> mLateDelayTap{};
@ -520,7 +518,7 @@ struct ReverbPipeline {
size_t mFadeSampleCount{1}; size_t mFadeSampleCount{1};
void updateDelayLine(const float gain, const float earlyDelay, const float lateDelay, void updateDelayLine(const float gain, const float earlyDelay, const float lateDelay,
const float density_mult, const float decayTime, const float frequency); const float density_mult, const float frequency);
void update3DPanning(const al::span<const float,3> ReflectionsPan, void update3DPanning(const al::span<const float,3> ReflectionsPan,
const al::span<const float,3> LateReverbPan, const float earlyGain, const float lateGain, const al::span<const float,3> LateReverbPan, const float earlyGain, const float lateGain,
const bool doUpmix, const MixParams *mainMix); const bool doUpmix, const MixParams *mainMix);
@ -792,7 +790,7 @@ void ReverbState::allocLines(const float frequency)
void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*)
{ {
const auto frequency = static_cast<float>(device->Frequency); const auto frequency = static_cast<float>(device->mSampleRate);
/* Allocate the delay lines. */ /* Allocate the delay lines. */
allocLines(frequency); allocLines(frequency);
@ -928,10 +926,15 @@ void EarlyReflections::updateLines(const float density_mult, const float diffusi
/* Calculate the delay length of each delay line. */ /* Calculate the delay length of each delay line. */
length = EARLY_LINE_LENGTHS[i] * density_mult; length = EARLY_LINE_LENGTHS[i] * density_mult;
Offset[i] = float2uint(length * frequency); Offset[i] = float2uint(length * frequency);
/* Calculate the gain (coefficient) for each line. */
Coeff[i] = CalcDecayCoeff(length, decayTime);
} }
/* Calculate the gain (coefficient) for the secondary reflections based on
* the average delay and decay time.
*/
const auto length = std::reduce(EARLY_LINE_LENGTHS.begin(), EARLY_LINE_LENGTHS.end(), 0.0f)
/ float{EARLY_LINE_LENGTHS.size()} * density_mult;
Coeff = CalcDecayCoeff(length, decayTime);
} }
/* Update the EAX modulation step and depth. Keep in mind that this kind of /* Update the EAX modulation step and depth. Keep in mind that this kind of
@ -1038,7 +1041,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion,
/* Update the offsets for the main effect delay line. */ /* Update the offsets for the main effect delay line. */
void ReverbPipeline::updateDelayLine(const float gain, const float earlyDelay, void ReverbPipeline::updateDelayLine(const float gain, const float earlyDelay,
const float lateDelay, const float density_mult, const float decayTime, const float frequency) const float lateDelay, const float density_mult, const float frequency)
{ {
/* Early reflection taps are decorrelated by means of an average room /* Early reflection taps are decorrelated by means of an average room
* reflection approximation described above the definition of the taps. * reflection approximation described above the definition of the taps.
@ -1050,11 +1053,11 @@ void ReverbPipeline::updateDelayLine(const float gain, const float earlyDelay,
* delay path and offsets that would continue the propagation naturally * delay path and offsets that would continue the propagation naturally
* into the late lines. * into the late lines.
*/ */
mEarlyDelayCoeff[1] = gain;
for(size_t i{0u};i < NUM_LINES;i++) for(size_t i{0u};i < NUM_LINES;i++)
{ {
float length{EARLY_TAP_LENGTHS[i]*density_mult}; float length{EARLY_TAP_LENGTHS[i]*density_mult};
mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency); mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency);
mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime) * gain;
/* Reduce the late delay tap by the shortest early delay line length to /* Reduce the late delay tap by the shortest early delay line length to
* compensate for the late line input being fed by the delayed early * compensate for the late line input being fed by the delayed early
@ -1120,7 +1123,8 @@ void ReverbPipeline::update3DPanning(const al::span<const float,3> ReflectionsPa
const auto earlymat = GetTransformFromVector(ReflectionsPan); const auto earlymat = GetTransformFromVector(ReflectionsPan);
const auto latemat = GetTransformFromVector(LateReverbPan); const auto latemat = GetTransformFromVector(LateReverbPan);
const auto [earlycoeffs, latecoeffs] = [&]{ const auto get_coeffs = [&]
{
if(doUpmix) if(doUpmix)
{ {
/* When upsampling, combine the early and late transforms with the /* When upsampling, combine the early and late transforms with the
@ -1148,7 +1152,7 @@ void ReverbPipeline::update3DPanning(const al::span<const float,3> ReflectionsPa
return res; return res;
}; };
return std::make_pair(mult_matrix(earlymat), mult_matrix(latemat)); return std::array{mult_matrix(earlymat), mult_matrix(latemat)};
} }
/* When not upsampling, combine the early and late A-to-B-Format /* When not upsampling, combine the early and late A-to-B-Format
@ -1175,8 +1179,9 @@ void ReverbPipeline::update3DPanning(const al::span<const float,3> ReflectionsPa
return res; return res;
}; };
return std::make_pair(mult_matrix(EarlyA2B, earlymat), mult_matrix(LateA2B, latemat)); return std::array{mult_matrix(EarlyA2B, earlymat), mult_matrix(LateA2B, latemat)};
}(); };
const auto [earlycoeffs, latecoeffs] = get_coeffs();
auto earlygains = mEarly.Gains.begin(); auto earlygains = mEarly.Gains.begin();
for(auto &coeffs : earlycoeffs) for(auto &coeffs : earlycoeffs)
@ -1191,7 +1196,7 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot,
{ {
auto &props = std::get<ReverbProps>(*props_); auto &props = std::get<ReverbProps>(*props_);
const DeviceBase *Device{Context->mDevice}; const DeviceBase *Device{Context->mDevice};
const auto frequency = static_cast<float>(Device->Frequency); const auto frequency = static_cast<float>(Device->mSampleRate);
/* If the HF limit parameter is flagged, calculate an appropriate limit /* If the HF limit parameter is flagged, calculate an appropriate limit
* based on the air absorption parameter. * based on the air absorption parameter.
@ -1243,8 +1248,7 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot,
mCurrentPipeline = !mCurrentPipeline; mCurrentPipeline = !mCurrentPipeline;
auto &oldpipeline = mPipelines[!mCurrentPipeline]; auto &oldpipeline = mPipelines[!mCurrentPipeline];
for(size_t j{0};j < NUM_LINES;++j) oldpipeline.mEarlyDelayCoeff[1] = 0.0f;
oldpipeline.mEarlyDelayCoeff[j][1] = 0.0f;
} }
auto &pipeline = mPipelines[mCurrentPipeline]; auto &pipeline = mPipelines[mCurrentPipeline];
@ -1253,7 +1257,7 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot,
/* Update the main effect delay and associated taps. */ /* Update the main effect delay and associated taps. */
pipeline.updateDelayLine(props.Gain, props.ReflectionsDelay, props.LateReverbDelay, pipeline.updateDelayLine(props.Gain, props.ReflectionsDelay, props.LateReverbDelay,
density_mult, props.DecayTime, frequency); density_mult, frequency);
/* Update early and late 3D panning. */ /* Update early and late 3D panning. */
mOutTarget = target.Main->Buffer; mOutTarget = target.Main->Buffer;
@ -1507,17 +1511,17 @@ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset,
/* First, load decorrelated samples from the main delay line as the /* First, load decorrelated samples from the main delay line as the
* primary reflections. * primary reflections.
*/ */
const auto fadeStep = float{1.0f / static_cast<float>(todo)}; const auto fadeStep = 1.0f / static_cast<float>(todo);
const auto earlycoeff0 = float{mEarlyDelayCoeff[0]};
const auto earlycoeff1 = float{mEarlyDelayCoeff[1]};
mEarlyDelayCoeff[0] = mEarlyDelayCoeff[1];
for(size_t j{0_uz};j < NUM_LINES;j++) for(size_t j{0_uz};j < NUM_LINES;j++)
{ {
const auto input = in_delay.get(j); const auto input = in_delay.get(j);
auto early_delay_tap0 = size_t{offset - mEarlyDelayTap[j][0]}; auto early_delay_tap0 = size_t{offset - mEarlyDelayTap[j][0]};
auto early_delay_tap1 = size_t{offset - mEarlyDelayTap[j][1]}; auto early_delay_tap1 = size_t{offset - mEarlyDelayTap[j][1]};
mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1]; mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1];
const auto coeff0 = float{mEarlyDelayCoeff[j][0]}; auto fadeCount = 0.0f;
const auto coeff1 = float{mEarlyDelayCoeff[j][1]};
mEarlyDelayCoeff[j][0] = mEarlyDelayCoeff[j][1];
auto fadeCount = float{0.0f};
auto tmp = tempSamples[j].begin(); auto tmp = tempSamples[j].begin();
for(size_t i{0_uz};i < todo;) for(size_t i{0_uz};i < todo;)
@ -1529,10 +1533,10 @@ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset,
const auto intap0 = input.subspan(early_delay_tap0, td); const auto intap0 = input.subspan(early_delay_tap0, td);
const auto intap1 = input.subspan(early_delay_tap1, td); const auto intap1 = input.subspan(early_delay_tap1, td);
auto do_blend = [coeff0,coeff1,fadeStep,&fadeCount](const float in0, auto do_blend = [earlycoeff0,earlycoeff1,fadeStep,&fadeCount](const float in0,
const float in1) noexcept -> float const float in1) noexcept -> float
{ {
const auto ret = lerpf(in0*coeff0, in1*coeff1, fadeStep*fadeCount); const auto ret = lerpf(in0*earlycoeff0, in1*earlycoeff1, fadeStep*fadeCount);
fadeCount += 1.0f; fadeCount += 1.0f;
return ret; return ret;
}; };
@ -1552,11 +1556,11 @@ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset,
/* Apply a delay and bounce to generate secondary reflections. */ /* Apply a delay and bounce to generate secondary reflections. */
early_delay.writeReflected(offset, tempSamples, todo); early_delay.writeReflected(offset, tempSamples, todo);
const auto feedb_coeff = mEarly.Coeff;
for(size_t j{0_uz};j < NUM_LINES;j++) for(size_t j{0_uz};j < NUM_LINES;j++)
{ {
const auto input = early_delay.get(j); const auto input = early_delay.get(j);
auto feedb_tap = size_t{offset - mEarly.Offset[j]}; auto feedb_tap = size_t{offset - mEarly.Offset[j]};
const auto feedb_coeff = float{mEarly.Coeff[j]};
auto out = outSamples[j].begin() + base; auto out = outSamples[j].begin() + base;
auto tmp = tempSamples[j].begin(); auto tmp = tempSamples[j].begin();
@ -1597,20 +1601,20 @@ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset,
auto Modulation::calcDelays(size_t todo) -> al::span<const uint> auto Modulation::calcDelays(size_t todo) -> al::span<const uint>
{ {
auto idx = uint{Index}; auto idx = Index;
const auto step = uint{Step}; const auto step = Step;
const auto depth = float{Depth * float{gCubicTable.sTableSteps}}; const auto depth = Depth * float{gCubicTable.sTableSteps};
const auto delays = al::span{ModDelays}.first(todo); const auto delays = al::span{ModDelays}.first(todo);
std::generate(delays.begin(), delays.end(), [step,depth,&idx] std::generate(delays.begin(), delays.end(), [step,depth,&idx]
{ {
idx += step; idx += step;
const auto x = float{static_cast<float>(idx&MOD_FRACMASK) * (1.0f/MOD_FRACONE)}; const auto x = static_cast<float>(idx&MOD_FRACMASK) * (1.0f/MOD_FRACONE);
/* Approximate sin(x*2pi). As long as it roughly fits a sinusoid shape /* Approximate sin(x*2pi). As long as it roughly fits a sinusoid shape
* and stays within [-1...+1], it needn't be perfect. * and stays within [-1...+1], it needn't be perfect.
*/ */
const auto lfo = float{!(idx&(MOD_FRACONE>>1)) const auto lfo = !(idx&(MOD_FRACONE>>1))
? ((-16.0f * x * x) + (8.0f * x)) ? ((-16.0f * x * x) + (8.0f * x))
: ((16.0f * x * x) + (-8.0f * x) + (-16.0f * x) + 8.0f)}; : ((16.0f * x * x) + (-8.0f * x) + (-16.0f * x) + 8.0f);
return float2uint((lfo+1.0f) * depth); return float2uint((lfo+1.0f) * depth);
}); });
Index = idx; Index = idx;
@ -1655,7 +1659,7 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo,
for(size_t j{0_uz};j < NUM_LINES;++j) for(size_t j{0_uz};j < NUM_LINES;++j)
{ {
const auto input = late_delay.get(j); const auto input = late_delay.get(j);
const auto midGain = float{mLate.T60[j].MidGain}; const auto midGain = mLate.T60[j].MidGain;
auto late_feedb_tap = size_t{offset - mLate.Offset[j]}; auto late_feedb_tap = size_t{offset - mLate.Offset[j]};
auto proc_sample = [input,midGain,&late_feedb_tap](const size_t idelay) -> float auto proc_sample = [input,midGain,&late_feedb_tap](const size_t idelay) -> float
@ -1663,7 +1667,7 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo,
/* Calculate the read sample offset and sub-sample offset /* Calculate the read sample offset and sub-sample offset
* between it and the next sample. * between it and the next sample.
*/ */
const auto delay = size_t{late_feedb_tap - (idelay>>gCubicTable.sTableBits)}; const auto delay = late_feedb_tap - (idelay>>gCubicTable.sTableBits);
const auto delayoffset = size_t{idelay & gCubicTable.sTableMask}; const auto delayoffset = size_t{idelay & gCubicTable.sTableMask};
++late_feedb_tap; ++late_feedb_tap;
@ -1675,10 +1679,10 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo,
const auto out2 = float{input[(delay-2) & (input.size()-1)]}; const auto out2 = float{input[(delay-2) & (input.size()-1)]};
const auto out3 = float{input[(delay-3) & (input.size()-1)]}; const auto out3 = float{input[(delay-3) & (input.size()-1)]};
const auto out = float{out0*gCubicTable.getCoeff0(delayoffset) const auto out = out0*gCubicTable.getCoeff0(delayoffset)
+ out1*gCubicTable.getCoeff1(delayoffset) + out1*gCubicTable.getCoeff1(delayoffset)
+ out2*gCubicTable.getCoeff2(delayoffset) + out2*gCubicTable.getCoeff2(delayoffset)
+ out3*gCubicTable.getCoeff3(delayoffset)}; + out3*gCubicTable.getCoeff3(delayoffset);
return out * midGain; return out * midGain;
}; };
std::transform(delays.begin(), delays.end(), tempSamples[j].begin(), proc_sample); std::transform(delays.begin(), delays.end(), tempSamples[j].begin(), proc_sample);
@ -1694,10 +1698,10 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo,
auto late_delay_tap0 = size_t{offset - mLateDelayTap[j][0]}; auto late_delay_tap0 = size_t{offset - mLateDelayTap[j][0]};
auto late_delay_tap1 = size_t{offset - mLateDelayTap[j][1]}; auto late_delay_tap1 = size_t{offset - mLateDelayTap[j][1]};
mLateDelayTap[j][0] = mLateDelayTap[j][1]; mLateDelayTap[j][0] = mLateDelayTap[j][1];
const auto densityGain = float{mLate.DensityGain}; const auto densityGain = mLate.DensityGain;
const auto densityStep = float{late_delay_tap0 != late_delay_tap1 const auto densityStep = late_delay_tap0 != late_delay_tap1
? densityGain*fadeStep : 0.0f}; ? densityGain*fadeStep : 0.0f;
auto fadeCount = float{0.0f}; auto fadeCount = 0.0f;
auto samples = tempSamples[j].begin(); auto samples = tempSamples[j].begin();
for(size_t i{0u};i < todo;) for(size_t i{0u};i < todo;)
@ -1766,8 +1770,7 @@ void ReverbState::process(const size_t samplesToDo, const al::span<const FloatBu
mMainDelay.write(offset, c, tmpspan); mMainDelay.write(offset, c, tmpspan);
} }
if(mPipelineState < Fading) mPipelineState = std::max(Fading, mPipelineState);
mPipelineState = Fading;
/* Process reverb for these samples. and mix them to the output. */ /* Process reverb for these samples. and mix them to the output. */
pipeline.processEarly(mMainDelay, offset, samplesToDo, mTempSamples, mEarlySamples); pipeline.processEarly(mMainDelay, offset, samplesToDo, mTempSamples, mEarlySamples);

View file

@ -249,7 +249,7 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
{ {
auto &props = std::get<VmorpherProps>(*props_); auto &props = std::get<VmorpherProps>(*props_);
const DeviceBase *device{context->mDevice}; const DeviceBase *device{context->mDevice};
const float frequency{static_cast<float>(device->Frequency)}; const float frequency{static_cast<float>(device->mSampleRate)};
const float step{props.Rate / frequency}; const float step{props.Rate / frequency};
mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f)); mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f));

View file

@ -3,9 +3,11 @@
#include "events.h" #include "events.h"
#include "alnumeric.h"
#include "alspan.h" #include "alspan.h"
#include "core/logging.h" #include "core/logging.h"
#include "device.h" #include "device.h"
#include "fmt/core.h"
namespace { namespace {
@ -19,7 +21,7 @@ ALCenum EnumFromEventType(const alc::EventType type)
case alc::EventType::DeviceRemoved: return ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT; case alc::EventType::DeviceRemoved: return ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT;
case alc::EventType::Count: break; case alc::EventType::Count: break;
} }
throw std::runtime_error{"Invalid EventType: "+std::to_string(al::to_underlying(type))}; throw std::runtime_error{fmt::format("Invalid EventType: {}", int{al::to_underlying(type)})};
} }
} // namespace } // namespace
@ -74,7 +76,7 @@ FORCE_ALIGN ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const AL
auto etype = alc::GetEventType(type); auto etype = alc::GetEventType(type);
if(!etype) if(!etype)
{ {
WARN("Invalid event type: 0x%04x\n", type); WARN("Invalid event type: {:#04x}", as_unsigned(type));
alcSetError(nullptr, ALC_INVALID_ENUM); alcSetError(nullptr, ALC_INVALID_ENUM);
return ALC_FALSE; return ALC_FALSE;
} }

View file

@ -1,12 +1,14 @@
#ifndef ALC_EXPORT_LIST_H #ifndef ALC_EXPORT_LIST_H
#define ALC_EXPORT_LIST_H #define ALC_EXPORT_LIST_H
#include "config.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "AL/al.h" #include "AL/al.h"
#include "AL/alext.h" #include "AL/alext.h"
#include "inprogext.h" #include "inprogext.h"
#ifdef ALSOFT_EAX #if ALSOFT_EAX
#include "context.h" #include "context.h"
#include "al/eax/x_ram.h" #include "al/eax/x_ram.h"
#endif #endif
@ -215,6 +217,10 @@ inline const FuncExport alcFunctions[]{
DECL(alPushDebugGroupEXT), DECL(alPushDebugGroupEXT),
DECL(alPopDebugGroupEXT), DECL(alPopDebugGroupEXT),
DECL(alGetDebugMessageLogEXT), DECL(alGetDebugMessageLogEXT),
DECL(alObjectLabelEXT),
DECL(alGetObjectLabelEXT),
DECL(alGetPointerEXT),
DECL(alGetPointervEXT),
/* Direct Context functions */ /* Direct Context functions */
DECL(alcGetProcAddress2), DECL(alcGetProcAddress2),
@ -365,15 +371,15 @@ inline const FuncExport alcFunctions[]{
DECL(alPushDebugGroupDirectEXT), DECL(alPushDebugGroupDirectEXT),
DECL(alPopDebugGroupDirectEXT), DECL(alPopDebugGroupDirectEXT),
DECL(alGetDebugMessageLogDirectEXT), DECL(alGetDebugMessageLogDirectEXT),
DECL(alObjectLabelEXT),
DECL(alObjectLabelDirectEXT), DECL(alObjectLabelDirectEXT),
DECL(alGetObjectLabelEXT),
DECL(alGetObjectLabelDirectEXT), DECL(alGetObjectLabelDirectEXT),
DECL(alGetPointerDirectEXT),
DECL(alGetPointervDirectEXT),
/* Extra functions */ /* Extra functions */
DECL(alsoft_set_log_callback), DECL(alsoft_set_log_callback),
}; };
#ifdef ALSOFT_EAX #if ALSOFT_EAX
inline const std::array eaxFunctions{ inline const std::array eaxFunctions{
DECL(EAXGet), DECL(EAXGet),
DECL(EAXSet), DECL(EAXSet),
@ -627,6 +633,8 @@ inline const EnumExport alcEnumerations[]{
DECL(AL_FORMAT_51CHN_I32), DECL(AL_FORMAT_51CHN_I32),
DECL(AL_FORMAT_61CHN_I32), DECL(AL_FORMAT_61CHN_I32),
DECL(AL_FORMAT_71CHN_I32), DECL(AL_FORMAT_71CHN_I32),
DECL(AL_FORMAT_BFORMAT2D_I32),
DECL(AL_FORMAT_BFORMAT3D_I32),
DECL(AL_FORMAT_UHJ2CHN_I32_SOFT), DECL(AL_FORMAT_UHJ2CHN_I32_SOFT),
DECL(AL_FORMAT_UHJ3CHN_I32_SOFT), DECL(AL_FORMAT_UHJ3CHN_I32_SOFT),
DECL(AL_FORMAT_UHJ4CHN_I32_SOFT), DECL(AL_FORMAT_UHJ4CHN_I32_SOFT),
@ -903,7 +911,7 @@ inline const EnumExport alcEnumerations[]{
DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT), DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT),
}; };
#ifdef ALSOFT_EAX #if ALSOFT_EAX
inline const std::array eaxEnumerations{ inline const std::array eaxEnumerations{
DECL(AL_EAX_RAM_SIZE), DECL(AL_EAX_RAM_SIZE),
DECL(AL_EAX_RAM_FREE), DECL(AL_EAX_RAM_FREE),
@ -911,7 +919,7 @@ inline const std::array eaxEnumerations{
DECL(AL_STORAGE_HARDWARE), DECL(AL_STORAGE_HARDWARE),
DECL(AL_STORAGE_ACCESSIBLE), DECL(AL_STORAGE_ACCESSIBLE),
}; };
#endif // ALSOFT_EAX #endif
#undef DECL #undef DECL
#endif /* ALC_EXPORT_LIST_H */ #endif /* ALC_EXPORT_LIST_H */

View file

@ -37,11 +37,6 @@ void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffe
#endif #endif
#endif #endif
#ifndef AL_SOFT_bformat_hoa
#define AL_SOFT_bformat_hoa
#define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D
#endif
#ifndef AL_SOFT_convolution_effect #ifndef AL_SOFT_convolution_effect
#define AL_SOFT_convolution_effect #define AL_SOFT_convolution_effect
#define AL_EFFECT_CONVOLUTION_SOFT 0xA000 #define AL_EFFECT_CONVOLUTION_SOFT 0xA000
@ -70,15 +65,18 @@ void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffe
#define AL_FORMAT_71CHN_I32 0x19E5 #define AL_FORMAT_71CHN_I32 0x19E5
#define AL_FORMAT_71CHN_FLOAT32 0x19E6 #define AL_FORMAT_71CHN_FLOAT32 0x19E6
#define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E7 #define AL_FORMAT_BFORMAT2D_I32 0x19E7
#define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19E8 #define AL_FORMAT_BFORMAT3D_I32 0x19E8
#define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19E9
#define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E9
#define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19EA
#define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19EB
#endif #endif
#ifndef AL_SOFT_source_panning #ifndef AL_SOFT_source_panning
#define AL_SOFT_source_panning #define AL_SOFT_source_panning
#define AL_PANNING_ENABLED_SOFT 0x19EA #define AL_PANNING_ENABLED_SOFT 0x19EC
#define AL_PAN_SOFT 0x19EB #define AL_PAN_SOFT 0x19ED
#endif #endif
/* Non-standard exports. Not part of any extension. */ /* Non-standard exports. Not part of any extension. */
@ -91,6 +89,11 @@ void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *us
AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb,
const ALuint *buffers) noexcept; const ALuint *buffers) noexcept;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) noexcept;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) noexcept;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) noexcept;
AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) noexcept;
AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) AL_API_NOEXCEPT; AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) AL_API_NOEXCEPT;
AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT;
ALint64SOFT AL_APIENTRY alGetInteger64DirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT; ALint64SOFT AL_APIENTRY alGetInteger64DirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT;

View file

@ -76,53 +76,77 @@ using namespace std::string_view_literals;
using std::chrono::seconds; using std::chrono::seconds;
using std::chrono::nanoseconds; using std::chrono::nanoseconds;
const char *GetLabelFromChannel(Channel channel) [[nodiscard]]
auto GetLabelFromChannel(Channel channel) -> std::string_view
{ {
switch(channel) switch(channel)
{ {
case FrontLeft: return "front-left"; case FrontLeft: return "front-left"sv;
case FrontRight: return "front-right"; case FrontRight: return "front-right"sv;
case FrontCenter: return "front-center"; case FrontCenter: return "front-center"sv;
case LFE: return "lfe"; case LFE: return "lfe"sv;
case BackLeft: return "back-left"; case BackLeft: return "back-left"sv;
case BackRight: return "back-right"; case BackRight: return "back-right"sv;
case BackCenter: return "back-center"; case BackCenter: return "back-center"sv;
case SideLeft: return "side-left"; case SideLeft: return "side-left"sv;
case SideRight: return "side-right"; case SideRight: return "side-right"sv;
case TopFrontLeft: return "top-front-left"; case TopFrontLeft: return "top-front-left"sv;
case TopFrontCenter: return "top-front-center"; case TopFrontCenter: return "top-front-center"sv;
case TopFrontRight: return "top-front-right"; case TopFrontRight: return "top-front-right"sv;
case TopCenter: return "top-center"; case TopCenter: return "top-center"sv;
case TopBackLeft: return "top-back-left"; case TopBackLeft: return "top-back-left"sv;
case TopBackCenter: return "top-back-center"; case TopBackCenter: return "top-back-center"sv;
case TopBackRight: return "top-back-right"; case TopBackRight: return "top-back-right"sv;
case BottomFrontLeft: return "bottom-front-left"; case BottomFrontLeft: return "bottom-front-left"sv;
case BottomFrontRight: return "bottom-front-right"; case BottomFrontRight: return "bottom-front-right"sv;
case BottomBackLeft: return "bottom-back-left"; case BottomBackLeft: return "bottom-back-left"sv;
case BottomBackRight: return "bottom-back-right"; case BottomBackRight: return "bottom-back-right"sv;
case Aux0: return "Aux0"; case Aux0: return "Aux0"sv;
case Aux1: return "Aux1"; case Aux1: return "Aux1"sv;
case Aux2: return "Aux2"; case Aux2: return "Aux2"sv;
case Aux3: return "Aux3"; case Aux3: return "Aux3"sv;
case Aux4: return "Aux4"; case Aux4: return "Aux4"sv;
case Aux5: return "Aux5"; case Aux5: return "Aux5"sv;
case Aux6: return "Aux6"; case Aux6: return "Aux6"sv;
case Aux7: return "Aux7"; case Aux7: return "Aux7"sv;
case Aux8: return "Aux8"; case Aux8: return "Aux8"sv;
case Aux9: return "Aux9"; case Aux9: return "Aux9"sv;
case Aux10: return "Aux10"; case Aux10: return "Aux10"sv;
case Aux11: return "Aux11"; case Aux11: return "Aux11"sv;
case Aux12: return "Aux12"; case Aux12: return "Aux12"sv;
case Aux13: return "Aux13"; case Aux13: return "Aux13"sv;
case Aux14: return "Aux14"; case Aux14: return "Aux14"sv;
case Aux15: return "Aux15"; case Aux15: return "Aux15"sv;
case MaxChannels: break; case MaxChannels: break;
} }
return "(unknown)"; return "(unknown)"sv;
}
[[nodiscard]]
auto GetLayoutName(DevAmbiLayout layout) noexcept -> std::string_view
{
switch(layout)
{
case DevAmbiLayout::FuMa: return "FuMa"sv;
case DevAmbiLayout::ACN: return "ACN"sv;
}
return "<unknown layout enum>"sv;
}
[[nodiscard]]
auto GetScalingName(DevAmbiScaling scaling) noexcept -> std::string_view
{
switch(scaling)
{
case DevAmbiScaling::FuMa: return "FuMa"sv;
case DevAmbiScaling::SN3D: return "SN3D"sv;
case DevAmbiScaling::N3D: return "N3D"sv;
}
return "<unknown scaling enum>"sv;
} }
@ -140,14 +164,14 @@ std::unique_ptr<FrontStablizer> CreateStablizer(const size_t outchans, const uin
return stablizer; return stablizer;
} }
void AllocChannels(ALCdevice *device, const size_t main_chans, const size_t real_chans) void AllocChannels(al::Device *device, const size_t main_chans, const size_t real_chans)
{ {
TRACE("Channel config, Main: %zu, Real: %zu\n", main_chans, real_chans); TRACE("Channel config, Main: {}, Real: {}", main_chans, real_chans);
/* Allocate extra channels for any post-filter output. */ /* Allocate extra channels for any post-filter output. */
const size_t num_chans{main_chans + real_chans}; const size_t num_chans{main_chans + real_chans};
TRACE("Allocating %zu channels, %zu bytes\n", num_chans, TRACE("Allocating {} channels, {} bytes", num_chans,
num_chans*sizeof(device->MixBuffer[0])); num_chans*sizeof(device->MixBuffer[0]));
device->MixBuffer.resize(num_chans); device->MixBuffer.resize(num_chans);
al::span<FloatBufferLine> buffer{device->MixBuffer}; al::span<FloatBufferLine> buffer{device->MixBuffer};
@ -239,7 +263,8 @@ struct DecoderConfig<DualBand, 0> {
using DecoderView = DecoderConfig<DualBand, 0>; using DecoderView = DecoderConfig<DualBand, 0>;
void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint order, const bool is3d) void InitNearFieldCtrl(al::Device *device, const float ctrl_dist, const uint order,
const bool is3d)
{ {
static const std::array<uint,MaxAmbiOrder+1> chans_per_order2d{{1, 2, 2, 2}}; static const std::array<uint,MaxAmbiOrder+1> chans_per_order2d{{1, 2, 2, 2}};
static const std::array<uint,MaxAmbiOrder+1> chans_per_order3d{{1, 3, 5, 7}}; static const std::array<uint,MaxAmbiOrder+1> chans_per_order3d{{1, 3, 5, 7}};
@ -249,10 +274,10 @@ void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint orde
return; return;
device->AvgSpeakerDist = std::clamp(ctrl_dist, 0.1f, 10.0f); device->AvgSpeakerDist = std::clamp(ctrl_dist, 0.1f, 10.0f);
TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist); TRACE("Using near-field reference distance: {:.2f} meters", device->AvgSpeakerDist);
const float w1{SpeedOfSoundMetersPerSec / const float w1{SpeedOfSoundMetersPerSec /
(device->AvgSpeakerDist * static_cast<float>(device->Frequency))}; (device->AvgSpeakerDist * static_cast<float>(device->mSampleRate))};
device->mNFCtrlFilter.init(w1); device->mNFCtrlFilter.init(w1);
auto iter = std::copy_n(is3d ? chans_per_order3d.begin() : chans_per_order2d.begin(), order+1u, auto iter = std::copy_n(is3d ? chans_per_order3d.begin() : chans_per_order2d.begin(), order+1u,
@ -260,7 +285,7 @@ void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint orde
std::fill(iter, device->NumChannelsPerOrder.end(), 0u); std::fill(iter, device->NumChannelsPerOrder.end(), 0u);
} }
void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels, void InitDistanceComp(al::Device *device, const al::span<const Channel> channels,
const al::span<const float,MaxOutputChannels> dists) const al::span<const float,MaxOutputChannels> dists)
{ {
const float maxdist{std::accumulate(dists.begin(), dists.end(), 0.0f, const float maxdist{std::accumulate(dists.begin(), dists.end(), 0.0f,
@ -269,7 +294,7 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f)) if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f))
return; return;
const auto distSampleScale = static_cast<float>(device->Frequency) / SpeedOfSoundMetersPerSec; const auto distSampleScale = static_cast<float>(device->mSampleRate)/SpeedOfSoundMetersPerSec;
struct DistCoeffs { uint Length{}; float Gain{}; }; struct DistCoeffs { uint Length{}; float Gain{}; };
std::vector<DistCoeffs> ChanDelay; std::vector<DistCoeffs> ChanDelay;
@ -294,7 +319,7 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)}; float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)};
if(delay > float{DistanceComp::MaxDelay-1}) if(delay > float{DistanceComp::MaxDelay-1})
{ {
ERR("Delay for channel %zu (%s) exceeds buffer length (%f > %d)\n", idx, ERR("Delay for channel {} ({}) exceeds buffer length ({:f} > {})", idx,
GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1); GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1);
delay = float{DistanceComp::MaxDelay-1}; delay = float{DistanceComp::MaxDelay-1};
} }
@ -302,7 +327,7 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
ChanDelay.resize(std::max(ChanDelay.size(), idx+1_uz)); ChanDelay.resize(std::max(ChanDelay.size(), idx+1_uz));
ChanDelay[idx].Length = static_cast<uint>(delay); ChanDelay[idx].Length = static_cast<uint>(delay);
ChanDelay[idx].Gain = distance / maxdist; ChanDelay[idx].Gain = distance / maxdist;
TRACE("Channel %s distance comp: %u samples, %f gain\n", GetLabelFromChannel(ch), TRACE("Channel {} distance comp: {} samples, {:f} gain", GetLabelFromChannel(ch),
ChanDelay[idx].Length, ChanDelay[idx].Gain); ChanDelay[idx].Length, ChanDelay[idx].Gain);
/* Round up to the next 4th sample, so each channel buffer starts /* Round up to the next 4th sample, so each channel buffer starts
@ -345,8 +370,8 @@ constexpr auto GetAmbiLayout(DevAmbiLayout layouttype) noexcept
} }
DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, auto MakeDecoderView(al::Device *device, const AmbDecConf *conf,
DecoderConfig<DualBand,MaxOutputChannels> &decoder) DecoderConfig<DualBand,MaxOutputChannels> &decoder) -> DecoderView
{ {
DecoderView ret{}; DecoderView ret{};
@ -441,10 +466,11 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
{ {
int idx{}; int idx{};
char c{}; char c{};
/* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */
if(sscanf(speaker.Name.c_str(), "AUX%d%c", &idx, &c) != 1 || idx < 0 if(sscanf(speaker.Name.c_str(), "AUX%d%c", &idx, &c) != 1 || idx < 0
|| idx >= MaxChannels-Aux0) || idx >= MaxChannels-Aux0)
{ {
ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str()); ERR("AmbDec speaker label \"{}\" not recognized", speaker.Name);
continue; continue;
} }
ch = static_cast<Channel>(Aux0+idx); ch = static_cast<Channel>(Aux0+idx);
@ -649,7 +675,7 @@ constexpr DecoderConfig<DualBand, 14> X7144Config{
}} }}
}; };
void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false, void InitPanning(al::Device *device, const bool hqdec=false, const bool stablize=false,
DecoderView decoder={}) DecoderView decoder={})
{ {
if(!decoder) if(!decoder)
@ -682,10 +708,13 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
avg_dist = *distopt; avg_dist = *distopt;
else if(auto delayopt = device->configValue<float>("decoder", "nfc-ref-delay")) else if(auto delayopt = device->configValue<float>("decoder", "nfc-ref-delay"))
{ {
WARN("nfc-ref-delay is deprecated, use speaker-dist instead\n"); WARN("nfc-ref-delay is deprecated, use speaker-dist instead");
avg_dist = *delayopt * SpeedOfSoundMetersPerSec; avg_dist = *delayopt * SpeedOfSoundMetersPerSec;
} }
TRACE("{}{} order ambisonic output ({} layout, {} scaling)", device->mAmbiOrder,
GetCounterSuffix(device->mAmbiOrder), GetLayoutName(device->mAmbiLayout),
GetScalingName(device->mAmbiScale));
InitNearFieldCtrl(device, avg_dist, device->mAmbiOrder, true); InitNearFieldCtrl(device, avg_dist, device->mAmbiOrder, true);
return; return;
} }
@ -700,7 +729,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
const size_t idx{device->channelIdxByName(decoder.mChannels[i])}; const size_t idx{device->channelIdxByName(decoder.mChannels[i])};
if(idx == InvalidChannelIndex) if(idx == InvalidChannelIndex)
{ {
ERR("Failed to find %s channel in device\n", ERR("Failed to find {} channel in device",
GetLabelFromChannel(decoder.mChannels[i])); GetLabelFromChannel(decoder.mChannels[i]));
continue; continue;
} }
@ -756,22 +785,21 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
} }
if(!hasfc) if(!hasfc)
{ {
stablizer = CreateStablizer(device->channelsFromFmt(), device->Frequency); stablizer = CreateStablizer(device->channelsFromFmt(), device->mSampleRate);
TRACE("Front stablizer enabled\n"); TRACE("Front stablizer enabled");
} }
} }
TRACE("Enabling %s-band %s-order%s ambisonic decoder\n", TRACE("Enabling {}-band {}-order{} ambisonic decoder", !dual_band ? "single" : "dual",
!dual_band ? "single" : "dual",
(decoder.mOrder > 3) ? "fourth" : (decoder.mOrder > 3) ? "fourth" :
(decoder.mOrder > 2) ? "third" : (decoder.mOrder > 2) ? "third" :
(decoder.mOrder > 1) ? "second" : "first", (decoder.mOrder > 1) ? "second" : "first",
decoder.mIs3D ? " periphonic" : ""); decoder.mIs3D ? " periphonic" : "");
device->AmbiDecoder = BFormatDec::Create(ambicount, chancoeffs, chancoeffslf, device->AmbiDecoder = BFormatDec::Create(ambicount, chancoeffs, chancoeffslf,
device->mXOverFreq/static_cast<float>(device->Frequency), std::move(stablizer)); device->mXOverFreq/static_cast<float>(device->mSampleRate), std::move(stablizer));
} }
void InitHrtfPanning(ALCdevice *device) void InitHrtfPanning(al::Device *device)
{ {
static constexpr float Deg180{al::numbers::pi_v<float>}; static constexpr float Deg180{al::numbers::pi_v<float>};
static constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; static constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/};
@ -929,7 +957,7 @@ void InitHrtfPanning(ALCdevice *device)
std::string_view mode{*modeopt}; std::string_view mode{*modeopt};
if(al::case_compare(mode, "basic"sv) == 0) if(al::case_compare(mode, "basic"sv) == 0)
{ {
ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", modeopt->c_str(), "ambi2"); ERR("HRTF mode \"{}\" deprecated, substituting \"{}\"", *modeopt, "ambi2");
mode = "ambi2"; mode = "ambi2";
} }
@ -937,16 +965,16 @@ void InitHrtfPanning(ALCdevice *device)
{ return al::case_compare(mode, entry.name) == 0; }; { return al::case_compare(mode, entry.name) == 0; };
auto iter = std::find_if(hrtf_modes.begin(), hrtf_modes.end(), match_entry); auto iter = std::find_if(hrtf_modes.begin(), hrtf_modes.end(), match_entry);
if(iter == hrtf_modes.end()) if(iter == hrtf_modes.end())
ERR("Unexpected hrtf-mode: %s\n", modeopt->c_str()); ERR("Unexpected hrtf-mode: {}", *modeopt);
else else
{ {
device->mRenderMode = iter->mode; device->mRenderMode = iter->mode;
ambi_order = iter->order; ambi_order = iter->order;
} }
} }
TRACE("%u%s order %sHRTF rendering enabled, using \"%s\"\n", ambi_order, TRACE("{}{} order {}HRTF rendering enabled, using \"{}\"", ambi_order,
GetCounterSuffix(ambi_order), (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", GetCounterSuffix(ambi_order), (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "",
device->mHrtfName.c_str()); device->mHrtfName);
bool perHrirMin{false}; bool perHrirMin{false};
auto AmbiPoints = al::span{AmbiPoints1O}.subspan(0); auto AmbiPoints = al::span{AmbiPoints1O}.subspan(0);
@ -983,7 +1011,7 @@ void InitHrtfPanning(ALCdevice *device)
InitNearFieldCtrl(device, Hrtf->mFields[0].distance, ambi_order, true); InitNearFieldCtrl(device, Hrtf->mFields[0].distance, ambi_order, true);
} }
void InitUhjPanning(ALCdevice *device) void InitUhjPanning(al::Device *device)
{ {
/* UHJ is always 2D first-order. */ /* UHJ is always 2D first-order. */
static constexpr size_t count{Ambi2DChannelsFromOrder(1)}; static constexpr size_t count{Ambi2DChannelsFromOrder(1)};
@ -996,11 +1024,21 @@ void InitUhjPanning(ALCdevice *device)
[](const uint8_t &acn) noexcept -> BFChannelConfig [](const uint8_t &acn) noexcept -> BFChannelConfig
{ return BFChannelConfig{1.0f/AmbiScale::FromUHJ[acn], acn}; }); { return BFChannelConfig{1.0f/AmbiScale::FromUHJ[acn], acn}; });
AllocChannels(device, count, device->channelsFromFmt()); AllocChannels(device, count, device->channelsFromFmt());
/* TODO: Should this default to something else? This is simply a regular
* (first-order) B-Format mixing which just happens to be UHJ-encoded. As I
* understand it, a proper first-order B-Format signal essentially has an
* infinite control distance, which we can't really do. However, from what
* I've read, 2 meters or so should be sufficient as the near-field
* reference becomes inconsequential beyond that.
*/
const auto spkr_dist = ConfigValueFloat({}, "uhj"sv, "distance-ref"sv).value_or(2.0f);
InitNearFieldCtrl(device, spkr_dist, device->mAmbiOrder, !device->m2DMixing);
} }
} // namespace } // namespace
void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncoding> stereomode) void aluInitRenderer(al::Device *device, int hrtf_id, std::optional<StereoEncoding> stereomode)
{ {
/* Hold the HRTF the device last used, in case it's used again. */ /* Hold the HRTF the device last used, in case it's used again. */
HrtfStorePtr old_hrtf{std::move(device->mHrtf)}; HrtfStorePtr old_hrtf{std::move(device->mHrtf)};
@ -1044,25 +1082,25 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
AmbDecConf conf{}; AmbDecConf conf{};
if(auto err = conf.load(config)) if(auto err = conf.load(config))
{ {
ERR("Failed to load layout file %s\n", config); ERR("Failed to load layout file {}", config);
ERR(" %s\n", err->c_str()); ERR(" {}", *err);
return false; return false;
} }
if(conf.Speakers.size() > MaxOutputChannels) if(conf.Speakers.size() > MaxOutputChannels)
{ {
ERR("Unsupported decoder speaker count %zu (max %zu)\n", conf.Speakers.size(), ERR("Unsupported decoder speaker count {} (max {})", conf.Speakers.size(),
MaxOutputChannels); MaxOutputChannels);
return false; return false;
} }
if(conf.ChanMask > Ambi3OrderMask) if(conf.ChanMask > Ambi3OrderMask)
{ {
ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, ERR("Unsupported decoder channel mask {:#x} (max {:#x})", conf.ChanMask,
Ambi3OrderMask); Ambi3OrderMask);
return false; return false;
} }
TRACE("Using %s decoder: \"%s\"\n", DevFmtChannelsString(device->FmtChans), TRACE("Using {} decoder: \"{}\"", DevFmtChannelsString(device->FmtChans),
conf.Description.c_str()); conf.Description);
device->mXOverFreq = std::clamp(conf.XOverFreq, 100.0f, 1000.0f); device->mXOverFreq = std::clamp(conf.XOverFreq, 100.0f, 1000.0f);
decoder_store = std::make_unique<DecoderConfig<DualBand,MaxOutputChannels>>(); decoder_store = std::make_unique<DecoderConfig<DualBand,MaxOutputChannels>>();
@ -1081,7 +1119,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
usingCustom = load_config(decopt->c_str()); usingCustom = load_config(decopt->c_str());
} }
if(!usingCustom && device->FmtChans != DevFmtAmbi3D) if(!usingCustom && device->FmtChans != DevFmtAmbi3D)
TRACE("Using built-in %s decoder\n", DevFmtChannelsString(device->FmtChans)); TRACE("Using built-in {} decoder", DevFmtChannelsString(device->FmtChans));
/* Enable the stablizer only for formats that have front-left, front- /* Enable the stablizer only for formats that have front-left, front-
* right, and front-center outputs. * right, and front-center outputs.
@ -1113,8 +1151,8 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
} }
if(auto *ambidec{device->AmbiDecoder.get()}) if(auto *ambidec{device->AmbiDecoder.get()})
{ {
device->PostProcess = ambidec->hasStablizer() ? &ALCdevice::ProcessAmbiDecStablized device->PostProcess = ambidec->hasStablizer() ? &al::Device::ProcessAmbiDecStablized
: &ALCdevice::ProcessAmbiDec; : &al::Device::ProcessAmbiDec;
} }
return; return;
} }
@ -1132,7 +1170,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
if(hrtf_id >= 0 && static_cast<uint>(hrtf_id) < device->mHrtfList.size()) if(hrtf_id >= 0 && static_cast<uint>(hrtf_id) < device->mHrtfList.size())
{ {
const std::string_view hrtfname{device->mHrtfList[static_cast<uint>(hrtf_id)]}; const std::string_view hrtfname{device->mHrtfList[static_cast<uint>(hrtf_id)]};
if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->mSampleRate)})
{ {
device->mHrtf = std::move(hrtf); device->mHrtf = std::move(hrtf);
device->mHrtfName = hrtfname; device->mHrtfName = hrtfname;
@ -1143,7 +1181,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
{ {
for(const std::string_view hrtfname : device->mHrtfList) for(const std::string_view hrtfname : device->mHrtfList)
{ {
if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->mSampleRate)})
{ {
device->mHrtf = std::move(hrtf); device->mHrtf = std::move(hrtf);
device->mHrtfName = hrtfname; device->mHrtfName = hrtfname;
@ -1165,7 +1203,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
} }
InitHrtfPanning(device); InitHrtfPanning(device);
device->PostProcess = &ALCdevice::ProcessHrtf; device->PostProcess = &al::Device::ProcessHrtf;
device->mHrtfStatus = ALC_HRTF_ENABLED_SOFT; device->mHrtfStatus = ALC_HRTF_ENABLED_SOFT;
return; return;
} }
@ -1174,23 +1212,27 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Uhj) if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Uhj)
{ {
auto ftype = std::string_view{};
switch(UhjEncodeQuality) switch(UhjEncodeQuality)
{ {
case UhjQualityType::IIR: case UhjQualityType::IIR:
device->mUhjEncoder = std::make_unique<UhjEncoderIIR>(); device->mUhjEncoder = std::make_unique<UhjEncoderIIR>();
ftype = "IIR"sv;
break; break;
case UhjQualityType::FIR256: case UhjQualityType::FIR256:
device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLength256>>(); device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLength256>>();
ftype = "FIR-256"sv;
break; break;
case UhjQualityType::FIR512: case UhjQualityType::FIR512:
device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLength512>>(); device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLength512>>();
ftype = "FIR-512"sv;
break; break;
} }
assert(device->mUhjEncoder != nullptr); assert(device->mUhjEncoder != nullptr);
TRACE("UHJ enabled\n"); TRACE("UHJ enabled ({} encoder)", ftype);
InitUhjPanning(device); InitUhjPanning(device);
device->PostProcess = &ALCdevice::ProcessUhj; device->PostProcess = &al::Device::ProcessUhj;
return; return;
} }
@ -1202,19 +1244,19 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncodin
if(*cflevopt > 0 && *cflevopt <= 6) if(*cflevopt > 0 && *cflevopt <= 6)
{ {
auto bs2b = std::make_unique<Bs2b::bs2b>(); auto bs2b = std::make_unique<Bs2b::bs2b>();
bs2b->set_params(*cflevopt, static_cast<int>(device->Frequency)); bs2b->set_params(*cflevopt, static_cast<int>(device->mSampleRate));
device->Bs2b = std::move(bs2b); device->Bs2b = std::move(bs2b);
TRACE("BS2B enabled\n"); TRACE("BS2B enabled");
InitPanning(device); InitPanning(device);
device->PostProcess = &ALCdevice::ProcessBs2b; device->PostProcess = &al::Device::ProcessBs2b;
return; return;
} }
} }
} }
TRACE("Stereo rendering\n"); TRACE("Stereo rendering");
InitPanning(device); InitPanning(device);
device->PostProcess = &ALCdevice::ProcessAmbiDec; device->PostProcess = &al::Device::ProcessAmbiDec;
} }

View file

@ -189,7 +189,11 @@
# between 24 and 48 points, with anti-aliasing) # between 24 and 48 points, with anti-aliasing)
# fast_bsinc24 - same as bsinc24, except without interpolation between down- # fast_bsinc24 - same as bsinc24, except without interpolation between down-
# sampling scales # sampling scales
#resampler = gaussian # bsinc48 - extrapolates samples using a band-limited Sinc filter (48 points,
# with anti-aliasing)
# fast_bsinc48 - same as bsinc48, except without interpolation between down-
# sampling scales
#resampler = spline
## rt-prio: (global) ## rt-prio: (global)
# Sets the real-time priority value for the mixing thread. Not all drivers may # Sets the real-time priority value for the mixing thread. Not all drivers may
@ -587,6 +591,13 @@
# configurations. Very experimental. # configurations. Very experimental.
#spatial-api = false #spatial-api = false
## exclusive-mode:
# Enables Exlusive mode for playback devices. This uses the device directly,
# allowing lower latencies but prevents the device from being used multiple
# times simultaneously. Ignores the periods setting when enabled, as WASAPI
# automatically sets a buffer size based on the period size.
#exclusive-mode = false
## allow-resampler: ## allow-resampler:
# Specifies whether to allow an extra resampler pass on the output. Enabling # Specifies whether to allow an extra resampler pass on the output. Enabling
# this will allow the playback device to be set to a different sample rate # this will allow the playback device to be set to a different sample rate
@ -645,6 +656,11 @@
# Sets whether to enable EAX extensions or not. # Sets whether to enable EAX extensions or not.
#enable = true #enable = true
## trace-commits: (global)
# Sets whether log EAX property commits with trace messages. This can
# significantly increase the amount of log messages for apps that use EAX.
#trace-commits = false
## ##
## Per-game compatibility options (these should only be set in per-game config ## Per-game compatibility options (these should only be set in per-game config
## files, *NOT* system- or user-level!) ## files, *NOT* system- or user-level!)
@ -686,3 +702,15 @@
## reverse-z: (global) ## reverse-z: (global)
# Reverses the local Z (front-back) position of 3D sound sources. # Reverses the local Z (front-back) position of 3D sound sources.
#reverse-z = false #reverse-z = false
## vendor-override:
# Overrides the string returned by alGetString(AL_VENDOR).
#vendor-override =
## version-override:
# Overrides the string returned by alGetString(AL_VERSION).
#version-override =
## renderer-override:
# Overrides the string returned by alGetString(AL_RENDERER).
#renderer-override =

View file

@ -1,4 +1,4 @@
version: 1.23.1.{build} version: 1.24.3.{build}
environment: environment:
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022

View file

@ -80,8 +80,37 @@ macro(find_component _component _pkgconfig _library _header)
${PC_LIB${_component}_LIBRARY_DIRS} ${PC_LIB${_component}_LIBRARY_DIRS}
) )
set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number." FORCE) if(DEFINED ${PC_${_component}_VERSION})
set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS." FORCE) set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING
"The ${_component} version number." FORCE)
elseif(EXISTS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h")
if(EXISTS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version_major.h")
file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version_major.h" majorver
REGEX "^#define[ \t]+LIB${_component}_VERSION_MAJOR[ \t]+[0-9]+$")
else()
file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h" majorver
REGEX "^#define[ \t]+LIB${_component}_VERSION_MAJOR[ \t]+[0-9]+$")
endif()
file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h" minorver
REGEX "^#define[ \t]+LIB${_component}_VERSION_MINOR[ \t]+[0-9]+$")
file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h" microver
REGEX "^#define[ \t]+LIB${_component}_VERSION_MICRO[ \t]+[0-9]+$")
string(REGEX REPLACE "^#define[ \t]+LIB${_component}_VERSION_MAJOR[ \t]+([0-9]+)$" "\\1"
majorver "${majorver}")
string(REGEX REPLACE "^#define[ \t]+LIB${_component}_VERSION_MINOR[ \t]+([0-9]+)$" "\\1"
minorver "${minorver}")
string(REGEX REPLACE "^#define[ \t]+LIB${_component}_VERSION_MICRO[ \t]+([0-9]+)$" "\\1"
microver "${microver}")
set(${_component}_VERSION "${majorver}.${minorver}.${microver}" CACHE STRING
"The ${_component} version number." FORCE)
unset(microver)
unset(minorver)
unset(majorver)
endif()
set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING
"The ${_component} CFLAGS." FORCE)
set_component_found(${_component}) set_component_found(${_component})

View file

@ -43,7 +43,7 @@ find_path(JACK_INCLUDE_DIR NAMES jack/jack.h
DOC "The JACK include directory" DOC "The JACK include directory"
) )
find_library(JACK_LIBRARY NAMES jack find_library(JACK_LIBRARY NAMES jack jack64
DOC "The JACK library" DOC "The JACK library"
) )

View file

@ -1,38 +1,44 @@
#include "alassert.h" #include "alassert.h"
#include <exception>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
namespace {
[[noreturn]]
void throw_error(const std::string &message)
{
throw std::runtime_error{message};
}
} /* namespace */
namespace al { namespace al {
[[noreturn]] [[noreturn]]
void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept
{ {
std::string errstr{filename}; /* Throwing an exception that tries to leave a noexcept function will
* hopefully cause the system to provide info about the caught exception in
* an error dialog. At least on Linux, this results in the process printing
*
* terminate called after throwing an instance of 'std::runtime_error'
* what(): <message here>
*
* before terminating from a SIGABRT. Hopefully Windows and Mac will do the
* appropriate things with the message to alert the user about an abnormal
* termination.
*/
auto errstr = std::string{filename};
errstr += ':'; errstr += ':';
errstr += std::to_string(linenum); errstr += std::to_string(linenum);
errstr += ": "; errstr += ": ";
errstr += funcname; errstr += funcname;
errstr += ": "; errstr += ": ";
errstr += message; errstr += message;
/* Calling std::terminate in a catch block hopefully causes the system to
* provide info about the caught exception in the error dialog. At least on throw_error(errstr);
* Linux, this results in the process printing
*
* terminate called after throwing an instance of 'std::runtime_error'
* what(): <message here>
*
* before terminating from a SIGABRT. Hopefully Windows and Mac will do the
* appropriate things with the message for an abnormal termination.
*/
try {
throw std::runtime_error{errstr};
}
catch(...) {
std::terminate();
}
} }
} /* namespace al */ } /* namespace al */

View file

@ -1,6 +1,7 @@
#ifndef AL_BIT_H #ifndef AL_BIT_H
#define AL_BIT_H #define AL_BIT_H
#include <algorithm>
#include <array> #include <array>
#ifndef __GNUC__ #ifndef __GNUC__
#include <cstdint> #include <cstdint>
@ -25,6 +26,16 @@ To> bit_cast(const From &src) noexcept
return *std::launder(reinterpret_cast<To*>(dst.data())); return *std::launder(reinterpret_cast<To*>(dst.data()));
} }
template<typename T>
std::enable_if_t<std::is_integral_v<T>,
T> byteswap(T value) noexcept
{
static_assert(std::has_unique_object_representations_v<T>);
auto bytes = al::bit_cast<std::array<std::byte,sizeof(T)>>(value);
std::reverse(bytes.begin(), bytes.end());
return al::bit_cast<T>(bytes);
}
#ifdef __BYTE_ORDER__ #ifdef __BYTE_ORDER__
enum class endian { enum class endian {
little = __ORDER_LITTLE_ENDIAN__, little = __ORDER_LITTLE_ENDIAN__,

View file

@ -20,7 +20,7 @@
namespace { namespace {
using ushort = unsigned short; using ushort = unsigned short;
using ushort2 = std::pair<ushort,ushort>; using ushort2 = std::array<ushort,2>;
using complex_d = std::complex<double>; using complex_d = std::complex<double>;
constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept
@ -56,8 +56,8 @@ struct BitReverser {
if(idx < revidx) if(idx < revidx)
{ {
mData[ret_i].first = static_cast<ushort>(idx); mData[ret_i][0] = static_cast<ushort>(idx);
mData[ret_i].second = static_cast<ushort>(revidx); mData[ret_i][1] = static_cast<ushort>(revidx);
++ret_i; ++ret_i;
} }
} }
@ -122,7 +122,7 @@ void complex_fft(const al::span<std::complex<double>> buffer, const double sign)
if(log2_size < gBitReverses.size()) LIKELY if(log2_size < gBitReverses.size()) LIKELY
{ {
for(auto &rev : gBitReverses[log2_size]) for(auto &rev : gBitReverses[log2_size])
std::swap(buffer[rev.first], buffer[rev.second]); std::swap(buffer[rev[0]], buffer[rev[1]]);
/* Iterative form of Danielson-Lanczos lemma */ /* Iterative form of Danielson-Lanczos lemma */
for(std::size_t i{0};i < log2_size;++i) for(std::size_t i{0};i < log2_size;++i)

View file

@ -123,31 +123,74 @@ class out_ptr_t {
static_assert(!std::is_same_v<PT,void*>); static_assert(!std::is_same_v<PT,void*>);
SP &mRes; SP &mRes;
std::variant<PT,void*> mPtr{}; std::variant<PT,void*> mPtr;
public: public:
out_ptr_t(SP &res) : mRes{res} { } explicit out_ptr_t(SP &res) : mRes{res} { }
~out_ptr_t() ~out_ptr_t() { std::visit([this](auto &ptr) { mRes.reset(static_cast<PT>(ptr)); }, mPtr); }
{
auto set_res = [this](auto &ptr) out_ptr_t() = delete;
{ mRes.reset(static_cast<PT>(ptr)); };
std::visit(set_res, mPtr);
}
out_ptr_t(const out_ptr_t&) = delete; out_ptr_t(const out_ptr_t&) = delete;
out_ptr_t& operator=(const out_ptr_t&) = delete; out_ptr_t& operator=(const out_ptr_t&) = delete;
operator PT*() noexcept operator PT*() noexcept /* NOLINT(google-explicit-constructor) */
{ return &std::get<PT>(mPtr); } { return &std::get<PT>(mPtr); }
operator void**() noexcept operator void**() noexcept /* NOLINT(google-explicit-constructor) */
{ return &mPtr.template emplace<void*>(); } { return &mPtr.template emplace<void*>(); }
}; };
template<typename T=void, typename SP, typename ...Args> template<typename T=void, typename SP, typename ...Args>
auto out_ptr(SP &res) auto out_ptr(SP &res, Args&& ...args)
{ {
using ptype = typename SP::element_type*; static_assert(sizeof...(args) == 0);
return out_ptr_t<SP,ptype>{res}; if constexpr(std::is_same_v<T,void>)
{
using ptype = typename SP::element_type*;
return out_ptr_t<SP,ptype,Args...>{res};
}
else
return out_ptr_t<SP,T,Args...>{res};
}
template<typename SP, typename PT, typename ...Args>
class inout_ptr_t {
static_assert(!std::is_same_v<PT,void*>);
SP &mRes;
std::variant<PT,void*> mPtr;
public:
explicit inout_ptr_t(SP &res) : mRes{res}, mPtr{res.get()} { }
~inout_ptr_t()
{
mRes.release();
std::visit([this](auto &ptr) { mRes.reset(static_cast<PT>(ptr)); }, mPtr);
}
inout_ptr_t() = delete;
inout_ptr_t(const inout_ptr_t&) = delete;
inout_ptr_t& operator=(const inout_ptr_t&) = delete;
operator PT*() noexcept /* NOLINT(google-explicit-constructor) */
{ return &std::get<PT>(mPtr); }
operator void**() noexcept /* NOLINT(google-explicit-constructor) */
{ return &mPtr.template emplace<void*>(mRes.get()); }
};
template<typename T=void, typename SP, typename ...Args>
auto inout_ptr(SP &res, Args&& ...args)
{
static_assert(sizeof...(args) == 0);
if constexpr(std::is_same_v<T,void>)
{
using ptype = typename SP::element_type*;
return inout_ptr_t<SP,ptype,Args...>{res};
}
else
return inout_ptr_t<SP,T,Args...>{res};
} }
} // namespace al } // namespace al

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