Merge pull request #1292 from Azaezel/alpha41/openALUpgrade

open al upgrade
This commit is contained in:
Brian Roberts 2024-07-23 14:46:03 -05:00 committed by GitHub
commit 7db28feb67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
289 changed files with 33931 additions and 27466 deletions

View file

@ -108,7 +108,7 @@ mark_as_advanced(SDL_WAYLAND)
mark_as_advanced(SDL_WERROR)
mark_as_advanced(SDL_X11)
mark_as_advanced(SDL_XINPUT)
mark_as_advanced(SDL2_DIR)
add_subdirectory(sdl ${TORQUE_LIB_TARG_DIRECTORY}/sdl2 EXCLUDE_FROM_ALL)
add_subdirectory(nativeFileDialogs ${TORQUE_LIB_TARG_DIRECTORY}/nfd EXCLUDE_FROM_ALL)
@ -199,64 +199,59 @@ add_subdirectory(glad ${TORQUE_LIB_TARG_DIRECTORY}/glad EXCLUDE_FROM_ALL)
if(TORQUE_SFX_OPENAL)
advanced_option(TORQUE_OGGVORBIS "Enable OGG Vorbis" ON)
advanced_option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32})
advanced_option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" ON)
advanced_option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" ON)
advanced_option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" OFF)
advanced_option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" OFF)
advanced_option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON)
mark_as_advanced(ALSOFT_REQUIRE_OBOE)
mark_as_advanced(ALSOFT_REQUIRE_PIPEWIRE)
mark_as_advanced(ALSOFT_REQUIRE_RTKIT)
mark_as_advanced(ALSOFT_REQUIRE_SSE3)
#Hide some unnecessary fields as advanced
mark_as_advanced(ALSOFT_INSTALL_AMBDEC_PRESETS)
mark_as_advanced(ALSOFT_BACKEND_DSOUND)
mark_as_advanced(ALSOFT_BACKEND_MMDEVAPI)
mark_as_advanced(ALSOFT_BACKEND_COREAUDIO)
mark_as_advanced(ALSOFT_BACKEND_DSOUND)
mark_as_advanced(ALSOFT_BACKEND_JACK)
mark_as_advanced(ALSOFT_BACKEND_OBOE)
mark_as_advanced(ALSOFT_BACKEND_OPENSL)
mark_as_advanced(ALSOFT_BACKEND_PIPEWIRE)
mark_as_advanced(ALSOFT_BACKEND_PORTAUDIO)
mark_as_advanced(ALSOFT_BACKEND_PULSEAUDIO)
mark_as_advanced(ALSOFT_BACKEND_SDL2)
mark_as_advanced(ALSOFT_BACKEND_WASAPI)
mark_as_advanced(ALSOFT_BACKEND_WAVE)
mark_as_advanced(ALSOFT_BACKEND_WINMM)
mark_as_advanced(ALSOFT_INSTALL_CONFIG)
mark_as_advanced(ALSOFT_BUILD_ROUTER)
mark_as_advanced(ALSOFT_CPUEXT_NEON)
mark_as_advanced(ALSOFT_CPUEXT_SSE)
mark_as_advanced(ALSOFT_CPUEXT_SSE2)
mark_as_advanced(ALSOFT_CPUEXT_SSE3)
mark_as_advanced(ALSOFT_CPUEXT_SSE4_1)
mark_as_advanced(ALSOFT_DLOPEN)
mark_as_advanced(ALSOFT_EMBED_HRTF_DATA)
mark_as_advanced(ALSOFT_EXAMPLES)
mark_as_advanced(ALSOFT_INSTALL_HRTF_DATA)
mark_as_advanced(ALSOFT_EAX)
advanced_option(ALSOFT_EMBED_HRTF_DATA "Embed the HRTF data files (increases library footprint)" OFF)
advanced_option(ALSOFT_EXAMPLES "Build example programs" OFF)
mark_as_advanced(ALSOFT_INSTALL)
advanced_option(ALSOFT_INSTALL_CONFIG "Install alsoft.conf sample configuration file" OFF)
advanced_option(ALSOFT_INSTALL_HRTF_DATA "Install HRTF data files" OFF)
advanced_option(ALSOFT_INSTALL_AMBDEC_PRESETS "Install AmbDec preset files" OFF)
advanced_option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" OFF)
advanced_option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" OFF)
mark_as_advanced(ALSOFT_UPDATE_BUILD_VERSION)
mark_as_advanced(ALSOFT_NO_CONFIG_UTIL)
mark_as_advanced(ALSOFT_NO_UID_DEFS)
mark_as_advanced(ALSOFT_REQUIRE_ALSA)
mark_as_advanced(ALSOFT_REQUIRE_COREAUDIO)
mark_as_advanced(ALSOFT_REQUIRE_DSOUND)
mark_as_advanced(ALSOFT_REQUIRE_JACK)
mark_as_advanced(ALSOFT_REQUIRE_MMDEVAPI)
mark_as_advanced(ALSOFT_REQUIRE_NEON)
mark_as_advanced(ALSOFT_REQUIRE_OBOE)
mark_as_advanced(ALSOFT_REQUIRE_OPENSL)
mark_as_advanced(ALSOFT_REQUIRE_OSS)
mark_as_advanced(ALSOFT_REQUIRE_PIPEWIRE)
mark_as_advanced(ALSOFT_REQUIRE_PORTAUDIO)
mark_as_advanced(ALSOFT_REQUIRE_PULSEAUDIO)
mark_as_advanced(ALSOFT_REQUIRE_QSA)
mark_as_advanced(ALSOFT_REQUIRE_SNDIO)
mark_as_advanced(ALSOFT_REQUIRE_SOLARIS)
mark_as_advanced(ALSOFT_REQUIRE_SDL2)
mark_as_advanced(ALSOFT_REQUIRE_SSE)
mark_as_advanced(ALSOFT_REQUIRE_SSE2)
mark_as_advanced(ALSOFT_REQUIRE_SSE3)
mark_as_advanced(ALSOFT_REQUIRE_SSE4_1)
mark_as_advanced(ALSOFT_REQUIRE_WASAPI)
mark_as_advanced(ALSOFT_REQUIRE_WINMM)
mark_as_advanced(ALSOFT_SEARCH_INSTALL_DATADIR)
mark_as_advanced(ALSOFT_TESTS)
mark_as_advanced(ALSOFT_UTILS)
mark_as_advanced(ALSOFT_WERROR)
mark_as_advanced(COREAUDIO_FRAMEWORK)
mark_as_advanced(CMAKE_DEBUG_POSTFIX)
mark_as_advanced(FORCE_STATIC_VCRT)
mark_as_advanced(ALSOFT_BACKEND_WASAPI)
mark_as_advanced(ALSOFT_BUILD_ROUTER)
mark_as_advanced(ALSOFT_REQUIRE_SDL2)
mark_as_advanced(ALSOFT_REQUIRE_WASAPI)
mark_as_advanced(ALSOFT_BACKEND_COREAUDIO)
mark_as_advanced(ALSOFT_OSX_FRAMEWORK)
mark_as_advanced(ALSOFT_STATIC_LIBGCC)
mark_as_advanced(ALSOFT_STATIC_STDCXX)
mark_as_advanced(AUDIOTOOLBOX_LIBRARY)
mark_as_advanced(AUDIOUNIT_INCLUDE_DIR)
endif()
advanced_option(INSTALL_DOCS "" OFF)
@ -273,9 +268,33 @@ add_subdirectory(flac ${TORQUE_LIB_TARG_DIRECTORY}/flac EXCLUDE_FROM_ALL)
advanced_option(INSTALL_DOCS "" OFF)
advanced_option(OPUS_INSTALL_PKG_CONFIG_MODULE "" OFF)
advanced_option(OPUS_INSTALL_CMAKE_CONFIG_MODULE "" OFF)
mark_as_advanced(OPUS_ASSERTIONS)
mark_as_advanced(OPUS_BUILD_PROGRAMS)
mark_as_advanced(OPUS_BUILD_SHARED_LIBRARY)
mark_as_advanced(OPUS_BUILD_TESTING)
mark_as_advanced(OPUS_CHECK_ASM)
mark_as_advanced(OPUS_CUSTOM_MODES)
mark_as_advanced(OPUS_DISABLE_INTRINSICS)
mark_as_advanced(OPUS_ENABLE_FLOAT_API)
mark_as_advanced(OPUS_FIXED_POINT)
mark_as_advanced(OPUS_FLOAT_APPROX)
mark_as_advanced(OPUS_FUZZING)
mark_as_advanced(OPUS_HARDENING)
mark_as_advanced(OPUS_STACK_PROTECTOR)
mark_as_advanced(OPUS_USE_ALLOCA)
mark_as_advanced(OPUS_X86_MAY_HAVE_AVX)
mark_as_advanced(OPUS_X86_MAY_HAVE_SSE)
mark_as_advanced(OPUS_X86_MAY_HAVE_SSE2)
mark_as_advanced(OPUS_X86_MAY_HAVE_SSE4_1)
mark_as_advanced(OPUS_X86_PRESUME_AVX)
mark_as_advanced(OPUS_X86_PRESUME_SSE)
mark_as_advanced(OPUS_X86_PRESUME_SSE2)
mark_as_advanced(OPUS_X86_PRESUME_SSE4_1)
add_subdirectory(opus ${TORQUE_LIB_TARG_DIRECTORY}/opus EXCLUDE_FROM_ALL)
add_subdirectory(libtheora ${TORQUE_LIB_TARG_DIRECTORY}/libtheora EXCLUDE_FROM_ALL)
mark_as_advanced(PULSEAUDIO_INCLUDE_DIR)
mark_as_advanced(PULSEAUDIO_LIBRARY)
advanced_option(BUILD_PROGRAMS "" OFF)
advanced_option(BUILD_EXAMPLES "" OFF)
advanced_option(ENABLE_CPACK "" OFF)
@ -288,4 +307,29 @@ add_subdirectory(libsndfile ${TORQUE_LIB_TARG_DIRECTORY}/libsndfile EXCLUDE_FROM
if(TORQUE_TESTING)
add_subdirectory(gtest ${TORQUE_LIB_TARG_DIRECTORY}/gtest EXCLUDE_FROM_ALL)
endif()
#endif()
#endif()
#misc randoms
mark_as_advanced(WINDRES)
mark_as_advanced(AUDIOUNIT_INCLUDE_DIR)
mark_as_advanced(CCACHE_BINARY)
mark_as_advanced(COREAUDIO_FRAMEWORK)
mark_as_advanced(FORCE_STATIC_VCRT)
mark_as_advanced(HAVE_GIT)
mark_as_advanced(LIBRARY_SUFFIX)
mark_as_advanced(USE_STATIC_CRT)
mark_as_advanced(BUILD_CXXLIBS)
mark_as_advanced(BUILD_DOCS)
mark_as_advanced(BUILD_REGTEST)
mark_as_advanced(BUILD_UTILS)
mark_as_advanced(ENABLE_64_BIT_WORDS)
mark_as_advanced(ENABLE_BOW_DOCS)
mark_as_advanced(ENABLE_EXTERNAL_LIBS)
mark_as_advanced(WITH_ASM)
mark_as_advanced(WITH_AVX)
mark_as_advanced(WITH_FORTIFY_SOURCE)
mark_as_advanced(WITH_OGG)
mark_as_advanced(WITH_STACK_PROTECTOR)

View file

@ -1,6 +1,6 @@
name: CI
on: [push]
on: [push, pull_request]
jobs:
build:
@ -14,6 +14,7 @@ jobs:
name: "Win32-Release",
os: windows-latest,
cmake_opts: "-A Win32 \
-DALSOFT_TESTS=ON \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \
@ -24,6 +25,7 @@ jobs:
name: "Win32-Debug",
os: windows-latest,
cmake_opts: "-A Win32 \
-DALSOFT_TESTS=ON \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \
@ -34,6 +36,7 @@ jobs:
name: "Win64-Release",
os: windows-latest,
cmake_opts: "-A x64 \
-DALSOFT_TESTS=ON \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \
@ -44,16 +47,42 @@ jobs:
name: "Win64-Debug",
os: windows-latest,
cmake_opts: "-A x64 \
-DALSOFT_TESTS=ON \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \
-DALSOFT_REQUIRE_WASAPI=ON",
build_type: "Debug"
}
- {
name: "Win64-UWP",
os: windows-latest,
cmake_opts: "-A x64 \
-DALSOFT_TESTS=OFF \
-DCMAKE_SYSTEM_NAME=WindowsStore \
\"-DCMAKE_SYSTEM_VERSION=10.0\" \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WASAPI=ON",
build_type: "Release"
}
- {
name: "macOS-Release",
os: macos-latest,
cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON",
cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON \
-DALSOFT_TESTS=ON",
build_type: "Release"
}
- {
name: "iOS-Release",
os: macos-latest,
cmake_opts: "-GXcode \
-DCMAKE_SYSTEM_NAME=iOS \
-DALSOFT_REQUIRE_COREAUDIO=ON \
-DALSOFT_UTILS=OFF \
-DALSOFT_EXAMPLES=OFF \
-DALSOFT_TESTS=OFF \
-DALSOFT_INSTALL=OFF \
\"-DCMAKE_OSX_ARCHITECTURES=arm64\"",
build_type: "Release"
}
- {
@ -65,7 +94,8 @@ jobs:
-DALSOFT_REQUIRE_PORTAUDIO=ON \
-DALSOFT_REQUIRE_PULSEAUDIO=ON \
-DALSOFT_REQUIRE_JACK=ON \
-DALSOFT_REQUIRE_PIPEWIRE=ON",
-DALSOFT_REQUIRE_PIPEWIRE=ON \
-DALSOFT_TESTS=ON",
deps_cmdline: "sudo apt update && sudo apt-get install -qq \
libpulse-dev \
portaudio19-dev \
@ -78,7 +108,7 @@ jobs:
}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- name: Install Dependencies
shell: bash
@ -97,6 +127,12 @@ jobs:
run: |
cmake --build build --config ${{matrix.config.build_type}}
- name: Test
shell: bash
run: |
cd build
ctest
- name: Create Archive
if: ${{ matrix.config.os == 'windows-latest' }}
shell: bash
@ -109,7 +145,7 @@ jobs:
- name: Upload Archive
# Upload package as an artifact of this workflow.
uses: actions/upload-artifact@v3.1.1
uses: actions/upload-artifact@v3.1.2
if: ${{ matrix.config.os == 'windows-latest' }}
with:
name: soft_oal-${{matrix.config.name}}

View file

@ -55,7 +55,7 @@ jobs:
copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll"
- name: Upload makemhr artifact
uses: actions/upload-artifact@v3.1.1
uses: actions/upload-artifact@v3.1.2
with:
name: makemhr
path: "Artifacts/"

View file

@ -1,6 +1,7 @@
build*/
winbuild
win64build
.vs/
## kdevelop
*.kdev4

View file

@ -1,6 +1,7 @@
# CMake build file list for OpenAL
cmake_minimum_required(VERSION 3.0.2)
cmake_minimum_required(VERSION 3.13)
enable_testing()
if(APPLE)
# The workaround for try_compile failing with code signing
@ -28,11 +29,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
FORCE)
endif()
endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
set(ALSOFT_UWP TRUE)
endif()
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
if(COMMAND CMAKE_POLICY)
cmake_policy(SET CMP0003 NEW)
cmake_policy(SET CMP0005 NEW)
@ -76,8 +76,8 @@ if(NOT CMAKE_DEBUG_POSTFIX)
endif()
set(DEFAULT_TARGET_PROPS
# Require C++14.
CXX_STANDARD 14
# Require C++17.
CXX_STANDARD 17
CXX_STANDARD_REQUIRED TRUE
# Prefer C11, but support C99 and earlier when possible.
C_STANDARD 11)
@ -109,6 +109,7 @@ option(ALSOFT_UTILS "Build utility programs" ON)
option(ALSOFT_NO_CONFIG_UTIL "Disable building the alsoft-config utility" OFF)
option(ALSOFT_EXAMPLES "Build example programs" ON)
option(ALSOFT_TESTS "Build test programs" OFF)
option(ALSOFT_INSTALL "Install main library" ON)
option(ALSOFT_INSTALL_CONFIG "Install alsoft.conf sample configuration file" ON)
@ -148,6 +149,8 @@ set(CPP_DEFS ) # C pre-processor, not C++
set(INC_PATHS )
set(C_FLAGS )
set(LINKER_FLAGS )
set(LINKER_FLAGS_DEBUG )
set(LINKER_FLAGS_RELEASE )
set(EXTRA_LIBS )
if(WIN32)
@ -165,7 +168,7 @@ elseif(APPLE)
endif()
# QNX's gcc do not uses /usr/include and /usr/lib pathes by default
# QNX's gcc do not uses /usr/include and /usr/lib paths by default
if("${CMAKE_C_PLATFORM_ID}" STREQUAL "QNX")
set(INC_PATHS ${INC_PATHS} /usr/include)
set(LINKER_FLAGS ${LINKER_FLAGS} -L/usr/lib)
@ -186,29 +189,18 @@ set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0)
set(EXPORT_DECL "")
if(NOT WIN32)
# Check if _POSIX_C_SOURCE and _XOPEN_SOURCE needs to be set for POSIX functions
check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_DEFAULT)
if(NOT HAVE_POSIX_MEMALIGN_DEFAULT)
set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600")
check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_POSIX)
if(NOT HAVE_POSIX_MEMALIGN_POSIX)
set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS})
else()
set(CPP_DEFS ${CPP_DEFS} _POSIX_C_SOURCE=200112L _XOPEN_SOURCE=600)
endif()
endif()
unset(OLD_REQUIRED_FLAGS)
endif()
# C99 has restrict, but C++ does not, so we can only utilize __restrict.
check_cxx_source_compiles("int *__restrict foo;
int main() { return 0; }" HAVE___RESTRICT)
if(HAVE___RESTRICT)
set(CPP_DEFS ${CPP_DEFS} RESTRICT=__restrict)
else()
set(CPP_DEFS ${CPP_DEFS} "RESTRICT=")
# Some systems erroneously require the __STDC_FORMAT_MACROS macro to be defined
# to get the fixed-width integer type formatter macros.
check_cxx_source_compiles("#include <cinttypes>
#include <cstdio>
int main()
{
int64_t i64{};
std::printf(\"%\" PRId64, i64);
}"
HAVE_STDC_FORMAT_MACROS)
if(NOT HAVE_STDC_FORMAT_MACROS)
set(CPP_DEFS ${CPP_DEFS} __STDC_FORMAT_MACROS)
endif()
# Some systems may need libatomic for atomic functions to work
@ -278,6 +270,11 @@ else()
endif()
endif()
check_cxx_compiler_flag(-Wno-interference-size HAVE_WNO_INTERFERENCE_SIZE)
if(HAVE_WNO_INTERFERENCE_SIZE)
set(C_FLAGS ${C_FLAGS} $<$<COMPILE_LANGUAGE:CXX>:-Wno-interference-size>)
endif()
if(ALSOFT_WERROR)
set(C_FLAGS ${C_FLAGS} -Werror)
endif()
@ -312,7 +309,7 @@ else()
option(ALSOFT_STATIC_STDCXX "Static link libstdc++" OFF)
if(ALSOFT_STATIC_STDCXX)
set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lstdc++,--pop-state")
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-static-libstdc++")
check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBSTDCXX_SWITCH)
set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES})
unset(OLD_REQUIRED_LIBRARIES)
@ -320,14 +317,14 @@ else()
if(NOT HAVE_STATIC_LIBSTDCXX_SWITCH)
message(FATAL_ERROR "Cannot static link libstdc++")
endif()
set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lstdc++,--pop-state")
set(LINKER_FLAGS ${LINKER_FLAGS} "-static-libstdc++")
endif()
if(WIN32)
option(ALSOFT_STATIC_WINPTHREAD "Static link libwinpthread" OFF)
if(ALSOFT_STATIC_WINPTHREAD)
set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lwinpthread,--pop-state")
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lstdc++,-lwinpthread,--pop-state")
check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBWINPTHREAD_SWITCH)
set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES})
unset(OLD_REQUIRED_LIBRARIES)
@ -335,38 +332,38 @@ else()
if(NOT HAVE_STATIC_LIBWINPTHREAD_SWITCH)
message(FATAL_ERROR "Cannot static link libwinpthread")
endif()
set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lwinpthread,--pop-state")
set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lstdc++,-lwinpthread,--pop-state")
endif()
endif()
endif()
# Set visibility/export options if available
if(WIN32)
if(NOT LIBTYPE STREQUAL "STATIC")
if(NOT LIBTYPE STREQUAL "STATIC")
if(WIN32)
set(EXPORT_DECL "__declspec(dllexport)")
endif()
else()
set(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
# Yes GCC, really don't accept visibility modes you don't support
set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Wattributes -Werror")
check_c_source_compiles("int foo() __attribute__((visibility(\"protected\")));
int main() {return 0;}" HAVE_GCC_PROTECTED_VISIBILITY)
if(HAVE_GCC_PROTECTED_VISIBILITY)
if(NOT LIBTYPE STREQUAL "STATIC")
set(EXPORT_DECL "__attribute__((visibility(\"protected\")))")
endif()
else()
check_c_source_compiles("int foo() __attribute__((visibility(\"default\")));
int main() {return 0;}" HAVE_GCC_DEFAULT_VISIBILITY)
if(HAVE_GCC_DEFAULT_VISIBILITY)
if(NOT LIBTYPE STREQUAL "STATIC")
set(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
# Yes GCC, really don't accept visibility modes you don't support
set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Wattributes -Werror")
check_c_source_compiles("int foo() __attribute__((visibility(\"protected\")));
int main() {return 0;}" HAVE_GCC_PROTECTED_VISIBILITY)
if(HAVE_GCC_PROTECTED_VISIBILITY)
set(EXPORT_DECL "__attribute__((visibility(\"protected\")))")
else()
check_c_source_compiles("int foo() __attribute__((visibility(\"default\")));
int main() {return 0;}" HAVE_GCC_DEFAULT_VISIBILITY)
if(HAVE_GCC_DEFAULT_VISIBILITY)
set(EXPORT_DECL "__attribute__((visibility(\"default\")))")
endif()
endif()
endif()
if(HAVE_GCC_PROTECTED_VISIBILITY OR HAVE_GCC_DEFAULT_VISIBILITY)
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
endif()
set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}")
endif()
endif()
@ -403,7 +400,7 @@ if(ALSOFT_CPUEXT_SSE AND HAVE_XMMINTRIN_H)
set(HAVE_SSE 1)
endif()
if(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE)
message(FATAL_ERROR "Failed to enabled required SSE CPU extensions")
message(FATAL_ERROR "Failed to enable required SSE CPU extensions")
endif()
option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON)
@ -448,10 +445,11 @@ if(ALSOFT_CPUEXT_NEON AND HAVE_ARM_NEON_H)
endif()
endif()
if(ALSOFT_REQUIRE_NEON AND NOT HAVE_NEON)
message(FATAL_ERROR "Failed to enabled required ARM NEON CPU extensions")
message(FATAL_ERROR "Failed to enable required ARM NEON CPU extensions")
endif()
set(ALSOFT_FORCE_ALIGN )
set(SSE_FLAGS )
set(FPMATH_SET "0")
if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2)
@ -476,6 +474,7 @@ if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2)
# OSs don't guarantee this on 32-bit, so externally-callable
# functions need to ensure an aligned stack.
set(EXPORT_DECL "${EXPORT_DECL}__attribute__((force_align_arg_pointer))")
set(ALSOFT_FORCE_ALIGN "__attribute__((force_align_arg_pointer))")
endif()
endif()
endif()
@ -493,13 +492,9 @@ if(HAVE_SSE2)
endif()
check_include_file(malloc.h HAVE_MALLOC_H)
check_include_file(cpuid.h HAVE_CPUID_H)
check_include_file(intrin.h HAVE_INTRIN_H)
check_include_file(guiddef.h HAVE_GUIDDEF_H)
if(NOT HAVE_GUIDDEF_H)
check_include_file(initguid.h HAVE_INITGUID_H)
endif()
# Some systems need libm for some math functions to work
set(MATH_LIB )
@ -517,7 +512,7 @@ if(HAVE_LIBRT)
set(RT_LIB rt)
endif()
# Check for the dlopen API (for dynamicly loading backend libs)
# Check for the dlopen API (for dynamically loading backend libs)
if(ALSOFT_DLOPEN)
check_include_file(dlfcn.h HAVE_DLFCN_H)
check_library_exists(dl dlopen "" HAVE_LIBDL)
@ -546,8 +541,6 @@ if(HAVE_INTRIN_H)
}" HAVE_CPUID_INTRINSIC)
endif()
check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN)
check_symbol_exists(_aligned_malloc malloc.h HAVE__ALIGNED_MALLOC)
check_symbol_exists(proc_pidpath libproc.h HAVE_PROC_PIDPATH)
if(NOT WIN32)
@ -582,34 +575,36 @@ if(NOT WIN32)
endif()
endif()
check_symbol_exists(getopt unistd.h HAVE_GETOPT)
# Common sources used by both the OpenAL implementation library, the OpenAL
# router, and certain tools and examples.
set(COMMON_OBJS
common/alassert.cpp
common/alassert.h
common/albit.h
common/albyte.h
common/alcomplex.cpp
common/alcomplex.h
common/aldeque.h
common/alfstream.cpp
common/alfstream.h
common/almalloc.cpp
common/almalloc.h
common/alnumbers.h
common/alnumeric.h
common/aloptional.h
common/alsem.cpp
common/alsem.h
common/alspan.h
common/alstring.cpp
common/alstring.h
common/althrd_setname.cpp
common/althrd_setname.h
common/althreads.h
common/altraits.h
common/atomic.h
common/comptr.h
common/dynload.cpp
common/dynload.h
common/flexarray.h
common/intrusive_ptr.h
common/opthelpers.h
common/pffft.cpp
common/pffft.h
common/phase_shifter.h
common/polyphase_resampler.cpp
common/polyphase_resampler.h
@ -618,8 +613,6 @@ set(COMMON_OBJS
common/ringbuffer.h
common/strutils.cpp
common/strutils.h
common/threads.cpp
common/threads.h
common/vecmat.h
common/vector.h)
@ -680,6 +673,8 @@ set(CORE_OBJS
core/mixer.cpp
core/mixer.h
core/resampler_limits.h
core/storage_formats.cpp
core/storage_formats.h
core/uhjfilter.cpp
core/uhjfilter.h
core/uiddefs.cpp
@ -726,7 +721,7 @@ if(NOT WIN32)
endif()
endif()
if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT)
message(FATAL_ERROR "Failed to enabled required RTKit support")
message(FATAL_ERROR "Failed to enable required RTKit support")
endif()
# Default mixers, always available
@ -742,6 +737,9 @@ set(OPENAL_OBJS
al/auxeffectslot.h
al/buffer.cpp
al/buffer.h
al/debug.cpp
al/debug.h
al/direct_defs.h
al/effect.cpp
al/effect.h
al/effects/autowah.cpp
@ -761,6 +759,7 @@ set(OPENAL_OBJS
al/effects/reverb.cpp
al/effects/vmorpher.cpp
al/error.cpp
al/error.h
al/event.cpp
al/event.h
al/extension.cpp
@ -798,6 +797,9 @@ set(ALC_OBJS
alc/effects/pshifter.cpp
alc/effects/reverb.cpp
alc/effects/vmorpher.cpp
alc/events.cpp
alc/events.h
alc/export_list.h
alc/inprogext.h
alc/panning.cpp)
@ -815,7 +817,6 @@ if(ALSOFT_EAX)
al/eax/fx_slot_index.h
al/eax/fx_slots.cpp
al/eax/fx_slots.h
al/eax/globals.cpp
al/eax/globals.h
al/eax/utils.cpp
al/eax/utils.h
@ -899,7 +900,7 @@ if(ALSOFT_BACKEND_PIPEWIRE AND PkgConfig_FOUND)
endif()
endif()
if(ALSOFT_REQUIRE_PIPEWIRE AND NOT HAVE_PIPEWIRE)
message(FATAL_ERROR "Failed to enabled required PipeWire backend")
message(FATAL_ERROR "Failed to enable required PipeWire backend")
endif()
# Check PulseAudio backend
@ -916,7 +917,7 @@ if(ALSOFT_BACKEND_PULSEAUDIO)
endif()
endif()
if(ALSOFT_REQUIRE_PULSEAUDIO AND NOT HAVE_PULSEAUDIO)
message(FATAL_ERROR "Failed to enabled required PulseAudio backend")
message(FATAL_ERROR "Failed to enable required PulseAudio backend")
endif()
if(NOT WIN32)
@ -963,66 +964,72 @@ if(NOT WIN32)
endif()
endif()
# Check SndIO backend
option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON)
# Check SndIO backend (disabled by default on non-BSDs)
if(BSD)
option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON)
else()
option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" OFF)
endif()
option(ALSOFT_REQUIRE_SNDIO "Require SndIO backend" OFF)
if(ALSOFT_BACKEND_SNDIO)
find_package(SoundIO)
if(SOUNDIO_FOUND)
find_package(SndIO)
if(SNDIO_FOUND)
set(HAVE_SNDIO 1)
set(BACKENDS "${BACKENDS} SndIO (linked),")
set(ALC_OBJS ${ALC_OBJS} alc/backends/sndio.cpp alc/backends/sndio.h)
set(EXTRA_LIBS ${SOUNDIO_LIBRARIES} ${EXTRA_LIBS})
set(INC_PATHS ${INC_PATHS} ${SOUNDIO_INCLUDE_DIRS})
set(EXTRA_LIBS ${SNDIO_LIBRARIES} ${EXTRA_LIBS})
set(INC_PATHS ${INC_PATHS} ${SNDIO_INCLUDE_DIRS})
endif()
endif()
endif()
if(ALSOFT_REQUIRE_ALSA AND NOT HAVE_ALSA)
message(FATAL_ERROR "Failed to enabled required ALSA backend")
message(FATAL_ERROR "Failed to enable required ALSA backend")
endif()
if(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS)
message(FATAL_ERROR "Failed to enabled required OSS backend")
message(FATAL_ERROR "Failed to enable required OSS backend")
endif()
if(ALSOFT_REQUIRE_SOLARIS AND NOT HAVE_SOLARIS)
message(FATAL_ERROR "Failed to enabled required Solaris backend")
message(FATAL_ERROR "Failed to enable required Solaris backend")
endif()
if(ALSOFT_REQUIRE_SNDIO AND NOT HAVE_SNDIO)
message(FATAL_ERROR "Failed to enabled required SndIO backend")
message(FATAL_ERROR "Failed to enable required SndIO backend")
endif()
# Check Windows-only backends
if(WIN32)
# Check MMSystem backend
option(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON)
option(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF)
if(ALSOFT_BACKEND_WINMM)
set(HAVE_WINMM 1)
set(BACKENDS "${BACKENDS} WinMM,")
set(ALC_OBJS ${ALC_OBJS} alc/backends/winmm.cpp alc/backends/winmm.h)
# There doesn't seem to be good way to search for winmm.lib for MSVC.
# find_library doesn't find it without being told to look in a specific
# place in the WindowsSDK, but it links anyway. If there ends up being
# Windows targets without this, another means to detect it is needed.
set(EXTRA_LIBS winmm ${EXTRA_LIBS})
endif()
# Check DSound backend
option(ALSOFT_BACKEND_DSOUND "Enable DirectSound backend" ON)
option(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF)
if(ALSOFT_BACKEND_DSOUND)
check_include_file(dsound.h HAVE_DSOUND_H)
if(DXSDK_DIR)
find_path(DSOUND_INCLUDE_DIR NAMES "dsound.h"
PATHS "${DXSDK_DIR}" PATH_SUFFIXES include
DOC "The DirectSound include directory")
if (NOT ALSOFT_UWP)
# Check MMSystem backend
option(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON)
option(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF)
if(ALSOFT_BACKEND_WINMM)
set(HAVE_WINMM 1)
set(BACKENDS "${BACKENDS} WinMM,")
set(ALC_OBJS ${ALC_OBJS} alc/backends/winmm.cpp alc/backends/winmm.h)
# There doesn't seem to be good way to search for winmm.lib for MSVC.
# find_library doesn't find it without being told to look in a specific
# place in the WindowsSDK, but it links anyway. If there ends up being
# Windows targets without this, another means to detect it is needed.
set(EXTRA_LIBS winmm ${EXTRA_LIBS})
endif()
if(HAVE_DSOUND_H OR DSOUND_INCLUDE_DIR)
set(HAVE_DSOUND 1)
set(BACKENDS "${BACKENDS} DirectSound,")
set(ALC_OBJS ${ALC_OBJS} alc/backends/dsound.cpp alc/backends/dsound.h)
if(NOT HAVE_DSOUND_H)
set(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIR})
# Check DSound backend
option(ALSOFT_BACKEND_DSOUND "Enable DirectSound backend" ON)
option(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF)
if(ALSOFT_BACKEND_DSOUND)
check_include_file(dsound.h HAVE_DSOUND_H)
if(DXSDK_DIR)
find_path(DSOUND_INCLUDE_DIR NAMES "dsound.h"
PATHS "${DXSDK_DIR}" PATH_SUFFIXES include
DOC "The DirectSound include directory")
endif()
if(HAVE_DSOUND_H OR DSOUND_INCLUDE_DIR)
set(HAVE_DSOUND 1)
set(BACKENDS "${BACKENDS} DirectSound,")
set(ALC_OBJS ${ALC_OBJS} alc/backends/dsound.cpp alc/backends/dsound.h)
if(NOT HAVE_DSOUND_H)
set(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIR})
endif()
endif()
endif()
endif()
@ -1040,13 +1047,13 @@ if(WIN32)
endif()
endif()
if(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM)
message(FATAL_ERROR "Failed to enabled required WinMM backend")
message(FATAL_ERROR "Failed to enable required WinMM backend")
endif()
if(ALSOFT_REQUIRE_DSOUND AND NOT HAVE_DSOUND)
message(FATAL_ERROR "Failed to enabled required DSound backend")
message(FATAL_ERROR "Failed to enable required DSound backend")
endif()
if(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI)
message(FATAL_ERROR "Failed to enabled required WASAPI backend")
message(FATAL_ERROR "Failed to enable required WASAPI backend")
endif()
# Check JACK backend
@ -1063,7 +1070,7 @@ if(ALSOFT_BACKEND_JACK)
endif()
endif()
if(ALSOFT_REQUIRE_JACK AND NOT HAVE_JACK)
message(FATAL_ERROR "Failed to enabled required JACK backend")
message(FATAL_ERROR "Failed to enable required JACK backend")
endif()
# Check CoreAudio backend
@ -1098,7 +1105,7 @@ if(ALSOFT_BACKEND_COREAUDIO)
endif()
endif()
if(ALSOFT_REQUIRE_COREAUDIO AND NOT HAVE_COREAUDIO)
message(FATAL_ERROR "Failed to enabled required CoreAudio backend")
message(FATAL_ERROR "Failed to enable required CoreAudio backend")
endif()
# Check for Oboe (Android) backend
@ -1130,7 +1137,7 @@ if(ALSOFT_BACKEND_OBOE)
endif()
endif()
if(ALSOFT_REQUIRE_OBOE AND NOT HAVE_OBOE)
message(FATAL_ERROR "Failed to enabled required Oboe backend")
message(FATAL_ERROR "Failed to enable required Oboe backend")
endif()
# Check for OpenSL (Android) backend
@ -1142,11 +1149,12 @@ if(ALSOFT_BACKEND_OPENSL)
set(HAVE_OPENSL 1)
set(ALC_OBJS ${ALC_OBJS} alc/backends/opensl.cpp alc/backends/opensl.h)
set(BACKENDS "${BACKENDS} OpenSL,")
set(EXTRA_LIBS "OpenSL::OpenSLES" ${EXTRA_LIBS})
set(EXTRA_LIBS ${OPENSL_LIBRARIES} ${EXTRA_LIBS})
set(INC_PATHS ${INC_PATHS} ${OPENSL_INCLUDE_DIRS})
endif()
endif()
if(ALSOFT_REQUIRE_OPENSL AND NOT HAVE_OPENSL)
message(FATAL_ERROR "Failed to enabled required OpenSL backend")
message(FATAL_ERROR "Failed to enable required OpenSL backend")
endif()
# Check PortAudio backend
@ -1163,7 +1171,7 @@ if(ALSOFT_BACKEND_PORTAUDIO)
endif()
endif()
if(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO)
message(FATAL_ERROR "Failed to enabled required PortAudio backend")
message(FATAL_ERROR "Failed to enable required PortAudio backend")
endif()
# Check for SDL2 backend
@ -1181,7 +1189,7 @@ if(ALSOFT_BACKEND_SDL2)
endif()
endif()
if(ALSOFT_REQUIRE_SDL2 AND NOT SDL2_FOUND)
message(FATAL_ERROR "Failed to enabled required SDL2 backend")
message(FATAL_ERROR "Failed to enable required SDL2 backend")
endif()
# Optionally enable the Wave Writer backend
@ -1309,11 +1317,12 @@ configure_file(
@ONLY)
add_library(common STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS})
target_include_directories(common PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/include)
target_compile_definitions(common PRIVATE ${CPP_DEFS})
target_compile_options(common PRIVATE ${C_FLAGS})
set_target_properties(common PROPERTIES ${DEFAULT_TARGET_PROPS} POSITION_INDEPENDENT_CODE TRUE)
add_library(alcommon STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS})
target_include_directories(alcommon PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/include
PUBLIC ${OpenAL_SOURCE_DIR}/common)
target_compile_definitions(alcommon PRIVATE ${CPP_DEFS})
target_compile_options(alcommon PRIVATE ${C_FLAGS})
set_target_properties(alcommon PROPERTIES ${DEFAULT_TARGET_PROPS} POSITION_INDEPENDENT_CODE TRUE)
unset(HAS_ROUTER)
@ -1348,7 +1357,7 @@ else()
PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}"
"AL_API=${EXPORT_DECL}" ${CPP_DEFS})
target_compile_options(OpenAL PRIVATE ${C_FLAGS})
target_link_libraries(OpenAL PRIVATE common ${LINKER_FLAGS})
target_link_libraries(OpenAL PRIVATE alcommon ${LINKER_FLAGS})
target_include_directories(OpenAL
PUBLIC
$<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
@ -1379,13 +1388,31 @@ else()
if(WIN32)
set_target_properties(${IMPL_TARGET} PROPERTIES PREFIX "")
endif()
target_link_libraries(${IMPL_TARGET} PRIVATE common ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB})
target_link_libraries(${IMPL_TARGET} PRIVATE alcommon ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB})
if(ALSOFT_UWP)
set(ALSOFT_CPPWINRT_VERSION "2.0.230706.1" CACHE STRING "The soft-oal default cppwinrt version")
find_program(NUGET_EXE NAMES nuget)
if(NOT NUGET_EXE)
message("NUGET.EXE not found.")
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()
if(NOT WIN32 AND NOT APPLE)
# FIXME: This doesn't put a dependency on the version script. Changing
# the version script will not cause a relink as it should.
set_property(TARGET ${IMPL_TARGET} APPEND_STRING PROPERTY
LINK_FLAGS " -Wl,--version-script=${OpenAL_SOURCE_DIR}/libopenal.version")
target_link_options(${IMPL_TARGET} PRIVATE
"-Wl,--version-script=${OpenAL_SOURCE_DIR}/libopenal.version")
endif()
if(APPLE AND ALSOFT_OSX_FRAMEWORK)
@ -1443,8 +1470,8 @@ set_target_properties(${IMPL_TARGET} PROPERTIES ${DEFAULT_TARGET_PROPS}
SOVERSION ${LIB_MAJOR_VERSION}
)
target_compile_definitions(${IMPL_TARGET}
PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}"
${CPP_DEFS})
PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES $<$<BOOL:${ALSOFT_EAX}>:ALSOFT_EAX>
"ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS})
target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS})
if(TARGET build_version)
@ -1462,8 +1489,7 @@ if(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC"
message(STATUS "WARNING: Cannot find dlltool, disabling .def/.lib generation")
endif()
else()
set_property(TARGET OpenAL APPEND_STRING PROPERTY
LINK_FLAGS " -Wl,--output-def,OpenAL32.def")
target_link_options(OpenAL PRIVATE "-Wl,--output-def,${PROJECT_BINARY_DIR}/OpenAL32.def")
add_custom_command(TARGET OpenAL POST_BUILD
COMMAND "${SED_EXECUTABLE}" -i -e "s/ @[^ ]*//" OpenAL32.def
COMMAND "${CMAKE_DLLTOOL}" -d OpenAL32.def -l OpenAL32.lib -D OpenAL32.dll
@ -1494,7 +1520,10 @@ if(FPMATH_SET)
message(STATUS "Building with SSE${FPMATH_SET} codegen")
message(STATUS "")
endif()
if(ALSOFT_UWP)
message(STATUS "Building with UWP support")
message(STATUS "")
endif()
if(ALSOFT_EAX)
message(STATUS "Building with legacy EAX extension support")
message(STATUS "")
@ -1580,7 +1609,7 @@ if(ALSOFT_UTILS)
target_include_directories(uhjdecoder
PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common)
target_compile_options(uhjdecoder PRIVATE ${C_FLAGS})
target_link_libraries(uhjdecoder PUBLIC common
target_link_libraries(uhjdecoder PUBLIC alcommon
PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG})
set_target_properties(uhjdecoder PROPERTIES ${DEFAULT_TARGET_PROPS})
@ -1589,7 +1618,7 @@ if(ALSOFT_UTILS)
target_include_directories(uhjencoder
PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common)
target_compile_options(uhjencoder PRIVATE ${C_FLAGS})
target_link_libraries(uhjencoder PUBLIC common
target_link_libraries(uhjencoder PUBLIC alcommon
PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG})
set_target_properties(uhjencoder PROPERTIES ${DEFAULT_TARGET_PROPS})
endif()
@ -1602,7 +1631,7 @@ if(ALSOFT_UTILS)
target_compile_definitions(sofa-support PRIVATE ${CPP_DEFS})
target_include_directories(sofa-support PUBLIC ${OpenAL_SOURCE_DIR}/common)
target_compile_options(sofa-support PRIVATE ${C_FLAGS})
target_link_libraries(sofa-support PUBLIC common MySOFA::MySOFA PRIVATE ${LINKER_FLAGS})
target_link_libraries(sofa-support PUBLIC alcommon MySOFA::MySOFA PRIVATE ${LINKER_FLAGS})
set_target_properties(sofa-support PROPERTIES ${DEFAULT_TARGET_PROPS})
set(MAKEMHR_SRCS
@ -1612,9 +1641,6 @@ if(ALSOFT_UTILS)
utils/makemhr/loadsofa.h
utils/makemhr/makemhr.cpp
utils/makemhr/makemhr.h)
if(NOT HAVE_GETOPT)
set(MAKEMHR_SRCS ${MAKEMHR_SRCS} utils/getopt.c utils/getopt.h)
endif()
add_executable(makemhr ${MAKEMHR_SRCS})
target_compile_definitions(makemhr PRIVATE ${CPP_DEFS})
target_include_directories(makemhr
@ -1644,22 +1670,22 @@ endif()
# Add a static library with common functions used by multiple example targets
add_library(ex-common STATIC EXCLUDE_FROM_ALL
add_library(al-excommon STATIC EXCLUDE_FROM_ALL
examples/common/alhelpers.c
examples/common/alhelpers.h)
target_compile_definitions(ex-common PUBLIC ${CPP_DEFS})
target_include_directories(ex-common PUBLIC ${OpenAL_SOURCE_DIR}/common)
target_compile_options(ex-common PUBLIC ${C_FLAGS})
target_link_libraries(ex-common PUBLIC OpenAL PRIVATE ${RT_LIB})
set_target_properties(ex-common PROPERTIES ${DEFAULT_TARGET_PROPS})
target_compile_definitions(al-excommon PUBLIC ${CPP_DEFS})
target_include_directories(al-excommon PUBLIC ${OpenAL_SOURCE_DIR}/common)
target_compile_options(al-excommon PUBLIC ${C_FLAGS})
target_link_libraries(al-excommon PUBLIC OpenAL PRIVATE ${RT_LIB})
set_target_properties(al-excommon PROPERTIES ${DEFAULT_TARGET_PROPS})
if(ALSOFT_EXAMPLES)
add_executable(altonegen examples/altonegen.c)
target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} ex-common ${UNICODE_FLAG})
target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} al-excommon ${UNICODE_FLAG})
set_target_properties(altonegen PROPERTIES ${DEFAULT_TARGET_PROPS})
add_executable(alrecord examples/alrecord.c)
target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} ex-common ${UNICODE_FLAG})
target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} al-excommon ${UNICODE_FLAG})
set_target_properties(alrecord PROPERTIES ${DEFAULT_TARGET_PROPS})
if(ALSOFT_INSTALL_EXAMPLES)
@ -1670,48 +1696,53 @@ if(ALSOFT_EXAMPLES)
if(SNDFILE_FOUND)
add_executable(alplay examples/alplay.c)
target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common
target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon
${UNICODE_FLAG})
set_target_properties(alplay PROPERTIES ${DEFAULT_TARGET_PROPS})
add_executable(alstream examples/alstream.c)
target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common
target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon
${UNICODE_FLAG})
set_target_properties(alstream PROPERTIES ${DEFAULT_TARGET_PROPS})
add_executable(alreverb examples/alreverb.c)
target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common
target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon
${UNICODE_FLAG})
set_target_properties(alreverb PROPERTIES ${DEFAULT_TARGET_PROPS})
add_executable(almultireverb examples/almultireverb.c)
target_link_libraries(almultireverb
PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common ${MATH_LIB} ${UNICODE_FLAG})
PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG})
set_target_properties(almultireverb PROPERTIES ${DEFAULT_TARGET_PROPS})
add_executable(allatency examples/allatency.c)
target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common
target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon
${UNICODE_FLAG})
set_target_properties(allatency PROPERTIES ${DEFAULT_TARGET_PROPS})
add_executable(alhrtf examples/alhrtf.c)
target_link_libraries(alhrtf
PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common ${MATH_LIB} ${UNICODE_FLAG})
PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG})
set_target_properties(alhrtf PROPERTIES ${DEFAULT_TARGET_PROPS})
add_executable(alstreamcb examples/alstreamcb.cpp)
target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common
target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon
${UNICODE_FLAG})
set_target_properties(alstreamcb PROPERTIES ${DEFAULT_TARGET_PROPS})
add_executable(alconvolve examples/alconvolve.c)
target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} common SndFile::SndFile ex-common
add_executable(aldirect examples/aldirect.cpp)
target_link_libraries(aldirect PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon
${UNICODE_FLAG})
set_target_properties(aldirect PROPERTIES ${DEFAULT_TARGET_PROPS})
add_executable(alconvolve examples/alconvolve.c)
target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} alcommon SndFile::SndFile
al-excommon ${UNICODE_FLAG})
set_target_properties(alconvolve PROPERTIES ${DEFAULT_TARGET_PROPS})
if(ALSOFT_INSTALL_EXAMPLES)
set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alplay alstream alreverb almultireverb allatency
alhrtf)
alhrtf aldirect)
endif()
message(STATUS "Building SndFile example programs")
@ -1720,7 +1751,7 @@ if(ALSOFT_EXAMPLES)
if(SDL2_FOUND)
add_executable(alloopback examples/alloopback.c)
target_link_libraries(alloopback
PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ex-common ${MATH_LIB})
PRIVATE ${LINKER_FLAGS} SDL2::SDL2 al-excommon ${MATH_LIB})
set_target_properties(alloopback PROPERTIES ${DEFAULT_TARGET_PROPS})
if(ALSOFT_INSTALL_EXAMPLES)
@ -1757,7 +1788,7 @@ if(ALSOFT_EXAMPLES)
add_executable(alffplay examples/alffplay.cpp)
target_include_directories(alffplay PRIVATE ${FFMPEG_INCLUDE_DIRS})
target_link_libraries(alffplay
PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ${FFMPEG_LIBRARIES} ex-common)
PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ${FFMPEG_LIBRARIES} al-excommon)
set_target_properties(alffplay PROPERTIES ${DEFAULT_TARGET_PROPS})
if(ALSOFT_INSTALL_EXAMPLES)
@ -1769,6 +1800,10 @@ if(ALSOFT_EXAMPLES)
message(STATUS "")
endif()
if (ALSOFT_TESTS)
add_subdirectory(tests)
endif()
if(EXTRA_INSTALLS)
install(TARGETS ${EXTRA_INSTALLS}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}

View file

@ -0,0 +1,38 @@
A modified PFFFT is included, with the following license.
Copyright (c) 2023 Christopher Robinson
Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
Copyright (c) 2004 the University Corporation for Atmospheric
Research ("UCAR"). All rights reserved. Developed by NCAR's
Computational and Information Systems Laboratory, UCAR,
www.cisl.ucar.edu.
Redistribution and use of the Software in source and binary forms,
with or without modification, is permitted provided that the
following conditions are met:
- Neither the names of NCAR's Computational and Information Systems
Laboratory, the University Corporation for Atmospheric Research,
nor the names of its sponsors or contributors may be used to
endorse or promote products derived from this Software without
specific prior written permission.
- Redistributions of source code must retain the above copyright
notices, this list of conditions, and the disclaimer below.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer below in the
documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1)
cmake_minimum_required(VERSION 3.1...3.18)
include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake")

View file

@ -64,6 +64,33 @@ as application-agnostic behavior of the library. See alsoftrc.sample for
available settings.
Language Bindings
-----------------
As a C API, OpenAL Soft can be used directly by any language that can use
functions with C linkage. For languages that can't directly use C-style
headers, bindings may be developed to allow code written in that language to
call into the library. Some bindings for some languages are listed here.
C# Bindings:
* [OpenTK](https://opentk.net/) includes low-level C# bindings for the OpenAL
API, including some extensions. It also includes utility libraries for math and
linear algebra, which can be useful for 3D calculations.
Java Bindings:
* [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
higher level Sound3D Toolkit API and utility functions to make easier use of
OpenAL features and capabilities.
Python Bindings:
* [PyOpenAL](https://pypi.org/project/PyOpenAL/). Also includes methods to play
wave files and, with PyOgg, also Vorbis, Opus, and FLAC.
Other bindings for these and other languages also exist. This list will grow as
more bindings are found.
Acknowledgements
----------------

File diff suppressed because it is too large Load diff

View file

@ -1,23 +1,24 @@
#ifndef AL_AUXEFFECTSLOT_H
#define AL_AUXEFFECTSLOT_H
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <string_view>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "alc/device.h"
#include "alc/effects/base.h"
#include "almalloc.h"
#include "atomic.h"
#include "alnumeric.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "intrusive_ptr.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include <memory>
#include "eax/api.h"
#include "eax/call.h"
#include "eax/effect.h"
#include "eax/exception.h"
@ -26,8 +27,6 @@
#endif // ALSOFT_EAX
struct ALbuffer;
struct ALeffect;
struct WetBuffer;
#ifdef ALSOFT_EAX
class EaxFxSlotException : public EaxException {
@ -38,30 +37,30 @@ public:
};
#endif // ALSOFT_EAX
enum class SlotState : ALenum {
Initial = AL_INITIAL,
Playing = AL_PLAYING,
Stopped = AL_STOPPED,
enum class SlotState : bool {
Initial, Playing,
};
struct ALeffectslot {
ALuint EffectId{};
float Gain{1.0f};
bool AuxSendAuto{true};
ALeffectslot *Target{nullptr};
ALbuffer *Buffer{nullptr};
struct {
struct EffectData {
EffectSlotType Type{EffectSlotType::None};
EffectProps Props{};
al::intrusive_ptr<EffectState> State;
} Effect;
};
EffectData Effect;
bool mPropsDirty{true};
SlotState mState{SlotState::Initial};
RefCount ref{0u};
std::atomic<ALuint> ref{0u};
EffectSlot *mSlot{nullptr};
@ -73,23 +72,23 @@ struct ALeffectslot {
ALeffectslot& operator=(const ALeffectslot&) = delete;
~ALeffectslot();
ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context);
void updateProps(ALCcontext *context);
ALenum initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps,
ALCcontext *context);
void updateProps(ALCcontext *context) const;
/* This can be new'd for the context's default effect slot. */
DEF_NEWDEL(ALeffectslot)
static void SetName(ALCcontext *context, ALuint id, std::string_view name);
#ifdef ALSOFT_EAX
public:
void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index);
EaxFxSlotIndexValue eax_get_index() const noexcept { return eax_fx_slot_index_; }
const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept
[[nodiscard]] auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return eax_fx_slot_index_; }
[[nodiscard]] auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES&
{ return eax_; }
// Returns `true` if all sources should be updated, or `false` otherwise.
bool eax_dispatch(const EaxCall& call)
[[nodiscard]] auto eax_dispatch(const EaxCall& call) -> bool
{ return call.is_get() ? eax_get(call) : eax_set(call); }
void eax_commit();
@ -193,6 +192,17 @@ private:
}
};
struct Eax5FlagsValidator {
void operator()(unsigned long ulFlags) const
{
EaxRangeValidator{}(
"Flags",
ulFlags,
0UL,
~EAX50FXSLOTFLAGS_RESERVED);
}
};
struct Eax5OcclusionValidator {
void operator()(long lOcclusion) const
{
@ -215,21 +225,13 @@ private:
}
};
struct Eax5FlagsValidator {
void operator()(unsigned long ulFlags) const
{
EaxRangeValidator{}(
"Flags",
ulFlags,
0UL,
~EAX50FXSLOTFLAGS_RESERVED);
}
};
struct Eax5AllValidator {
void operator()(const EAX50FXSLOTPROPERTIES& all) const
{
Eax4AllValidator{}(static_cast<const EAX40FXSLOTPROPERTIES&>(all));
Eax4GuidLoadEffectValidator{}(all.guidLoadEffect);
Eax4VolumeValidator{}(all.lVolume);
Eax4LockValidator{}(all.lLock);
Eax5FlagsValidator{}(all.ulFlags);
Eax5OcclusionValidator{}(all.lOcclusion);
Eax5OcclusionLfRatioValidator{}(all.flOcclusionLFRatio);
}
@ -277,14 +279,14 @@ private:
dst = src;
}
constexpr bool eax4_fx_slot_is_legacy() const noexcept
[[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool
{ return eax_fx_slot_index_ < 2; }
void eax4_fx_slot_ensure_unlocked() const;
static ALenum eax_get_efx_effect_type(const GUID& guid);
const GUID& eax_get_eax_default_effect_guid() const noexcept;
long eax_get_eax_default_lock() const noexcept;
[[nodiscard]] static auto eax_get_efx_effect_type(const GUID& guid) -> ALenum;
[[nodiscard]] auto eax_get_eax_default_effect_guid() const noexcept -> const GUID&;
[[nodiscard]] auto eax_get_eax_default_lock() const noexcept -> long;
void eax4_fx_slot_set_defaults(Eax4Props& props) noexcept;
void eax5_fx_slot_set_defaults(Eax5Props& props) noexcept;
@ -293,8 +295,8 @@ private:
void eax_fx_slot_set_current_defaults();
void eax_fx_slot_set_defaults();
void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) const;
void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) const;
static void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props);
static void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props);
void eax_fx_slot_get(const EaxCall& call) const;
// Returns `true` if all sources should be updated, or `false` otherwise.
bool eax_get(const EaxCall& call);
@ -307,7 +309,7 @@ private:
void eax4_fx_slot_set_all(const EaxCall& call);
void eax5_fx_slot_set_all(const EaxCall& call);
bool eax_fx_slot_should_update_sources() const noexcept;
[[nodiscard]] auto eax_fx_slot_should_update_sources() const noexcept -> bool;
// Returns `true` if all sources should be updated, or `false` otherwise.
bool eax4_fx_slot_set(const EaxCall& call);
@ -365,4 +367,20 @@ EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context);
void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot);
#endif // ALSOFT_EAX
struct EffectSlotSubList {
uint64_t FreeMask{~0_u64};
gsl::owner<std::array<ALeffectslot,64>*> EffectSlots{nullptr};
EffectSlotSubList() noexcept = default;
EffectSlotSubList(const EffectSlotSubList&) = delete;
EffectSlotSubList(EffectSlotSubList&& rhs) noexcept
: FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots}
{ rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; }
~EffectSlotSubList();
EffectSlotSubList& operator=(const EffectSlotSubList&) = delete;
EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; }
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,23 @@
#ifndef AL_BUFFER_H
#define AL_BUFFER_H
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <string_view>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "albyte.h"
#include "alc/inprogext.h"
#include "almalloc.h"
#include "atomic.h"
#include "alnumeric.h"
#include "core/buffer_storage.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include "eax/x_ram.h"
enum class EaxStorage : uint8_t {
Automatic,
Accessible,
@ -26,7 +29,7 @@ enum class EaxStorage : uint8_t {
struct ALbuffer : public BufferStorage {
ALbitfieldSOFT Access{0u};
al::vector<al::byte,16> mDataStorage;
al::vector<std::byte,16> mDataStorage;
ALuint OriginalSize{0};
@ -42,12 +45,14 @@ struct ALbuffer : public BufferStorage {
ALuint mLoopEnd{0u};
/* Number of times buffer was attached to a source (deletion can only occur when 0) */
RefCount ref{0u};
std::atomic<ALuint> ref{0u};
/* Self ID */
ALuint id{0};
DISABLE_ALLOC()
static void SetName(ALCcontext *context, ALuint id, std::string_view name);
DISABLE_ALLOC
#ifdef ALSOFT_EAX
EaxStorage eax_x_ram_mode{EaxStorage::Automatic};
@ -55,4 +60,19 @@ struct ALbuffer : public BufferStorage {
#endif // ALSOFT_EAX
};
struct BufferSubList {
uint64_t FreeMask{~0_u64};
gsl::owner<std::array<ALbuffer,64>*> Buffers{nullptr};
BufferSubList() noexcept = default;
BufferSubList(const BufferSubList&) = delete;
BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers}
{ rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; }
~BufferSubList();
BufferSubList& operator=(const BufferSubList&) = delete;
BufferSubList& operator=(BufferSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; }
};
#endif

View file

@ -0,0 +1,618 @@
#include "config.h"
#include "debug.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cstring>
#include <deque>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "alc/context.h"
#include "alc/device.h"
#include "alc/inprogext.h"
#include "alnumeric.h"
#include "alspan.h"
#include "alstring.h"
#include "auxeffectslot.h"
#include "buffer.h"
#include "core/logging.h"
#include "core/voice.h"
#include "direct_defs.h"
#include "effect.h"
#include "error.h"
#include "filter.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "source.h"
/* Declared here to prevent compilers from thinking it should be inlined, which
* GCC warns about increasing code size.
*/
DebugGroup::~DebugGroup() = default;
namespace {
static_assert(DebugSeverityBase+DebugSeverityCount <= 32, "Too many debug bits");
template<typename T, T ...Vals>
constexpr auto make_array_sequence(std::integer_sequence<T, Vals...>)
{ return std::array<T,sizeof...(Vals)>{Vals...}; }
template<typename T, size_t N>
constexpr auto make_array_sequence()
{ return make_array_sequence(std::make_integer_sequence<T,N>{}); }
constexpr auto GetDebugSource(ALenum source) noexcept -> std::optional<DebugSource>
{
switch(source)
{
case AL_DEBUG_SOURCE_API_EXT: return DebugSource::API;
case AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT: return DebugSource::System;
case AL_DEBUG_SOURCE_THIRD_PARTY_EXT: return DebugSource::ThirdParty;
case AL_DEBUG_SOURCE_APPLICATION_EXT: return DebugSource::Application;
case AL_DEBUG_SOURCE_OTHER_EXT: return DebugSource::Other;
}
return std::nullopt;
}
constexpr auto GetDebugType(ALenum type) noexcept -> std::optional<DebugType>
{
switch(type)
{
case AL_DEBUG_TYPE_ERROR_EXT: return DebugType::Error;
case AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT: return DebugType::DeprecatedBehavior;
case AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT: return DebugType::UndefinedBehavior;
case AL_DEBUG_TYPE_PORTABILITY_EXT: return DebugType::Portability;
case AL_DEBUG_TYPE_PERFORMANCE_EXT: return DebugType::Performance;
case AL_DEBUG_TYPE_MARKER_EXT: return DebugType::Marker;
case AL_DEBUG_TYPE_PUSH_GROUP_EXT: return DebugType::PushGroup;
case AL_DEBUG_TYPE_POP_GROUP_EXT: return DebugType::PopGroup;
case AL_DEBUG_TYPE_OTHER_EXT: return DebugType::Other;
}
return std::nullopt;
}
constexpr auto GetDebugSeverity(ALenum severity) noexcept -> std::optional<DebugSeverity>
{
switch(severity)
{
case AL_DEBUG_SEVERITY_HIGH_EXT: return DebugSeverity::High;
case AL_DEBUG_SEVERITY_MEDIUM_EXT: return DebugSeverity::Medium;
case AL_DEBUG_SEVERITY_LOW_EXT: return DebugSeverity::Low;
case AL_DEBUG_SEVERITY_NOTIFICATION_EXT: return DebugSeverity::Notification;
}
return std::nullopt;
}
constexpr auto GetDebugSourceEnum(DebugSource source) -> ALenum
{
switch(source)
{
case DebugSource::API: return AL_DEBUG_SOURCE_API_EXT;
case DebugSource::System: return AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT;
case DebugSource::ThirdParty: return AL_DEBUG_SOURCE_THIRD_PARTY_EXT;
case DebugSource::Application: return AL_DEBUG_SOURCE_APPLICATION_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))};
}
constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum
{
switch(type)
{
case DebugType::Error: return AL_DEBUG_TYPE_ERROR_EXT;
case DebugType::DeprecatedBehavior: return AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT;
case DebugType::UndefinedBehavior: return AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT;
case DebugType::Portability: return AL_DEBUG_TYPE_PORTABILITY_EXT;
case DebugType::Performance: return AL_DEBUG_TYPE_PERFORMANCE_EXT;
case DebugType::Marker: return AL_DEBUG_TYPE_MARKER_EXT;
case DebugType::PushGroup: return AL_DEBUG_TYPE_PUSH_GROUP_EXT;
case DebugType::PopGroup: return AL_DEBUG_TYPE_POP_GROUP_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))};
}
constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum
{
switch(severity)
{
case DebugSeverity::High: return AL_DEBUG_SEVERITY_HIGH_EXT;
case DebugSeverity::Medium: return AL_DEBUG_SEVERITY_MEDIUM_EXT;
case DebugSeverity::Low: return AL_DEBUG_SEVERITY_LOW_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))};
}
constexpr auto GetDebugSourceName(DebugSource source) noexcept -> const char*
{
switch(source)
{
case DebugSource::API: return "API";
case DebugSource::System: return "Audio System";
case DebugSource::ThirdParty: return "Third Party";
case DebugSource::Application: return "Application";
case DebugSource::Other: return "Other";
}
return "<invalid source>";
}
constexpr auto GetDebugTypeName(DebugType type) noexcept -> const char*
{
switch(type)
{
case DebugType::Error: return "Error";
case DebugType::DeprecatedBehavior: return "Deprecated Behavior";
case DebugType::UndefinedBehavior: return "Undefined Behavior";
case DebugType::Portability: return "Portability";
case DebugType::Performance: return "Performance";
case DebugType::Marker: return "Marker";
case DebugType::PushGroup: return "Push Group";
case DebugType::PopGroup: return "Pop Group";
case DebugType::Other: return "Other";
}
return "<invalid type>";
}
constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> const char*
{
switch(severity)
{
case DebugSeverity::High: return "High";
case DebugSeverity::Medium: return "Medium";
case DebugSeverity::Low: return "Low";
case DebugSeverity::Notification: return "Notification";
}
return "<invalid severity>";
}
} // namespace
void ALCcontext::sendDebugMessage(std::unique_lock<std::mutex> &debuglock, DebugSource source,
DebugType type, ALuint id, DebugSeverity severity, std::string_view message)
{
if(!mDebugEnabled.load(std::memory_order_relaxed)) UNLIKELY
return;
if(message.length() >= MaxDebugMessageLength) UNLIKELY
{
ERR("Debug message too long (%zu >= %d):\n-> %.*s\n", message.length(),
MaxDebugMessageLength, al::sizei(message), message.data());
return;
}
DebugGroup &debug = mDebugGroups.back();
const uint64_t idfilter{(1_u64 << (DebugSourceBase+al::to_underlying(source)))
| (1_u64 << (DebugTypeBase+al::to_underlying(type)))
| (uint64_t{id} << 32)};
auto iditer = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(), idfilter);
if(iditer != debug.mIdFilters.cend() && *iditer == idfilter)
return;
const uint filter{(1u << (DebugSourceBase+al::to_underlying(source)))
| (1u << (DebugTypeBase+al::to_underlying(type)))
| (1u << (DebugSeverityBase+al::to_underlying(severity)))};
auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter);
if(iter != debug.mFilters.cend() && *iter == filter)
return;
if(mDebugCb)
{
auto callback = mDebugCb;
auto param = mDebugParam;
debuglock.unlock();
callback(GetDebugSourceEnum(source), GetDebugTypeEnum(type), id,
GetDebugSeverityEnum(severity), static_cast<ALsizei>(message.length()), message.data(),
param);
}
else
{
if(mDebugLog.size() < MaxDebugLoggedMessages)
mDebugLog.emplace_back(source, type, id, severity, message);
else UNLIKELY
ERR("Debug message log overflow. Lost message:\n"
" Source: %s\n"
" Type: %s\n"
" ID: %u\n"
" Severity: %s\n"
" Message: \"%.*s\"\n",
GetDebugSourceName(source), GetDebugTypeName(type), id,
GetDebugSeverityName(severity), al::sizei(message), message.data());
}
}
FORCE_ALIGN DECL_FUNCEXT2(void, alDebugMessageCallback,EXT, ALDEBUGPROCEXT,callback, void*,userParam)
FORCE_ALIGN void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context,
ALDEBUGPROCEXT callback, void *userParam) noexcept
{
std::lock_guard<std::mutex> debuglock{context->mDebugCbLock};
context->mDebugCb = callback;
context->mDebugParam = userParam;
}
FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageInsert,EXT, ALenum,source, ALenum,type, ALuint,id, ALenum,severity, ALsizei,length, const ALchar*,message)
FORCE_ALIGN void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source,
ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) noexcept
try {
if(!context->mContextFlags.test(ContextFlags::DebugBit))
return;
if(!message)
throw al::context_error{AL_INVALID_VALUE, "Null message pointer"};
auto msgview = (length < 0) ? std::string_view{message}
: std::string_view{message, static_cast<uint>(length)};
if(msgview.size() >= MaxDebugMessageLength)
throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)",
msgview.size(), MaxDebugMessageLength};
auto dsource = GetDebugSource(source);
if(!dsource)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source};
if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application)
throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source};
auto dtype = GetDebugType(type);
if(!dtype)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type};
auto dseverity = GetDebugSeverity(severity);
if(!dseverity)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity};
context->debugMessage(*dsource, *dtype, id, *dseverity, msgview);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageControl,EXT, ALenum,source, ALenum,type, ALenum,severity, ALsizei,count, const ALuint*,ids, ALboolean,enable)
FORCE_ALIGN void AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source,
ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) noexcept
try {
if(count > 0)
{
if(!ids)
throw al::context_error{AL_INVALID_VALUE, "IDs is null with non-0 count"};
if(source == AL_DONT_CARE_EXT)
throw al::context_error{AL_INVALID_OPERATION,
"Debug source cannot be AL_DONT_CARE_EXT with IDs"};
if(type == AL_DONT_CARE_EXT)
throw al::context_error{AL_INVALID_OPERATION,
"Debug type cannot be AL_DONT_CARE_EXT with IDs"};
if(severity != AL_DONT_CARE_EXT)
throw al::context_error{AL_INVALID_OPERATION,
"Debug severity must be AL_DONT_CARE_EXT with IDs"};
}
if(enable != AL_TRUE && enable != AL_FALSE)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug enable %d", enable};
static constexpr size_t ElemCount{DebugSourceCount + DebugTypeCount + DebugSeverityCount};
static constexpr auto Values = make_array_sequence<uint8_t,ElemCount>();
auto srcIndices = al::span{Values}.subspan(DebugSourceBase,DebugSourceCount);
if(source != AL_DONT_CARE_EXT)
{
auto dsource = GetDebugSource(source);
if(!dsource)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source};
srcIndices = srcIndices.subspan(al::to_underlying(*dsource), 1);
}
auto typeIndices = al::span{Values}.subspan(DebugTypeBase,DebugTypeCount);
if(type != AL_DONT_CARE_EXT)
{
auto dtype = GetDebugType(type);
if(!dtype)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type};
typeIndices = typeIndices.subspan(al::to_underlying(*dtype), 1);
}
auto svrIndices = al::span{Values}.subspan(DebugSeverityBase,DebugSeverityCount);
if(severity != AL_DONT_CARE_EXT)
{
auto dseverity = GetDebugSeverity(severity);
if(!dseverity)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity};
svrIndices = svrIndices.subspan(al::to_underlying(*dseverity), 1);
}
std::lock_guard<std::mutex> debuglock{context->mDebugCbLock};
DebugGroup &debug = context->mDebugGroups.back();
if(count > 0)
{
const uint filterbase{(1u<<srcIndices[0]) | (1u<<typeIndices[0])};
for(const uint id : al::span{ids, static_cast<uint>(count)})
{
const uint64_t filter{filterbase | (uint64_t{id} << 32)};
auto iter = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(),
filter);
if(!enable && (iter == debug.mIdFilters.cend() || *iter != filter))
debug.mIdFilters.insert(iter, filter);
else if(enable && iter != debug.mIdFilters.cend() && *iter == filter)
debug.mIdFilters.erase(iter);
}
}
else
{
auto apply_filter = [enable,&debug](const uint filter)
{
auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter);
if(!enable && (iter == debug.mFilters.cend() || *iter != filter))
debug.mFilters.insert(iter, filter);
else if(enable && iter != debug.mFilters.cend() && *iter == filter)
debug.mFilters.erase(iter);
};
auto apply_severity = [apply_filter,svrIndices](const uint filter)
{
std::for_each(svrIndices.cbegin(), svrIndices.cend(),
[apply_filter,filter](const uint idx){ apply_filter(filter | (1<<idx)); });
};
auto apply_type = [apply_severity,typeIndices](const uint filter)
{
std::for_each(typeIndices.cbegin(), typeIndices.cend(),
[apply_severity,filter](const uint idx){ apply_severity(filter | (1<<idx)); });
};
std::for_each(srcIndices.cbegin(), srcIndices.cend(),
[apply_type](const uint idx){ apply_type(1<<idx); });
}
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
FORCE_ALIGN DECL_FUNCEXT4(void, alPushDebugGroup,EXT, ALenum,source, ALuint,id, ALsizei,length, const ALchar*,message)
FORCE_ALIGN void AL_APIENTRY alPushDebugGroupDirectEXT(ALCcontext *context, ALenum source,
ALuint id, ALsizei length, const ALchar *message) noexcept
try {
if(length < 0)
{
size_t newlen{std::strlen(message)};
if(newlen >= MaxDebugMessageLength)
throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", newlen,
MaxDebugMessageLength};
length = static_cast<ALsizei>(newlen);
}
else if(length >= MaxDebugMessageLength)
throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%d >= %d)", length,
MaxDebugMessageLength};
auto dsource = GetDebugSource(source);
if(!dsource)
throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source};
if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application)
throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source};
std::unique_lock<std::mutex> debuglock{context->mDebugCbLock};
if(context->mDebugGroups.size() >= MaxDebugGroupDepth)
throw al::context_error{AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups"};
context->mDebugGroups.emplace_back(*dsource, id,
std::string_view{message, static_cast<uint>(length)});
auto &oldback = *(context->mDebugGroups.end()-2);
auto &newback = context->mDebugGroups.back();
newback.mFilters = oldback.mFilters;
newback.mIdFilters = oldback.mIdFilters;
if(context->mContextFlags.test(ContextFlags::DebugBit))
context->sendDebugMessage(debuglock, newback.mSource, DebugType::PushGroup, newback.mId,
DebugSeverity::Notification, newback.mMessage);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT)
FORCE_ALIGN void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) noexcept
try {
std::unique_lock<std::mutex> debuglock{context->mDebugCbLock};
if(context->mDebugGroups.size() <= 1)
throw al::context_error{AL_STACK_UNDERFLOW_EXT,
"Attempting to pop the default debug group"};
DebugGroup &debug = context->mDebugGroups.back();
const auto source = debug.mSource;
const auto id = debug.mId;
std::string message{std::move(debug.mMessage)};
context->mDebugGroups.pop_back();
if(context->mContextFlags.test(ContextFlags::DebugBit))
context->sendDebugMessage(debuglock, source, DebugType::PopGroup, id,
DebugSeverity::Notification, message);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
FORCE_ALIGN DECL_FUNCEXT8(ALuint, alGetDebugMessageLog,EXT, ALuint,count, ALsizei,logBufSize, ALenum*,sources, ALenum*,types, ALuint*,ids, ALenum*,severities, ALsizei*,lengths, ALchar*,logBuf)
FORCE_ALIGN ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count,
ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities,
ALsizei *lengths, ALchar *logBuf) noexcept
try {
if(logBufSize < 0)
throw al::context_error{AL_INVALID_VALUE, "Negative debug log buffer size"};
auto sourcesOut = al::span{sources, sources ? count : 0u};
auto typesOut = al::span{types, types ? count : 0u};
auto idsOut = al::span{ids, ids ? count : 0u};
auto severitiesOut = al::span{severities, severities ? count : 0u};
auto lengthsOut = al::span{lengths, lengths ? count : 0u};
auto logOut = al::span{logBuf, logBuf ? static_cast<ALuint>(logBufSize) : 0u};
std::lock_guard<std::mutex> debuglock{context->mDebugCbLock};
for(ALuint i{0};i < count;++i)
{
if(context->mDebugLog.empty())
return i;
auto &entry = context->mDebugLog.front();
const size_t tocopy{entry.mMessage.size() + 1};
if(logOut.data() != nullptr)
{
if(logOut.size() < tocopy)
return i;
auto oiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logOut.begin());
*oiter = '\0';
logOut = {oiter+1, logOut.end()};
}
if(!sourcesOut.empty())
{
sourcesOut.front() = GetDebugSourceEnum(entry.mSource);
sourcesOut = sourcesOut.subspan<1>();
}
if(!typesOut.empty())
{
typesOut.front() = GetDebugTypeEnum(entry.mType);
typesOut = typesOut.subspan<1>();
}
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();
}
return count;
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
return 0;
}
FORCE_ALIGN DECL_FUNCEXT4(void, alObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,length, const ALchar*,label)
FORCE_ALIGN void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier,
ALuint name, ALsizei length, const ALchar *label) noexcept
try {
if(!label && length != 0)
throw al::context_error{AL_INVALID_VALUE, "Null label pointer"};
auto objname = (length < 0) ? std::string_view{label}
: std::string_view{label, static_cast<uint>(length)};
if(objname.size() >= MaxObjectLabelLength)
throw al::context_error{AL_INVALID_VALUE, "Object label length too long (%zu >= %d)",
objname.size(), MaxObjectLabelLength};
switch(identifier)
{
case AL_SOURCE_EXT: ALsource::SetName(context, name, objname); return;
case AL_BUFFER: ALbuffer::SetName(context, name, objname); return;
case AL_FILTER_EXT: ALfilter::SetName(context, name, objname); return;
case AL_EFFECT_EXT: ALeffect::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};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label)
FORCE_ALIGN void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier,
ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept
try {
if(bufSize < 0)
throw al::context_error{AL_INVALID_VALUE, "Negative label bufSize"};
if(!label && !length)
throw al::context_error{AL_INVALID_VALUE, "Null length and label"};
if(label && bufSize == 0)
throw al::context_error{AL_INVALID_VALUE, "Zero label bufSize"};
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)
{
std::string_view objname;
auto iter = names.find(name);
if(iter != names.end())
objname = iter->second;
if(labelOut.empty())
*length = static_cast<ALsizei>(objname.size());
else
{
const size_t tocopy{std::min(objname.size(), labelOut.size()-1)};
auto oiter = std::copy_n(objname.cbegin(), tocopy, labelOut.begin());
*oiter = '\0';
if(length)
*length = static_cast<ALsizei>(tocopy);
}
};
if(identifier == AL_SOURCE_EXT)
{
std::lock_guard srclock{context->mSourceLock};
copy_name(context->mSourceNames);
}
else if(identifier == AL_BUFFER)
{
ALCdevice *device{context->mALDevice.get()};
std::lock_guard buflock{device->BufferLock};
copy_name(device->mBufferNames);
}
else if(identifier == AL_FILTER_EXT)
{
ALCdevice *device{context->mALDevice.get()};
std::lock_guard filterlock{device->FilterLock};
copy_name(device->mFilterNames);
}
else if(identifier == AL_EFFECT_EXT)
{
ALCdevice *device{context->mALDevice.get()};
std::lock_guard effectlock{device->EffectLock};
copy_name(device->mEffectNames);
}
else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT)
{
std::lock_guard slotlock{context->mEffectSlotLock};
copy_name(context->mEffectSlotNames);
}
else
throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}

View file

@ -0,0 +1,70 @@
#ifndef AL_DEBUG_H
#define AL_DEBUG_H
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
using uint = unsigned int;
/* Somewhat arbitrary. Avoid letting it get out of control if the app enables
* logging but never reads it.
*/
inline constexpr std::uint8_t MaxDebugLoggedMessages{64};
inline constexpr std::uint16_t MaxDebugMessageLength{1024};
inline constexpr std::uint8_t MaxDebugGroupDepth{64};
inline constexpr std::uint16_t MaxObjectLabelLength{1024};
inline constexpr uint DebugSourceBase{0};
enum class DebugSource : std::uint8_t {
API = 0,
System,
ThirdParty,
Application,
Other,
};
inline constexpr uint DebugSourceCount{5};
inline constexpr uint DebugTypeBase{DebugSourceBase + DebugSourceCount};
enum class DebugType : std::uint8_t {
Error = 0,
DeprecatedBehavior,
UndefinedBehavior,
Portability,
Performance,
Marker,
PushGroup,
PopGroup,
Other,
};
inline constexpr uint DebugTypeCount{9};
inline constexpr uint DebugSeverityBase{DebugTypeBase + DebugTypeCount};
enum class DebugSeverity : std::uint8_t {
High = 0,
Medium,
Low,
Notification,
};
inline constexpr uint DebugSeverityCount{4};
struct DebugGroup {
const uint mId;
const DebugSource mSource;
std::string mMessage;
std::vector<uint> mFilters;
std::vector<std::uint64_t> mIdFilters;
template<typename T>
DebugGroup(DebugSource source, uint id, T&& message)
: mId{id}, mSource{source}, mMessage{std::forward<T>(message)}
{ }
DebugGroup(const DebugGroup&) = default;
DebugGroup(DebugGroup&&) = default;
~DebugGroup();
};
#endif /* AL_DEBUG_H */

View file

@ -0,0 +1,127 @@
#ifndef AL_DIRECT_DEFS_H
#define AL_DIRECT_DEFS_H
namespace detail_ {
template<typename T>
constexpr T DefaultVal() noexcept { return T{}; }
template<>
constexpr void DefaultVal() noexcept { }
} // namespace detail_
#define DECL_FUNC(R, Name) \
auto AL_APIENTRY Name() noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct(context.get()); \
}
#define DECL_FUNC1(R, Name, T1,n1) \
auto AL_APIENTRY Name(T1 n1) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct(context.get(), n1); \
}
#define DECL_FUNC2(R, Name, T1,n1, T2,n2) \
auto AL_APIENTRY Name(T1 n1, T2 n2) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct(context.get(), n1, n2); \
}
#define DECL_FUNC3(R, Name, T1,n1, T2,n2, T3,n3) \
auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct(context.get(), n1, n2, n3); \
}
#define DECL_FUNC4(R, Name, T1,n1, T2,n2, T3,n3, T4,n4) \
auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3, T4 n4) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct(context.get(), n1, n2, n3, n4); \
}
#define DECL_FUNC5(R, Name, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5) \
auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct(context.get(), n1, n2, n3, n4, n5); \
}
#define DECL_FUNCEXT(R, Name,Ext) \
auto AL_APIENTRY Name##Ext() noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct##Ext(context.get()); \
}
#define DECL_FUNCEXT1(R, Name,Ext, T1,n1) \
auto AL_APIENTRY Name##Ext(T1 n1) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct##Ext(context.get(), n1); \
}
#define DECL_FUNCEXT2(R, Name,Ext, T1,n1, T2,n2) \
auto AL_APIENTRY Name##Ext(T1 n1, T2 n2) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct##Ext(context.get(), n1, n2); \
}
#define DECL_FUNCEXT3(R, Name,Ext, T1,n1, T2,n2, T3,n3) \
auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct##Ext(context.get(), n1, n2, n3); \
}
#define DECL_FUNCEXT4(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4) \
auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct##Ext(context.get(), n1, n2, n3, n4); \
}
#define DECL_FUNCEXT5(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5) \
auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5); \
}
#define DECL_FUNCEXT6(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5, T6,n6) \
auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5, T6 n6) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6); \
}
#define DECL_FUNCEXT8(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5, T6,n6, T7,n7, T8,n8) \
auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5, T6 n6, T7 n7, T8 n8) noexcept -> R \
{ \
auto context = GetContextRef(); \
if(!context) UNLIKELY return detail_::DefaultVal<R>(); \
return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6, n7, n8); \
}
#endif /* AL_DIRECT_DEFS_H */

View file

@ -10,34 +10,39 @@
//
#include <array>
#include <cfloat>
#include <cstdint>
#include <cstring>
#include <array>
#include <tuple>
#ifdef _WIN32
#include <guiddef.h>
#endif
#include "AL/al.h"
#ifndef GUID_DEFINED
#define GUID_DEFINED
typedef struct _GUID {
#ifndef _WIN32
using GUID = struct _GUID { /* NOLINT(*-reserved-identifier) */
std::uint32_t Data1;
std::uint16_t Data2;
std::uint16_t Data3;
std::uint8_t Data4[8];
} GUID;
std::array<std::uint8_t,8> Data4;
};
#ifndef _SYS_GUID_OPERATOR_EQ_
#define _SYS_GUID_OPERATOR_EQ_
inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept
{ return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; }
inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept
{ return !(lhs == rhs); }
#endif // _SYS_GUID_OPERATOR_EQ_
#endif // GUID_DEFINED
#endif // _WIN32
#define DECL_EQOP(T, ...) \
[[nodiscard]] auto get_members() const noexcept { return std::forward_as_tuple(__VA_ARGS__); } \
[[nodiscard]] friend bool operator==(const T &lhs, const T &rhs) noexcept \
{ return lhs.get_members() == rhs.get_members(); } \
[[nodiscard]] friend bool operator!=(const T &lhs, const T &rhs) noexcept \
{ return !(lhs == rhs); }
extern const GUID DSPROPSETID_EAX_ReverbProperties;
@ -276,11 +281,15 @@ struct EAXVECTOR {
float x;
float y;
float z;
[[nodiscard]]
auto get_members() const noexcept { return std::forward_as_tuple(x, y, z); }
}; // EAXVECTOR
[[nodiscard]]
inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept
{ return std::memcmp(&lhs, &rhs, sizeof(EAXVECTOR)) == 0; }
{ return lhs.get_members() == rhs.get_members(); }
[[nodiscard]]
inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept
{ return !(lhs == rhs); }
@ -361,6 +370,7 @@ constexpr auto EAXCONTEXT_MINMACROFXFACTOR = 0.0F;
constexpr auto EAXCONTEXT_MAXMACROFXFACTOR = 1.0F;
constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F;
constexpr auto EAXCONTEXT_DEFAULTLASTERROR = EAX_OK;
extern const GUID EAXPROPERTYID_EAX40_FXSlot0;
extern const GUID EAXPROPERTYID_EAX50_FXSlot0;
@ -613,7 +623,7 @@ struct EAX30SOURCEPROPERTIES {
float flOcclusionLFRatio; // occlusion low-frequency level re. main control
float flOcclusionRoomRatio; // relative occlusion control for room effect
float flOcclusionDirectRatio; // relative occlusion control for direct path
long lExclusion; // main exlusion control (attenuation at high frequencies)
long lExclusion; // main exclusion control (attenuation at high frequencies)
float flExclusionLFRatio; // exclusion low-frequency level re. main control
long lOutsideVolumeHF; // outside sound cone level at high frequencies
float flDopplerFactor; // like DS3D flDopplerFactor but per source
@ -653,11 +663,11 @@ struct EAXSPEAKERLEVELPROPERTIES {
}; // EAXSPEAKERLEVELPROPERTIES
struct EAX40ACTIVEFXSLOTS {
GUID guidActiveFXSlots[EAX40_MAX_ACTIVE_FXSLOTS];
std::array<GUID,EAX40_MAX_ACTIVE_FXSLOTS> guidActiveFXSlots;
}; // EAX40ACTIVEFXSLOTS
struct EAX50ACTIVEFXSLOTS {
GUID guidActiveFXSlots[EAX50_MAX_ACTIVE_FXSLOTS];
std::array<GUID,EAX50_MAX_ACTIVE_FXSLOTS> guidActiveFXSlots;
}; // EAX50ACTIVEFXSLOTS
// Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property.
@ -836,6 +846,11 @@ struct EAXREVERBPROPERTIES {
float flLFReference; // reference low frequency
float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect
unsigned long ulFlags; // modifies the behavior of properties
DECL_EQOP(EAXREVERBPROPERTIES, ulEnvironment, flEnvironmentSize, flEnvironmentDiffusion, lRoom,
lRoomHF, lRoomLF, flDecayTime, flDecayHFRatio, flDecayLFRatio, lReflections,
flReflectionsDelay, vReflectionsPan, lReverb, flReverbDelay, vReverbPan, flEchoTime,
flEchoDepth, flModulationTime, flModulationDepth, flAirAbsorptionHF, flHFReference,
flLFReference, flRoomRolloffFactor, ulFlags)
}; // EAXREVERBPROPERTIES
@ -965,6 +980,7 @@ enum EAXAGCCOMPRESSOR_PROPERTY : unsigned int {
struct EAXAGCCOMPRESSORPROPERTIES {
unsigned long ulOnOff; // Switch Compressor on or off
DECL_EQOP(EAXAGCCOMPRESSORPROPERTIES, ulOnOff)
}; // EAXAGCCOMPRESSORPROPERTIES
@ -991,6 +1007,7 @@ struct EAXAUTOWAHPROPERTIES {
float flReleaseTime; // Release time (seconds)
long lResonance; // Resonance (mB)
long lPeakLevel; // Peak level (mB)
DECL_EQOP(EAXAUTOWAHPROPERTIES, flAttackTime, flReleaseTime, lResonance, lPeakLevel)
}; // EAXAUTOWAHPROPERTIES
@ -1038,6 +1055,7 @@ struct EAXCHORUSPROPERTIES {
float flDepth; // Depth (0 to 1)
float flFeedback; // Feedback (-1 to 1)
float flDelay; // Delay (seconds)
DECL_EQOP(EAXCHORUSPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay)
}; // EAXCHORUSPROPERTIES
@ -1086,6 +1104,7 @@ struct EAXDISTORTIONPROPERTIES {
float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz)
float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz)
float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz)
DECL_EQOP(EAXDISTORTIONPROPERTIES, flEdge, lGain, flLowPassCutOff, flEQCenter, flEQBandwidth)
}; // EAXDISTORTIONPROPERTIES
@ -1130,6 +1149,7 @@ struct EAXECHOPROPERTIES {
float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1)
float flFeedback; // Controls the duration of echo repetition (0 to 1)
float flSpread; // Controls the left-right spread of the echoes
DECL_EQOP(EAXECHOPROPERTIES, flDelay, flLRDelay, flDamping, flFeedback, flSpread)
}; // EAXECHOPROPERTIES
@ -1184,6 +1204,8 @@ struct EAXEQUALIZERPROPERTIES {
float flMid2Width; // (octaves)
long lHighGain; // (mB)
float flHighCutOff; // (Hz)
DECL_EQOP(EAXEQUALIZERPROPERTIES, lLowGain, flLowCutOff, lMid1Gain, flMid1Center, flMid1Width,
lMid2Gain, flMid2Center, flMid2Width, lHighGain, flHighCutOff)
}; // EAXEQUALIZERPROPERTIES
@ -1255,6 +1277,7 @@ struct EAXFLANGERPROPERTIES {
float flDepth; // Depth (0 to 1)
float flFeedback; // Feedback (0 to 1)
float flDelay; // Delay (seconds)
DECL_EQOP(EAXFLANGERPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay)
}; // EAXFLANGERPROPERTIES
@ -1305,6 +1328,7 @@ struct EAXFREQUENCYSHIFTERPROPERTIES {
float flFrequency; // (Hz)
unsigned long ulLeftDirection; // see enum above
unsigned long ulRightDirection; // see enum above
DECL_EQOP(EAXFREQUENCYSHIFTERPROPERTIES, flFrequency, ulLeftDirection, ulRightDirection)
}; // EAXFREQUENCYSHIFTERPROPERTIES
@ -1383,6 +1407,8 @@ struct EAXVOCALMORPHERPROPERTIES {
long lPhonemeBCoarseTuning; // (semitones)
unsigned long ulWaveform; // Waveform selector - see enum above
float flRate; // (Hz)
DECL_EQOP(EAXVOCALMORPHERPROPERTIES, ulPhonemeA, lPhonemeACoarseTuning, ulPhonemeB,
lPhonemeBCoarseTuning, ulWaveform, flRate)
}; // EAXVOCALMORPHERPROPERTIES
@ -1425,6 +1451,7 @@ enum EAXPITCHSHIFTER_PROPERTY : unsigned int {
struct EAXPITCHSHIFTERPROPERTIES {
long lCoarseTune; // Amount of pitch shift (semitones)
long lFineTune; // Amount of pitch shift (cents)
DECL_EQOP(EAXPITCHSHIFTERPROPERTIES, lCoarseTune, lFineTune)
}; // EAXPITCHSHIFTERPROPERTIES
@ -1460,6 +1487,7 @@ struct EAXRINGMODULATORPROPERTIES {
float flFrequency; // Frequency of modulation (Hz)
float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz)
unsigned long ulWaveform; // Waveform selector - see enum above
DECL_EQOP(EAXRINGMODULATORPROPERTIES, flFrequency, flHighPassCutOff, ulWaveform)
}; // EAXRINGMODULATORPROPERTIES
@ -1490,4 +1518,5 @@ using LPEAXGET = ALenum(AL_APIENTRY*)(
ALvoid* property_buffer,
ALuint property_size);
#undef DECL_EQOP
#endif // !EAX_API_INCLUDED

View file

@ -22,8 +22,7 @@ EaxCall::EaxCall(
ALuint property_source_id,
ALvoid* property_buffer,
ALuint property_size)
: mCallType{type}, mVersion{0}, mPropertySetId{EaxCallPropertySetId::none}
, mIsDeferred{(property_id & deferred_flag) != 0}
: mCallType{type}, mIsDeferred{(property_id & deferred_flag) != 0}
, mPropertyId{property_id & ~deferred_flag}, mPropertySourceId{property_source_id}
, mPropertyBuffer{property_buffer}, mPropertyBufferSize{property_size}
{

View file

@ -31,16 +31,16 @@ public:
ALvoid* property_buffer,
ALuint property_size);
bool is_get() const noexcept { return mCallType == EaxCallType::get; }
bool is_deferred() const noexcept { return mIsDeferred; }
int get_version() const noexcept { return mVersion; }
EaxCallPropertySetId get_property_set_id() const noexcept { return mPropertySetId; }
ALuint get_property_id() const noexcept { return mPropertyId; }
ALuint get_property_al_name() const noexcept { return mPropertySourceId; }
EaxFxSlotIndex get_fx_slot_index() const noexcept { return mFxSlotIndex; }
[[nodiscard]] auto is_get() const noexcept -> bool { return mCallType == EaxCallType::get; }
[[nodiscard]] auto is_deferred() const noexcept -> bool { return mIsDeferred; }
[[nodiscard]] auto get_version() const noexcept -> int { return mVersion; }
[[nodiscard]] auto get_property_set_id() const noexcept -> EaxCallPropertySetId { return mPropertySetId; }
[[nodiscard]] auto get_property_id() const noexcept -> ALuint { return mPropertyId; }
[[nodiscard]] auto get_property_al_name() const noexcept -> ALuint { return mPropertySourceId; }
[[nodiscard]] auto get_fx_slot_index() const noexcept -> EaxFxSlotIndex { return mFxSlotIndex; }
template<typename TException, typename TValue>
TValue& get_value() const
[[nodiscard]] auto get_value() const -> TValue&
{
if(mPropertyBufferSize < sizeof(TValue))
fail_too_small();
@ -49,32 +49,32 @@ public:
}
template<typename TValue>
al::span<TValue> get_values(size_t max_count) const
[[nodiscard]] auto get_values(size_t max_count) const -> al::span<TValue>
{
if(max_count == 0 || mPropertyBufferSize < sizeof(TValue))
fail_too_small();
const auto count = minz(mPropertyBufferSize / sizeof(TValue), max_count);
return al::as_span(static_cast<TValue*>(mPropertyBuffer), count);
const auto count = std::min(mPropertyBufferSize/sizeof(TValue), max_count);
return {static_cast<TValue*>(mPropertyBuffer), count};
}
template<typename TValue>
al::span<TValue> get_values() const
[[nodiscard]] auto get_values() const -> al::span<TValue>
{
return get_values<TValue>(~size_t{});
return get_values<TValue>(~0_uz);
}
template<typename TException, typename TValue>
void set_value(const TValue& value) const
auto set_value(const TValue& value) const -> void
{
get_value<TException, TValue>() = value;
}
private:
const EaxCallType mCallType;
int mVersion;
EaxFxSlotIndex mFxSlotIndex;
EaxCallPropertySetId mPropertySetId;
int mVersion{};
EaxFxSlotIndex mFxSlotIndex{};
EaxCallPropertySetId mPropertySetId{EaxCallPropertySetId::none};
bool mIsDeferred;
const ALuint mPropertyId;

View file

@ -4,60 +4,56 @@
#include <cassert>
#include <memory>
#include <variant>
#include "alnumeric.h"
#include "AL/al.h"
#include "AL/alext.h"
#include "core/effects/base.h"
#include "call.h"
struct EaxEffectErrorMessages
{
struct EaxEffectErrorMessages {
static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; }
static constexpr auto unknown_version() noexcept { return "Unknown version."; }
}; // EaxEffectErrorMessages
/* TODO: Use std::variant (C++17). */
enum class EaxEffectType {
None, Reverb, Chorus, Autowah, Compressor, Distortion, Echo, Equalizer, Flanger,
FrequencyShifter, Modulator, PitchShifter, VocalMorpher
};
struct EaxEffectProps {
EaxEffectType mType;
union {
EAXREVERBPROPERTIES mReverb;
EAXCHORUSPROPERTIES mChorus;
EAXAUTOWAHPROPERTIES mAutowah;
EAXAGCCOMPRESSORPROPERTIES mCompressor;
EAXDISTORTIONPROPERTIES mDistortion;
EAXECHOPROPERTIES mEcho;
EAXEQUALIZERPROPERTIES mEqualizer;
EAXFLANGERPROPERTIES mFlanger;
EAXFREQUENCYSHIFTERPROPERTIES mFrequencyShifter;
EAXRINGMODULATORPROPERTIES mModulator;
EAXPITCHSHIFTERPROPERTIES mPitchShifter;
EAXVOCALMORPHERPROPERTIES mVocalMorpher;
};
};
using EaxEffectProps = std::variant<std::monostate,
EAXREVERBPROPERTIES,
EAXCHORUSPROPERTIES,
EAXAUTOWAHPROPERTIES,
EAXAGCCOMPRESSORPROPERTIES,
EAXDISTORTIONPROPERTIES,
EAXECHOPROPERTIES,
EAXEQUALIZERPROPERTIES,
EAXFLANGERPROPERTIES,
EAXFREQUENCYSHIFTERPROPERTIES,
EAXRINGMODULATORPROPERTIES,
EAXPITCHSHIFTERPROPERTIES,
EAXVOCALMORPHERPROPERTIES>;
template<typename... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<typename... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
constexpr ALenum EnumFromEaxEffectType(const EaxEffectProps &props)
{
switch(props.mType)
{
case EaxEffectType::None: break;
case EaxEffectType::Reverb: return AL_EFFECT_EAXREVERB;
case EaxEffectType::Chorus: return AL_EFFECT_CHORUS;
case EaxEffectType::Autowah: return AL_EFFECT_AUTOWAH;
case EaxEffectType::Compressor: return AL_EFFECT_COMPRESSOR;
case EaxEffectType::Distortion: return AL_EFFECT_DISTORTION;
case EaxEffectType::Echo: return AL_EFFECT_ECHO;
case EaxEffectType::Equalizer: return AL_EFFECT_EQUALIZER;
case EaxEffectType::Flanger: return AL_EFFECT_FLANGER;
case EaxEffectType::FrequencyShifter: return AL_EFFECT_FREQUENCY_SHIFTER;
case EaxEffectType::Modulator: return AL_EFFECT_RING_MODULATOR;
case EaxEffectType::PitchShifter: return AL_EFFECT_PITCH_SHIFTER;
case EaxEffectType::VocalMorpher: return AL_EFFECT_VOCAL_MORPHER;
}
return AL_EFFECT_NULL;
return std::visit(overloaded{
[](const std::monostate&) noexcept { return AL_EFFECT_NULL; },
[](const EAXREVERBPROPERTIES&) noexcept { return AL_EFFECT_EAXREVERB; },
[](const EAXCHORUSPROPERTIES&) noexcept { return AL_EFFECT_CHORUS; },
[](const EAXAUTOWAHPROPERTIES&) noexcept { return AL_EFFECT_AUTOWAH; },
[](const EAXAGCCOMPRESSORPROPERTIES&) noexcept { return AL_EFFECT_COMPRESSOR; },
[](const EAXDISTORTIONPROPERTIES&) noexcept { return AL_EFFECT_DISTORTION; },
[](const EAXECHOPROPERTIES&) noexcept { return AL_EFFECT_ECHO; },
[](const EAXEQUALIZERPROPERTIES&) noexcept { return AL_EFFECT_EQUALIZER; },
[](const EAXFLANGERPROPERTIES&) noexcept { return AL_EFFECT_FLANGER; },
[](const EAXFREQUENCYSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_FREQUENCY_SHIFTER; },
[](const EAXRINGMODULATORPROPERTIES&) noexcept { return AL_EFFECT_RING_MODULATOR; },
[](const EAXPITCHSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_PITCH_SHIFTER; },
[](const EAXVOCALMORPHERPROPERTIES&) noexcept { return AL_EFFECT_VOCAL_MORPHER; }
}, props);
}
struct EaxReverbCommitter {
@ -105,7 +101,6 @@ struct EaxReverbCommitter {
bool commit(const EAX_REVERBPROPERTIES &props);
bool commit(const EAX20LISTENERPROPERTIES &props);
bool commit(const EAXREVERBPROPERTIES &props);
bool commit(const EaxEffectProps &props);
static void SetDefaults(EAX_REVERBPROPERTIES &props);
static void SetDefaults(EAX20LISTENERPROPERTIES &props);
@ -115,16 +110,13 @@ struct EaxReverbCommitter {
static void Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props);
static void Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props);
static void Get(const EaxCall &call, const EAXREVERBPROPERTIES &props);
static void Get(const EaxCall &call, const EaxEffectProps &props);
static void Set(const EaxCall &call, EAX_REVERBPROPERTIES &props);
static void Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props);
static void Set(const EaxCall &call, EAXREVERBPROPERTIES &props);
static void Set(const EaxCall &call, EaxEffectProps &props);
static void translate(const EAX_REVERBPROPERTIES& src, EaxEffectProps& dst) noexcept;
static void translate(const EAX20LISTENERPROPERTIES& src, EaxEffectProps& dst) noexcept;
static void translate(const EAXREVERBPROPERTIES& src, EaxEffectProps& dst) noexcept;
static void translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept;
static void translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept;
};
template<typename T>
@ -149,51 +141,137 @@ struct EaxCommitter {
[[noreturn]] static void fail(const char *message);
[[noreturn]] static void fail_unknown_property_id()
{ fail(EaxEffectErrorMessages::unknown_property_id()); }
bool commit(const EaxEffectProps &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EaxEffectProps &props);
static void Set(const EaxCall &call, EaxEffectProps &props);
};
struct EaxAutowahCommitter : public EaxCommitter<EaxAutowahCommitter> {
using EaxCommitter<EaxAutowahCommitter>::EaxCommitter;
bool commit(const EAXAUTOWAHPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props);
static void Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props);
};
struct EaxChorusCommitter : public EaxCommitter<EaxChorusCommitter> {
using EaxCommitter<EaxChorusCommitter>::EaxCommitter;
bool commit(const EAXCHORUSPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props);
static void Set(const EaxCall &call, EAXCHORUSPROPERTIES &props);
};
struct EaxCompressorCommitter : public EaxCommitter<EaxCompressorCommitter> {
using EaxCommitter<EaxCompressorCommitter>::EaxCommitter;
bool commit(const EAXAGCCOMPRESSORPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props);
static void Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props);
};
struct EaxDistortionCommitter : public EaxCommitter<EaxDistortionCommitter> {
using EaxCommitter<EaxDistortionCommitter>::EaxCommitter;
bool commit(const EAXDISTORTIONPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props);
static void Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props);
};
struct EaxEchoCommitter : public EaxCommitter<EaxEchoCommitter> {
using EaxCommitter<EaxEchoCommitter>::EaxCommitter;
bool commit(const EAXECHOPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXECHOPROPERTIES &props);
static void Set(const EaxCall &call, EAXECHOPROPERTIES &props);
};
struct EaxEqualizerCommitter : public EaxCommitter<EaxEqualizerCommitter> {
using EaxCommitter<EaxEqualizerCommitter>::EaxCommitter;
bool commit(const EAXEQUALIZERPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props);
static void Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props);
};
struct EaxFlangerCommitter : public EaxCommitter<EaxFlangerCommitter> {
using EaxCommitter<EaxFlangerCommitter>::EaxCommitter;
bool commit(const EAXFLANGERPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props);
static void Set(const EaxCall &call, EAXFLANGERPROPERTIES &props);
};
struct EaxFrequencyShifterCommitter : public EaxCommitter<EaxFrequencyShifterCommitter> {
using EaxCommitter<EaxFrequencyShifterCommitter>::EaxCommitter;
bool commit(const EAXFREQUENCYSHIFTERPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props);
static void Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props);
};
struct EaxModulatorCommitter : public EaxCommitter<EaxModulatorCommitter> {
using EaxCommitter<EaxModulatorCommitter>::EaxCommitter;
bool commit(const EAXRINGMODULATORPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props);
static void Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props);
};
struct EaxPitchShifterCommitter : public EaxCommitter<EaxPitchShifterCommitter> {
using EaxCommitter<EaxPitchShifterCommitter>::EaxCommitter;
bool commit(const EAXPITCHSHIFTERPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props);
static void Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props);
};
struct EaxVocalMorpherCommitter : public EaxCommitter<EaxVocalMorpherCommitter> {
using EaxCommitter<EaxVocalMorpherCommitter>::EaxCommitter;
bool commit(const EAXVOCALMORPHERPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props);
static void Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props);
};
struct EaxNullCommitter : public EaxCommitter<EaxNullCommitter> {
using EaxCommitter<EaxNullCommitter>::EaxCommitter;
bool commit(const std::monostate &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const std::monostate &props);
static void Set(const EaxCall &call, std::monostate &props);
};
template<typename T>
struct CommitterFromProps { };
template<> struct CommitterFromProps<std::monostate> { using type = EaxNullCommitter; };
template<> struct CommitterFromProps<EAXREVERBPROPERTIES> { using type = EaxReverbCommitter; };
template<> struct CommitterFromProps<EAXCHORUSPROPERTIES> { using type = EaxChorusCommitter; };
template<> struct CommitterFromProps<EAXAGCCOMPRESSORPROPERTIES> { using type = EaxCompressorCommitter; };
template<> struct CommitterFromProps<EAXAUTOWAHPROPERTIES> { using type = EaxAutowahCommitter; };
template<> struct CommitterFromProps<EAXDISTORTIONPROPERTIES> { using type = EaxDistortionCommitter; };
template<> struct CommitterFromProps<EAXECHOPROPERTIES> { using type = EaxEchoCommitter; };
template<> struct CommitterFromProps<EAXEQUALIZERPROPERTIES> { using type = EaxEqualizerCommitter; };
template<> struct CommitterFromProps<EAXFLANGERPROPERTIES> { using type = EaxFlangerCommitter; };
template<> struct CommitterFromProps<EAXFREQUENCYSHIFTERPROPERTIES> { using type = EaxFrequencyShifterCommitter; };
template<> struct CommitterFromProps<EAXRINGMODULATORPROPERTIES> { using type = EaxModulatorCommitter; };
template<> struct CommitterFromProps<EAXPITCHSHIFTERPROPERTIES> { using type = EaxPitchShifterCommitter; };
template<> struct CommitterFromProps<EAXVOCALMORPHERPROPERTIES> { using type = EaxVocalMorpherCommitter; };
template<typename T>
using CommitterFor = typename CommitterFromProps<std::remove_cv_t<std::remove_reference_t<T>>>::type;
class EaxEffect {
public:
@ -238,51 +316,39 @@ public:
State4 state5_{};
template<typename T, typename ...Args>
void call_set_defaults(Args&& ...args)
{ return T::SetDefaults(std::forward<Args>(args)...); }
void call_set_defaults(const ALenum altype, EaxEffectProps &props)
static void call_set_defaults(const ALenum altype, EaxEffectProps &props)
{
if(altype == AL_EFFECT_EAXREVERB)
return call_set_defaults<EaxReverbCommitter>(props);
if(altype == AL_EFFECT_CHORUS)
return call_set_defaults<EaxChorusCommitter>(props);
if(altype == AL_EFFECT_AUTOWAH)
return call_set_defaults<EaxAutowahCommitter>(props);
if(altype == AL_EFFECT_COMPRESSOR)
return call_set_defaults<EaxCompressorCommitter>(props);
if(altype == AL_EFFECT_DISTORTION)
return call_set_defaults<EaxDistortionCommitter>(props);
if(altype == AL_EFFECT_ECHO)
return call_set_defaults<EaxEchoCommitter>(props);
if(altype == AL_EFFECT_EQUALIZER)
return call_set_defaults<EaxEqualizerCommitter>(props);
if(altype == AL_EFFECT_FLANGER)
return call_set_defaults<EaxFlangerCommitter>(props);
if(altype == AL_EFFECT_FREQUENCY_SHIFTER)
return call_set_defaults<EaxFrequencyShifterCommitter>(props);
if(altype == AL_EFFECT_RING_MODULATOR)
return call_set_defaults<EaxModulatorCommitter>(props);
if(altype == AL_EFFECT_PITCH_SHIFTER)
return call_set_defaults<EaxPitchShifterCommitter>(props);
if(altype == AL_EFFECT_VOCAL_MORPHER)
return call_set_defaults<EaxVocalMorpherCommitter>(props);
return call_set_defaults<EaxNullCommitter>(props);
switch(altype)
{
case AL_EFFECT_EAXREVERB: return EaxReverbCommitter::SetDefaults(props);
case AL_EFFECT_CHORUS: return EaxChorusCommitter::SetDefaults(props);
case AL_EFFECT_AUTOWAH: return EaxAutowahCommitter::SetDefaults(props);
case AL_EFFECT_COMPRESSOR: return EaxCompressorCommitter::SetDefaults(props);
case AL_EFFECT_DISTORTION: return EaxDistortionCommitter::SetDefaults(props);
case AL_EFFECT_ECHO: return EaxEchoCommitter::SetDefaults(props);
case AL_EFFECT_EQUALIZER: return EaxEqualizerCommitter::SetDefaults(props);
case AL_EFFECT_FLANGER: return EaxFlangerCommitter::SetDefaults(props);
case AL_EFFECT_FREQUENCY_SHIFTER: return EaxFrequencyShifterCommitter::SetDefaults(props);
case AL_EFFECT_RING_MODULATOR: return EaxModulatorCommitter::SetDefaults(props);
case AL_EFFECT_PITCH_SHIFTER: return EaxPitchShifterCommitter::SetDefaults(props);
case AL_EFFECT_VOCAL_MORPHER: return EaxVocalMorpherCommitter::SetDefaults(props);
case AL_EFFECT_NULL: break;
}
return EaxNullCommitter::SetDefaults(props);
}
template<typename T>
void init()
{
call_set_defaults<EaxReverbCommitter>(state1_.d);
EaxReverbCommitter::SetDefaults(state1_.d);
state1_.i = state1_.d;
call_set_defaults<EaxReverbCommitter>(state2_.d);
EaxReverbCommitter::SetDefaults(state2_.d);
state2_.i = state2_.d;
call_set_defaults<EaxReverbCommitter>(state3_.d);
EaxReverbCommitter::SetDefaults(state3_.d);
state3_.i = state3_.d;
call_set_defaults<T>(state4_.d);
T::SetDefaults(state4_.d);
state4_.i = state4_.d;
call_set_defaults<T>(state5_.d);
T::SetDefaults(state5_.d);
state5_.i = state5_.d;
}
@ -290,9 +356,9 @@ public:
{
switch(eax_version)
{
case 1: call_set_defaults<EaxReverbCommitter>(state1_.d); break;
case 2: call_set_defaults<EaxReverbCommitter>(state2_.d); break;
case 3: call_set_defaults<EaxReverbCommitter>(state3_.d); break;
case 1: EaxReverbCommitter::SetDefaults(state1_.d); break;
case 2: EaxReverbCommitter::SetDefaults(state2_.d); break;
case 3: EaxReverbCommitter::SetDefaults(state3_.d); break;
case 4: call_set_defaults(altype, state4_.d); break;
case 5: call_set_defaults(altype, state5_.d); break;
}
@ -300,47 +366,20 @@ public:
}
#define EAXCALL(T, Callable, ...) \
if(T == EaxEffectType::Reverb) \
return Callable<EaxReverbCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Chorus) \
return Callable<EaxChorusCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Autowah) \
return Callable<EaxAutowahCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Compressor) \
return Callable<EaxCompressorCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Distortion) \
return Callable<EaxDistortionCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Echo) \
return Callable<EaxEchoCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Equalizer) \
return Callable<EaxEqualizerCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Flanger) \
return Callable<EaxFlangerCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::FrequencyShifter) \
return Callable<EaxFrequencyShifterCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Modulator) \
return Callable<EaxModulatorCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::PitchShifter) \
return Callable<EaxPitchShifterCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::VocalMorpher) \
return Callable<EaxVocalMorpherCommitter>(__VA_ARGS__); \
return Callable<EaxNullCommitter>(__VA_ARGS__)
template<typename T, typename ...Args>
static void call_set(Args&& ...args)
{ return T::Set(std::forward<Args>(args)...); }
static void call_set(const EaxCall &call, EaxEffectProps &props)
{ EAXCALL(props.mType, call_set, call, props); }
{
return std::visit([&](auto &arg)
{ return CommitterFor<decltype(arg)>::Set(call, arg); },
props);
}
void set(const EaxCall &call)
{
switch(call.get_version())
{
case 1: call_set<EaxReverbCommitter>(call, state1_.d); break;
case 2: call_set<EaxReverbCommitter>(call, state2_.d); break;
case 3: call_set<EaxReverbCommitter>(call, state3_.d); break;
case 1: EaxReverbCommitter::Set(call, state1_.d); break;
case 2: EaxReverbCommitter::Set(call, state2_.d); break;
case 3: EaxReverbCommitter::Set(call, state3_.d); break;
case 4: call_set(call, state4_.d); break;
case 5: call_set(call, state5_.d); break;
}
@ -348,32 +387,32 @@ public:
}
template<typename T, typename ...Args>
static void call_get(Args&& ...args)
{ return T::Get(std::forward<Args>(args)...); }
static void call_get(const EaxCall &call, const EaxEffectProps &props)
{ EAXCALL(props.mType, call_get, call, props); }
{
return std::visit([&](auto &arg)
{ return CommitterFor<decltype(arg)>::Get(call, arg); },
props);
}
void get(const EaxCall &call)
void get(const EaxCall &call) const
{
switch(call.get_version())
{
case 1: call_get<EaxReverbCommitter>(call, state1_.d); break;
case 2: call_get<EaxReverbCommitter>(call, state2_.d); break;
case 3: call_get<EaxReverbCommitter>(call, state3_.d); break;
case 1: EaxReverbCommitter::Get(call, state1_.d); break;
case 2: EaxReverbCommitter::Get(call, state2_.d); break;
case 3: EaxReverbCommitter::Get(call, state3_.d); break;
case 4: call_get(call, state4_.d); break;
case 5: call_get(call, state5_.d); break;
}
}
template<typename T, typename ...Args>
bool call_commit(Args&& ...args)
{ return T{props_, al_effect_props_}.commit(std::forward<Args>(args)...); }
bool call_commit(const EaxEffectProps &props)
{ EAXCALL(props.mType, call_commit, props); }
{
return std::visit([&](auto &arg)
{ return CommitterFor<decltype(arg)>{props_, al_effect_props_}.commit(arg); },
props);
}
bool commit(int eax_version)
{
@ -388,15 +427,15 @@ public:
{
case 1:
state1_.i = state1_.d;
ret |= call_commit<EaxReverbCommitter>(state1_.d);
ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state1_.d);
break;
case 2:
state2_.i = state2_.d;
ret |= call_commit<EaxReverbCommitter>(state2_.d);
ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state2_.d);
break;
case 3:
state3_.i = state3_.d;
ret |= call_commit<EaxReverbCommitter>(state3_.d);
ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state3_.d);
break;
case 4:
state4_.i = state4_.d;

View file

@ -6,54 +6,27 @@
#include <string>
EaxException::EaxException(const char *context, const char *message)
EaxException::EaxException(std::string_view context, std::string_view message)
: std::runtime_error{make_message(context, message)}
{
}
EaxException::~EaxException() = default;
std::string EaxException::make_message(const char *context, const char *message)
std::string EaxException::make_message(std::string_view context, std::string_view message)
{
const auto context_size = (context ? std::string::traits_type::length(context) : 0);
const auto has_contex = (context_size > 0);
const auto message_size = (message ? std::string::traits_type::length(message) : 0);
const auto has_message = (message_size > 0);
if (!has_contex && !has_message)
{
return std::string{};
}
static constexpr char left_prefix[] = "[";
const auto left_prefix_size = std::string::traits_type::length(left_prefix);
static constexpr char right_prefix[] = "] ";
const auto right_prefix_size = std::string::traits_type::length(right_prefix);
const auto what_size =
(
has_contex ?
left_prefix_size + context_size + right_prefix_size :
0) +
message_size +
1;
auto what = std::string{};
what.reserve(what_size);
if(context.empty() && message.empty())
return what;
if (has_contex)
what.reserve((!context.empty() ? context.size() + 3 : 0) + message.length() + 1);
if(!context.empty())
{
what.append(left_prefix, left_prefix_size);
what.append(context, context_size);
what.append(right_prefix, right_prefix_size);
}
if (has_message)
{
what.append(message, message_size);
what += "[";
what += context;
what += "] ";
}
what += message;
return what;
}

View file

@ -1,18 +1,23 @@
#ifndef EAX_EXCEPTION_INCLUDED
#define EAX_EXCEPTION_INCLUDED
#include <stdexcept>
#include <string>
#include <string_view>
class EaxException : public std::runtime_error {
static std::string make_message(const char *context, const char *message);
static std::string make_message(std::string_view context, std::string_view message);
public:
EaxException(const char *context, const char *message);
EaxException() = delete;
EaxException(const EaxException&) = default;
EaxException(EaxException&&) = default;
EaxException(std::string_view context, std::string_view message);
~EaxException() override;
}; // EaxException
auto operator=(const EaxException&) -> EaxException& = default;
auto operator=(EaxException&&) -> EaxException& = default;
};
#endif // !EAX_EXCEPTION_INCLUDED
#endif /* EAX_EXCEPTION_INCLUDED */

View file

@ -3,17 +3,16 @@
#include <cstddef>
#include <optional>
#include "aloptional.h"
#include "api.h"
using EaxFxSlotIndexValue = std::size_t;
class EaxFxSlotIndex : public al::optional<EaxFxSlotIndexValue>
{
class EaxFxSlotIndex : public std::optional<EaxFxSlotIndexValue> {
public:
using al::optional<EaxFxSlotIndexValue>::optional;
using std::optional<EaxFxSlotIndexValue>::optional;
EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; }
EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; }

View file

@ -6,13 +6,10 @@
#include "al/auxeffectslot.h"
#include "api.h"
#include "call.h"
#include "fx_slot_index.h"
class EaxFxSlots
{
class EaxFxSlots {
public:
void initialize(ALCcontext& al_context);
@ -25,11 +22,9 @@ public:
}
const ALeffectslot& get(
EaxFxSlotIndex index) const;
[[nodiscard]] auto get(EaxFxSlotIndex index) const -> const ALeffectslot&;
ALeffectslot& get(
EaxFxSlotIndex index);
[[nodiscard]] auto get(EaxFxSlotIndex index) -> ALeffectslot&;
private:
using Items = std::array<EaxAlEffectSlotUPtr, EAX_MAX_FXSLOTS>;
@ -39,8 +34,7 @@ private:
[[noreturn]]
static void fail(
const char* message);
static void fail(const char* message);
void initialize_fx_slots(ALCcontext& al_context);
}; // EaxFxSlots

View file

@ -1,21 +0,0 @@
#include "config.h"
#include "globals.h"
bool eax_g_is_enabled = true;
const char eax1_ext_name[] = "EAX";
const char eax2_ext_name[] = "EAX2.0";
const char eax3_ext_name[] = "EAX3.0";
const char eax4_ext_name[] = "EAX4.0";
const char eax5_ext_name[] = "EAX5.0";
const char eax_x_ram_ext_name[] = "EAX-RAM";
const char eax_eax_set_func_name[] = "EAXSet";
const char eax_eax_get_func_name[] = "EAXGet";
const char eax_eax_set_buffer_mode_func_name[] = "EAXSetBufferMode";
const char eax_eax_get_buffer_mode_func_name[] = "EAXGetBufferMode";

View file

@ -1,22 +1,6 @@
#ifndef EAX_GLOBALS_INCLUDED
#define EAX_GLOBALS_INCLUDED
inline bool eax_g_is_enabled{true};
extern bool eax_g_is_enabled;
extern const char eax1_ext_name[];
extern const char eax2_ext_name[];
extern const char eax3_ext_name[];
extern const char eax4_ext_name[];
extern const char eax5_ext_name[];
extern const char eax_x_ram_ext_name[];
extern const char eax_eax_set_func_name[];
extern const char eax_eax_get_func_name[];
extern const char eax_eax_set_buffer_mode_func_name[];
extern const char eax_eax_get_buffer_mode_func_name[];
#endif // !EAX_GLOBALS_INCLUDED
#endif /* EAX_GLOBALS_INCLUDED */

View file

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

View file

@ -4,8 +4,11 @@
#include <algorithm>
#include <cstdint>
#include <string>
#include <string_view>
#include <type_traits>
#include "opthelpers.h"
using EaxDirtyFlags = unsigned int;
struct EaxAlLowPassParam {
@ -13,16 +16,13 @@ struct EaxAlLowPassParam {
float gain_hf;
};
void eax_log_exception(const char *message) noexcept;
void eax_log_exception(std::string_view message) noexcept;
template<typename TException, typename TValue>
void eax_validate_range(
const char* value_name,
const TValue& value,
const TValue& min_value,
void eax_validate_range(std::string_view value_name, const TValue& value, const TValue& min_value,
const TValue& max_value)
{
if (value >= min_value && value <= max_value)
if(value >= min_value && value <= max_value) LIKELY
return;
const auto message =

View file

@ -24,15 +24,7 @@ constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC";
constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE";
constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE";
ALboolean AL_APIENTRY EAXSetBufferMode(
ALsizei n,
const ALuint* buffers,
ALint value);
ALenum AL_APIENTRY EAXGetBufferMode(
ALuint buffer,
ALint* pReserved);
ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint *buffers, ALint value) noexcept;
ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint *pReserved) noexcept;
#endif // !EAX_X_RAM_INCLUDED

View file

@ -28,9 +28,13 @@
#include <iterator>
#include <memory>
#include <mutex>
#include <new>
#include <numeric>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
#include "AL/al.h"
#include "AL/alc.h"
@ -38,26 +42,23 @@
#include "AL/efx-presets.h"
#include "AL/efx.h"
#include "al/effects/effects.h"
#include "albit.h"
#include "alc/context.h"
#include "alc/device.h"
#include "alc/effects/base.h"
#include "alc/inprogext.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "alstring.h"
#include "core/except.h"
#include "core/logging.h"
#include "direct_defs.h"
#include "error.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "eax/exception.h"
#endif // ALSOFT_EAX
const EffectList gEffectList[16]{
const std::array<EffectList,16> gEffectList{{
{ "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB },
{ "reverb", REVERB_EFFECT, AL_EFFECT_REVERB },
{ "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH },
@ -73,95 +74,74 @@ const EffectList gEffectList[16]{
{ "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER },
{ "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT },
{ "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE },
{ "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_REVERB_SOFT },
};
{ "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_SOFT },
}};
bool DisabledEffects[MAX_EFFECTS];
effect_exception::effect_exception(ALenum code, const char *msg, ...) : mErrorCode{code}
{
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
}
effect_exception::~effect_exception() = default;
namespace {
struct EffectPropsItem {
ALenum Type;
const EffectProps &DefaultProps;
const EffectVtable &Vtable;
};
constexpr EffectPropsItem EffectPropsList[] = {
{ AL_EFFECT_NULL, NullEffectProps, NullEffectVtable },
{ AL_EFFECT_EAXREVERB, ReverbEffectProps, ReverbEffectVtable },
{ AL_EFFECT_REVERB, StdReverbEffectProps, StdReverbEffectVtable },
{ AL_EFFECT_AUTOWAH, AutowahEffectProps, AutowahEffectVtable },
{ AL_EFFECT_CHORUS, ChorusEffectProps, ChorusEffectVtable },
{ AL_EFFECT_COMPRESSOR, CompressorEffectProps, CompressorEffectVtable },
{ AL_EFFECT_DISTORTION, DistortionEffectProps, DistortionEffectVtable },
{ AL_EFFECT_ECHO, EchoEffectProps, EchoEffectVtable },
{ AL_EFFECT_EQUALIZER, EqualizerEffectProps, EqualizerEffectVtable },
{ AL_EFFECT_FLANGER, FlangerEffectProps, FlangerEffectVtable },
{ AL_EFFECT_FREQUENCY_SHIFTER, FshifterEffectProps, FshifterEffectVtable },
{ AL_EFFECT_RING_MODULATOR, ModulatorEffectProps, ModulatorEffectVtable },
{ AL_EFFECT_PITCH_SHIFTER, PshifterEffectProps, PshifterEffectVtable },
{ AL_EFFECT_VOCAL_MORPHER, VmorpherEffectProps, VmorpherEffectVtable },
{ AL_EFFECT_DEDICATED_DIALOGUE, DedicatedEffectProps, DedicatedEffectVtable },
{ AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedEffectProps, DedicatedEffectVtable },
{ AL_EFFECT_CONVOLUTION_REVERB_SOFT, ConvolutionEffectProps, ConvolutionEffectVtable },
};
using SubListAllocator = al::allocator<std::array<ALeffect,64>>;
void ALeffect_setParami(ALeffect *effect, ALenum param, int value)
{ effect->vtab->setParami(&effect->Props, param, value); }
void ALeffect_setParamiv(ALeffect *effect, ALenum param, const int *values)
{ effect->vtab->setParamiv(&effect->Props, param, values); }
void ALeffect_setParamf(ALeffect *effect, ALenum param, float value)
{ effect->vtab->setParamf(&effect->Props, param, value); }
void ALeffect_setParamfv(ALeffect *effect, ALenum param, const float *values)
{ effect->vtab->setParamfv(&effect->Props, param, values); }
void ALeffect_getParami(const ALeffect *effect, ALenum param, int *value)
{ effect->vtab->getParami(&effect->Props, param, value); }
void ALeffect_getParamiv(const ALeffect *effect, ALenum param, int *values)
{ effect->vtab->getParamiv(&effect->Props, param, values); }
void ALeffect_getParamf(const ALeffect *effect, ALenum param, float *value)
{ effect->vtab->getParamf(&effect->Props, param, value); }
void ALeffect_getParamfv(const ALeffect *effect, ALenum param, float *values)
{ effect->vtab->getParamfv(&effect->Props, param, values); }
const EffectPropsItem *getEffectPropsItemByType(ALenum type)
constexpr auto GetDefaultProps(ALenum type) noexcept -> const EffectProps&
{
auto iter = std::find_if(std::begin(EffectPropsList), std::end(EffectPropsList),
[type](const EffectPropsItem &item) noexcept -> bool
{ return item.Type == type; });
return (iter != std::end(EffectPropsList)) ? al::to_address(iter) : nullptr;
switch(type)
{
case AL_EFFECT_NULL: return NullEffectProps;
case AL_EFFECT_EAXREVERB: return ReverbEffectProps;
case AL_EFFECT_REVERB: return StdReverbEffectProps;
case AL_EFFECT_AUTOWAH: return AutowahEffectProps;
case AL_EFFECT_CHORUS: return ChorusEffectProps;
case AL_EFFECT_COMPRESSOR: return CompressorEffectProps;
case AL_EFFECT_DISTORTION: return DistortionEffectProps;
case AL_EFFECT_ECHO: return EchoEffectProps;
case AL_EFFECT_EQUALIZER: return EqualizerEffectProps;
case AL_EFFECT_FLANGER: return FlangerEffectProps;
case AL_EFFECT_FREQUENCY_SHIFTER: return FshifterEffectProps;
case AL_EFFECT_RING_MODULATOR: return ModulatorEffectProps;
case AL_EFFECT_PITCH_SHIFTER: return PshifterEffectProps;
case AL_EFFECT_VOCAL_MORPHER: return VmorpherEffectProps;
case AL_EFFECT_DEDICATED_DIALOGUE: return DedicatedDialogEffectProps;
case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return DedicatedLfeEffectProps;
case AL_EFFECT_CONVOLUTION_SOFT: return ConvolutionEffectProps;
}
return NullEffectProps;
}
void InitEffectParams(ALeffect *effect, ALenum type)
void InitEffectParams(ALeffect *effect, ALenum type) noexcept
{
const EffectPropsItem *item{getEffectPropsItemByType(type)};
if(item)
switch(type)
{
effect->Props = item->DefaultProps;
effect->vtab = &item->Vtable;
}
else
{
effect->Props = EffectProps{};
effect->vtab = &NullEffectVtable;
case AL_EFFECT_NULL: effect->PropsVariant.emplace<NullEffectHandler>(); break;
case AL_EFFECT_EAXREVERB: effect->PropsVariant.emplace<ReverbEffectHandler>(); break;
case AL_EFFECT_REVERB: effect->PropsVariant.emplace<StdReverbEffectHandler>(); break;
case AL_EFFECT_AUTOWAH: effect->PropsVariant.emplace<AutowahEffectHandler>(); break;
case AL_EFFECT_CHORUS: effect->PropsVariant.emplace<ChorusEffectHandler>(); break;
case AL_EFFECT_COMPRESSOR: effect->PropsVariant.emplace<CompressorEffectHandler>(); break;
case AL_EFFECT_DISTORTION: effect->PropsVariant.emplace<DistortionEffectHandler>(); break;
case AL_EFFECT_ECHO: effect->PropsVariant.emplace<EchoEffectHandler>(); break;
case AL_EFFECT_EQUALIZER: effect->PropsVariant.emplace<EqualizerEffectHandler>(); break;
case AL_EFFECT_FLANGER: effect->PropsVariant.emplace<ChorusEffectHandler>(); break;
case AL_EFFECT_FREQUENCY_SHIFTER: effect->PropsVariant.emplace<FshifterEffectHandler>(); break;
case AL_EFFECT_RING_MODULATOR: effect->PropsVariant.emplace<ModulatorEffectHandler>(); break;
case AL_EFFECT_PITCH_SHIFTER: effect->PropsVariant.emplace<PshifterEffectHandler>(); break;
case AL_EFFECT_VOCAL_MORPHER: effect->PropsVariant.emplace<VmorpherEffectHandler>(); break;
case AL_EFFECT_DEDICATED_DIALOGUE:
effect->PropsVariant.emplace<DedicatedDialogEffectHandler>();
break;
case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT:
effect->PropsVariant.emplace<DedicatedLfeEffectHandler>();
break;
case AL_EFFECT_CONVOLUTION_SOFT:
effect->PropsVariant.emplace<ConvolutionEffectHandler>();
break;
}
effect->Props = GetDefaultProps(type);
effect->type = type;
}
bool EnsureEffects(ALCdevice *device, size_t needed)
{
size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), size_t{0},
auto EnsureEffects(ALCdevice *device, size_t needed) noexcept -> bool
try {
size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz,
[](size_t cur, const EffectSubList &sublist) noexcept -> size_t
{ return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })};
@ -170,21 +150,19 @@ bool EnsureEffects(ALCdevice *device, size_t needed)
if(device->EffectList.size() >= 1<<25) UNLIKELY
return false;
device->EffectList.emplace_back();
auto sublist = device->EffectList.end() - 1;
sublist->FreeMask = ~0_u64;
sublist->Effects = static_cast<ALeffect*>(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64));
if(!sublist->Effects) UNLIKELY
{
device->EffectList.pop_back();
return false;
}
count += 64;
EffectSubList sublist{};
sublist.FreeMask = ~0_u64;
sublist.Effects = SubListAllocator{}.allocate(1);
device->EffectList.emplace_back(std::move(sublist));
count += std::tuple_size_v<SubListAllocator::value_type>;
}
return true;
}
catch(...) {
return false;
}
ALeffect *AllocEffect(ALCdevice *device)
ALeffect *AllocEffect(ALCdevice *device) noexcept
{
auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(),
[](const EffectSubList &entry) noexcept -> bool
@ -193,7 +171,7 @@ ALeffect *AllocEffect(ALCdevice *device)
auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
ASSUME(slidx < 64);
ALeffect *effect{al::construct_at(sublist->Effects + slidx)};
ALeffect *effect{al::construct_at(al::to_address(sublist->Effects->begin() + slidx))};
InitEffectParams(effect, AL_EFFECT_NULL);
/* Add 1 to avoid effect ID 0. */
@ -206,16 +184,18 @@ ALeffect *AllocEffect(ALCdevice *device)
void FreeEffect(ALCdevice *device, ALeffect *effect)
{
device->mEffectNames.erase(effect->id);
const ALuint id{effect->id - 1};
const size_t lidx{id >> 6};
const ALuint slidx{id & 0x3f};
al::destroy_at(effect);
std::destroy_at(effect);
device->EffectList[lidx].FreeMask |= 1_u64 << slidx;
}
inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect*
{
const size_t lidx{(id-1) >> 6};
const ALuint slidx{(id-1) & 0x3f};
@ -225,320 +205,294 @@ inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
EffectSubList &sublist = device->EffectList[lidx];
if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
return nullptr;
return sublist.Effects + slidx;
return al::to_address(sublist.Effects->begin() + slidx);
}
} // namespace
AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
if(n < 0) UNLIKELY
context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
AL_API DECL_FUNC2(void, alGenEffects, ALsizei,n, ALuint*,effects)
FORCE_ALIGN void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) noexcept
try {
if(n < 0)
throw al::context_error{AL_INVALID_VALUE, "Generating %d effects", n};
if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
if(!EnsureEffects(device, static_cast<ALuint>(n)))
{
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, (n==1)?"":"s");
return;
}
std::lock_guard<std::mutex> effectlock{device->EffectLock};
if(n == 1) LIKELY
{
/* Special handling for the easy and normal case. */
ALeffect *effect{AllocEffect(device)};
effects[0] = effect->id;
}
else
{
/* Store the allocated buffer IDs in a separate local list, to avoid
* modifying the user storage in case of failure.
*/
al::vector<ALuint> ids;
ids.reserve(static_cast<ALuint>(n));
do {
ALeffect *effect{AllocEffect(device)};
ids.emplace_back(effect->id);
} while(--n);
std::copy(ids.cbegin(), ids.cend(), effects);
}
const al::span eids{effects, static_cast<ALuint>(n)};
if(!EnsureEffects(device, eids.size()))
throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n,
(n == 1) ? "" : "s"};
std::generate(eids.begin(), eids.end(), [device]{ return AllocEffect(device)->id; });
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
if(n < 0) UNLIKELY
context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects)
FORCE_ALIGN void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n,
const ALuint *effects) noexcept
try {
if(n < 0)
throw al::context_error{AL_INVALID_VALUE, "Deleting %d effects", n};
if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
/* First try to find any effects that are invalid. */
auto validate_effect = [device](const ALuint eid) -> bool
{ return !eid || LookupEffect(device, eid) != nullptr; };
const ALuint *effects_end = effects + n;
auto inveffect = std::find_if_not(effects, effects_end, validate_effect);
if(inveffect != effects_end) UNLIKELY
{
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect);
return;
}
const al::span eids{effects, static_cast<ALuint>(n)};
auto inveffect = std::find_if_not(eids.begin(), eids.end(), validate_effect);
if(inveffect != eids.end())
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", *inveffect};
/* All good. Delete non-0 effect IDs. */
auto delete_effect = [device](ALuint eid) -> void
{
ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr};
if(effect) FreeEffect(device, effect);
if(ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr})
FreeEffect(device, effect);
};
std::for_each(effects, effects_end, delete_effect);
std::for_each(eids.begin(), eids.end(), delete_effect);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect)
START_API_FUNC
AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect)
FORCE_ALIGN ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) noexcept
{
ContextRef context{GetContextRef()};
if(context) LIKELY
{
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
if(!effect || LookupEffect(device, effect))
return AL_TRUE;
}
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
if(!effect || LookupEffect(device, effect))
return AL_TRUE;
return AL_FALSE;
}
END_API_FUNC
AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
AL_API DECL_FUNC3(void, alEffecti, ALuint,effect, ALenum,param, ALint,value)
FORCE_ALIGN void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param,
ALint value) noexcept
try {
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else if(param == AL_EFFECT_TYPE)
if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
switch(param)
{
bool isOk{value == AL_EFFECT_NULL};
if(!isOk)
case AL_EFFECT_TYPE:
if(value != AL_EFFECT_NULL)
{
for(const EffectList &effectitem : gEffectList)
{
if(value == effectitem.val && !DisabledEffects[effectitem.type])
{
isOk = true;
break;
}
}
auto check_effect = [value](const EffectList &item) -> bool
{ return value == item.val && !DisabledEffects.test(item.type); };
if(!std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect))
throw al::context_error{AL_INVALID_VALUE, "Effect type 0x%04x not supported",
value};
}
if(isOk)
InitEffectParams(aleffect, value);
else
context->setError(AL_INVALID_VALUE, "Effect type 0x%04x not supported", value);
}
else try
{
/* Call the appropriate handler */
ALeffect_setParami(aleffect, param, value);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values)
START_API_FUNC
{
switch(param)
{
case AL_EFFECT_TYPE:
alEffecti(effect, param, values[0]);
InitEffectParams(aleffect, value);
return;
}
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
/* Call the appropriate handler */
std::visit([aleffect,param,value](auto &arg)
{
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.SetParami(std::get<PropType>(aleffect->Props), param, value);
}, aleffect->PropsVariant);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
AL_API DECL_FUNC3(void, alEffectiv, ALuint,effect, ALenum,param, const ALint*,values)
FORCE_ALIGN void AL_APIENTRY alEffectivDirect(ALCcontext *context, ALuint effect, ALenum param,
const ALint *values) noexcept
try {
switch(param)
{
case AL_EFFECT_TYPE:
alEffectiDirect(context, effect, param, *values);
return;
}
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
/* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg)
{
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.SetParamiv(std::get<PropType>(aleffect->Props), param, values);
}, aleffect->PropsVariant);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
AL_API DECL_FUNC3(void, alEffectf, ALuint,effect, ALenum,param, ALfloat,value)
FORCE_ALIGN void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param,
ALfloat value) noexcept
try {
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
/* Call the appropriate handler */
std::visit([aleffect,param,value](auto &arg)
{
/* Call the appropriate handler */
ALeffect_setParamiv(aleffect, param, values);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.SetParamf(std::get<PropType>(aleffect->Props), param, value);
}, aleffect->PropsVariant);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
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,
const ALfloat *values) noexcept
try {
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
/* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg)
{
/* Call the appropriate handler */
ALeffect_setParamf(aleffect, param, value);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.SetParamfv(std::get<PropType>(aleffect->Props), param, values);
}, aleffect->PropsVariant);
}
END_API_FUNC
AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
{
/* Call the appropriate handler */
ALeffect_setParamfv(aleffect, param, values);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
AL_API DECL_FUNC3(void, alGetEffecti, ALuint,effect, ALenum,param, ALint*,value)
FORCE_ALIGN void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param,
ALint *value) noexcept
try {
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else if(param == AL_EFFECT_TYPE)
if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
switch(param)
{
case AL_EFFECT_TYPE:
*value = aleffect->type;
else try
{
/* Call the appropriate handler */
ALeffect_getParami(aleffect, param, value);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values)
START_API_FUNC
{
switch(param)
{
case AL_EFFECT_TYPE:
alGetEffecti(effect, param, values);
return;
}
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
/* Call the appropriate handler */
std::visit([aleffect,param,value](auto &arg)
{
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.GetParami(std::get<PropType>(aleffect->Props), param, value);
}, aleffect->PropsVariant);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
AL_API DECL_FUNC3(void, alGetEffectiv, ALuint,effect, ALenum,param, ALint*,values)
FORCE_ALIGN void AL_APIENTRY alGetEffectivDirect(ALCcontext *context, ALuint effect, ALenum param,
ALint *values) noexcept
try {
switch(param)
{
case AL_EFFECT_TYPE:
alGetEffectiDirect(context, effect, param, values);
return;
}
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
/* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg)
{
/* Call the appropriate handler */
ALeffect_getParamiv(aleffect, param, values);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.GetParamiv(std::get<PropType>(aleffect->Props), param, values);
}, aleffect->PropsVariant);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
AL_API DECL_FUNC3(void, alGetEffectf, ALuint,effect, ALenum,param, ALfloat*,value)
FORCE_ALIGN void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param,
ALfloat *value) noexcept
try {
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
/* Call the appropriate handler */
std::visit([aleffect,param,value](auto &arg)
{
/* Call the appropriate handler */
ALeffect_getParamf(aleffect, param, value);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.GetParamf(std::get<PropType>(aleffect->Props), param, value);
}, aleffect->PropsVariant);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
AL_API DECL_FUNC3(void, alGetEffectfv, ALuint,effect, ALenum,param, ALfloat*,values)
FORCE_ALIGN void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param,
ALfloat *values) noexcept
try {
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
if(!aleffect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
/* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg)
{
/* Call the appropriate handler */
ALeffect_getParamfv(aleffect, param, values);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.GetParamfv(std::get<PropType>(aleffect->Props), param, values);
}, aleffect->PropsVariant);
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
void InitEffect(ALeffect *effect)
@ -546,26 +500,43 @@ void InitEffect(ALeffect *effect)
InitEffectParams(effect, AL_EFFECT_NULL);
}
void ALeffect::SetName(ALCcontext* context, ALuint id, std::string_view name)
{
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> effectlock{device->EffectLock};
auto effect = LookupEffect(device, id);
if(!effect)
throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", id};
device->mEffectNames.insert_or_assign(id, name);
}
EffectSubList::~EffectSubList()
{
if(!Effects)
return;
uint64_t usemask{~FreeMask};
while(usemask)
{
const int idx{al::countr_zero(usemask)};
al::destroy_at(Effects+idx);
std::destroy_at(al::to_address(Effects->begin()+idx));
usemask &= ~(1_u64 << idx);
}
FreeMask = ~usemask;
al_free(Effects);
SubListAllocator{}.deallocate(Effects, 1);
Effects = nullptr;
}
#define DECL(x) { #x, EFX_REVERB_PRESET_##x }
static const struct {
const char name[32];
struct EffectPreset {
const char name[32]; /* NOLINT(*-avoid-c-arrays) */
EFXEAXREVERBPROPERTIES props;
} reverblist[] = {
};
#define DECL(x) EffectPreset{#x, EFX_REVERB_PRESET_##x}
static constexpr std::array reverblist{
DECL(GENERIC),
DECL(PADDEDCELL),
DECL(ROOM),
@ -695,61 +666,62 @@ static const struct {
};
#undef DECL
void LoadReverbPreset(const char *name, ALeffect *effect)
void LoadReverbPreset(const std::string_view name, ALeffect *effect)
{
if(al::strcasecmp(name, "NONE") == 0)
using namespace std::string_view_literals;
if(al::case_compare(name, "NONE"sv) == 0)
{
InitEffectParams(effect, AL_EFFECT_NULL);
TRACE("Loading reverb '%s'\n", "NONE");
return;
}
if(!DisabledEffects[EAXREVERB_EFFECT])
if(!DisabledEffects.test(EAXREVERB_EFFECT))
InitEffectParams(effect, AL_EFFECT_EAXREVERB);
else if(!DisabledEffects[REVERB_EFFECT])
else if(!DisabledEffects.test(REVERB_EFFECT))
InitEffectParams(effect, AL_EFFECT_REVERB);
else
InitEffectParams(effect, AL_EFFECT_NULL);
for(const auto &reverbitem : reverblist)
{
const EFXEAXREVERBPROPERTIES *props;
if(al::strcasecmp(name, reverbitem.name) != 0)
if(al::case_compare(name, std::data(reverbitem.name)) != 0)
continue;
TRACE("Loading reverb '%s'\n", reverbitem.name);
props = &reverbitem.props;
effect->Props.Reverb.Density = props->flDensity;
effect->Props.Reverb.Diffusion = props->flDiffusion;
effect->Props.Reverb.Gain = props->flGain;
effect->Props.Reverb.GainHF = props->flGainHF;
effect->Props.Reverb.GainLF = props->flGainLF;
effect->Props.Reverb.DecayTime = props->flDecayTime;
effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio;
effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio;
effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain;
effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay;
effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0];
effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1];
effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2];
effect->Props.Reverb.LateReverbGain = props->flLateReverbGain;
effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay;
effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0];
effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1];
effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2];
effect->Props.Reverb.EchoTime = props->flEchoTime;
effect->Props.Reverb.EchoDepth = props->flEchoDepth;
effect->Props.Reverb.ModulationTime = props->flModulationTime;
effect->Props.Reverb.ModulationDepth = props->flModulationDepth;
effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF;
effect->Props.Reverb.HFReference = props->flHFReference;
effect->Props.Reverb.LFReference = props->flLFReference;
effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor;
effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE;
TRACE("Loading reverb '%s'\n", std::data(reverbitem.name));
const auto &props = reverbitem.props;
auto &dst = std::get<ReverbProps>(effect->Props);
dst.Density = props.flDensity;
dst.Diffusion = props.flDiffusion;
dst.Gain = props.flGain;
dst.GainHF = props.flGainHF;
dst.GainLF = props.flGainLF;
dst.DecayTime = props.flDecayTime;
dst.DecayHFRatio = props.flDecayHFRatio;
dst.DecayLFRatio = props.flDecayLFRatio;
dst.ReflectionsGain = props.flReflectionsGain;
dst.ReflectionsDelay = props.flReflectionsDelay;
dst.ReflectionsPan[0] = props.flReflectionsPan[0];
dst.ReflectionsPan[1] = props.flReflectionsPan[1];
dst.ReflectionsPan[2] = props.flReflectionsPan[2];
dst.LateReverbGain = props.flLateReverbGain;
dst.LateReverbDelay = props.flLateReverbDelay;
dst.LateReverbPan[0] = props.flLateReverbPan[0];
dst.LateReverbPan[1] = props.flLateReverbPan[1];
dst.LateReverbPan[2] = props.flLateReverbPan[2];
dst.EchoTime = props.flEchoTime;
dst.EchoDepth = props.flEchoDepth;
dst.ModulationTime = props.flModulationTime;
dst.ModulationDepth = props.flModulationDepth;
dst.AirAbsorptionGainHF = props.flAirAbsorptionGainHF;
dst.HFReference = props.flHFReference;
dst.LFReference = props.flLFReference;
dst.RoomRolloffFactor = props.flRoomRolloffFactor;
dst.DecayHFLimit = props.iDecayHFLimit ? AL_TRUE : AL_FALSE;
return;
}
WARN("Reverb preset '%s' not found\n", name);
WARN("Reverb preset '%.*s' not found\n", al::sizei(name), name.data());
}
bool IsValidEffectType(ALenum type) noexcept
@ -757,10 +729,7 @@ bool IsValidEffectType(ALenum type) noexcept
if(type == AL_EFFECT_NULL)
return true;
for(const auto &effect_item : gEffectList)
{
if(type == effect_item.val && !DisabledEffects[effect_item.type])
return true;
}
return false;
auto check_effect = [type](const EffectList &item) noexcept -> bool
{ return type == item.val && !DisabledEffects.test(item.type); };
return std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect);
}

View file

@ -1,11 +1,20 @@
#ifndef AL_EFFECT_H
#define AL_EFFECT_H
#include <array>
#include <bitset>
#include <cstdint>
#include <string_view>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "al/effects/effects.h"
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "core/effects/base.h"
#include "effects/effects.h"
enum {
@ -27,36 +36,55 @@ enum {
MAX_EFFECTS
};
extern bool DisabledEffects[MAX_EFFECTS];
extern float ReverbBoost;
inline std::bitset<MAX_EFFECTS> DisabledEffects;
struct EffectList {
const char name[16];
int type;
const char name[16]; /* NOLINT(*-avoid-c-arrays) */
ALuint type;
ALenum val;
};
extern const EffectList gEffectList[16];
extern const std::array<EffectList,16> gEffectList;
using EffectHandlerVariant = std::variant<NullEffectHandler,ReverbEffectHandler,
StdReverbEffectHandler,AutowahEffectHandler,ChorusEffectHandler,CompressorEffectHandler,
DistortionEffectHandler,EchoEffectHandler,EqualizerEffectHandler,FlangerEffectHandler,
FshifterEffectHandler,ModulatorEffectHandler,PshifterEffectHandler,VmorpherEffectHandler,
DedicatedDialogEffectHandler,DedicatedLfeEffectHandler,ConvolutionEffectHandler>;
struct ALeffect {
// Effect type (AL_EFFECT_NULL, ...)
ALenum type{AL_EFFECT_NULL};
EffectHandlerVariant PropsVariant;
EffectProps Props{};
const EffectVtable *vtab{nullptr};
/* Self ID */
ALuint id{0u};
DISABLE_ALLOC()
static void SetName(ALCcontext *context, ALuint id, std::string_view name);
DISABLE_ALLOC
};
void InitEffect(ALeffect *effect);
void LoadReverbPreset(const char *name, ALeffect *effect);
void LoadReverbPreset(const std::string_view name, ALeffect *effect);
bool IsValidEffectType(ALenum type) noexcept;
struct EffectSubList {
uint64_t FreeMask{~0_u64};
gsl::owner<std::array<ALeffect,64>*> Effects{nullptr}; /* 64 */
EffectSubList() noexcept = default;
EffectSubList(const EffectSubList&) = delete;
EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects}
{ rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; }
~EffectSubList();
EffectSubList& operator=(const EffectSubList&) = delete;
EffectSubList& operator=(EffectSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; }
};
#endif

View file

@ -13,6 +13,7 @@
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
@ -20,100 +21,87 @@
namespace {
void Autowah_setParamf(EffectProps *props, ALenum param, float val)
constexpr EffectProps genDefaultProps() noexcept
{
AutowahProps props{};
props.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
props.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
props.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
props.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
return props;
}
} // namespace
const EffectProps AutowahEffectProps{genDefaultProps()};
void AutowahEffectHandler::SetParami(AutowahProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
void AutowahEffectHandler::SetParamiv(AutowahProps&, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
param};
}
void AutowahEffectHandler::SetParamf(AutowahProps &props, ALenum param, float val)
{
switch(param)
{
case AL_AUTOWAH_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"};
props->Autowah.AttackTime = val;
props.AttackTime = val;
break;
case AL_AUTOWAH_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"};
props->Autowah.ReleaseTime = val;
props.ReleaseTime = val;
break;
case AL_AUTOWAH_RESONANCE:
if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
throw effect_exception{AL_INVALID_VALUE, "Autowah resonance out of range"};
props->Autowah.Resonance = val;
props.Resonance = val;
break;
case AL_AUTOWAH_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"};
props->Autowah.PeakGain = val;
props.PeakGain = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param};
}
}
void Autowah_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Autowah_setParamf(props, param, vals[0]); }
void AutowahEffectHandler::SetParamfv(AutowahProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void Autowah_setParami(EffectProps*, ALenum param, int)
void AutowahEffectHandler::GetParami(const AutowahProps&, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
void Autowah_setParamiv(EffectProps*, ALenum param, const int*)
void AutowahEffectHandler::GetParamiv(const AutowahProps&, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
param};
}
void Autowah_getParamf(const EffectProps *props, ALenum param, float *val)
void AutowahEffectHandler::GetParamf(const AutowahProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_AUTOWAH_ATTACK_TIME:
*val = props->Autowah.AttackTime;
break;
case AL_AUTOWAH_RELEASE_TIME:
*val = props->Autowah.ReleaseTime;
break;
case AL_AUTOWAH_RESONANCE:
*val = props->Autowah.Resonance;
break;
case AL_AUTOWAH_PEAK_GAIN:
*val = props->Autowah.PeakGain;
break;
case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; break;
case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; break;
case AL_AUTOWAH_RESONANCE: *val = props.Resonance; break;
case AL_AUTOWAH_PEAK_GAIN: *val = props.PeakGain; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param};
}
}
void Autowah_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Autowah_getParamf(props, param, vals); }
void Autowah_getParami(const EffectProps*, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
void Autowah_getParamiv(const EffectProps*, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
param};
}
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Autowah);
const EffectProps AutowahEffectProps{genDefaultProps()};
void AutowahEffectHandler::GetParamfv(const AutowahProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }
#ifdef ALSOFT_EAX
namespace {
@ -189,62 +177,62 @@ template<>
throw Exception{message};
}
template<>
bool AutowahCommitter::commit(const EaxEffectProps &props)
bool EaxAutowahCommitter::commit(const EAXAUTOWAHPROPERTIES &props)
{
if(props.mType == mEaxProps.mType
&& mEaxProps.mAutowah.flAttackTime == props.mAutowah.flAttackTime
&& mEaxProps.mAutowah.flReleaseTime == props.mAutowah.flReleaseTime
&& mEaxProps.mAutowah.lResonance == props.mAutowah.lResonance
&& mEaxProps.mAutowah.lPeakLevel == props.mAutowah.lPeakLevel)
if(auto *cur = std::get_if<EAXAUTOWAHPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
mAlProps.Autowah.AttackTime = props.mAutowah.flAttackTime;
mAlProps.Autowah.ReleaseTime = props.mAutowah.flReleaseTime;
mAlProps.Autowah.Resonance = level_mb_to_gain(static_cast<float>(props.mAutowah.lResonance));
mAlProps.Autowah.PeakGain = level_mb_to_gain(static_cast<float>(props.mAutowah.lPeakLevel));
mAlProps = [&]{
AutowahProps ret{};
ret.AttackTime = props.flAttackTime;
ret.ReleaseTime = props.flReleaseTime;
ret.Resonance = level_mb_to_gain(static_cast<float>(props.lResonance));
ret.PeakGain = level_mb_to_gain(static_cast<float>(props.lPeakLevel));
return ret;
}();
return true;
}
template<>
void AutowahCommitter::SetDefaults(EaxEffectProps &props)
void EaxAutowahCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Autowah;
props.mAutowah.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME;
props.mAutowah.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME;
props.mAutowah.lResonance = EAXAUTOWAH_DEFAULTRESONANCE;
props.mAutowah.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL;
static constexpr EAXAUTOWAHPROPERTIES defprops{[]
{
EAXAUTOWAHPROPERTIES ret{};
ret.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME;
ret.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME;
ret.lResonance = EAXAUTOWAH_DEFAULTRESONANCE;
ret.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL;
return ret;
}()};
props = defprops;
}
template<>
void AutowahCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxAutowahCommitter::Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXAUTOWAH_NONE: break;
case EAXAUTOWAH_ALLPARAMETERS: call.set_value<Exception>(props.mAutowah); break;
case EAXAUTOWAH_ATTACKTIME: call.set_value<Exception>(props.mAutowah.flAttackTime); break;
case EAXAUTOWAH_RELEASETIME: call.set_value<Exception>(props.mAutowah.flReleaseTime); break;
case EAXAUTOWAH_RESONANCE: call.set_value<Exception>(props.mAutowah.lResonance); break;
case EAXAUTOWAH_PEAKLEVEL: call.set_value<Exception>(props.mAutowah.lPeakLevel); break;
case EAXAUTOWAH_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXAUTOWAH_ATTACKTIME: call.set_value<Exception>(props.flAttackTime); break;
case EAXAUTOWAH_RELEASETIME: call.set_value<Exception>(props.flReleaseTime); break;
case EAXAUTOWAH_RESONANCE: call.set_value<Exception>(props.lResonance); break;
case EAXAUTOWAH_PEAKLEVEL: call.set_value<Exception>(props.lPeakLevel); break;
default: fail_unknown_property_id();
}
}
template<>
void AutowahCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxAutowahCommitter::Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXAUTOWAH_NONE: break;
case EAXAUTOWAH_ALLPARAMETERS: defer<AllValidator>(call, props.mAutowah); break;
case EAXAUTOWAH_ATTACKTIME: defer<AttackTimeValidator>(call, props.mAutowah.flAttackTime); break;
case EAXAUTOWAH_RELEASETIME: defer<ReleaseTimeValidator>(call, props.mAutowah.flReleaseTime); break;
case EAXAUTOWAH_RESONANCE: defer<ResonanceValidator>(call, props.mAutowah.lResonance); break;
case EAXAUTOWAH_PEAKLEVEL: defer<PeakLevelValidator>(call, props.mAutowah.lPeakLevel); break;
case EAXAUTOWAH_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXAUTOWAH_ATTACKTIME: defer<AttackTimeValidator>(call, props.flAttackTime); break;
case EAXAUTOWAH_RELEASETIME: defer<ReleaseTimeValidator>(call, props.flReleaseTime); break;
case EAXAUTOWAH_RESONANCE: defer<ResonanceValidator>(call, props.lResonance); break;
case EAXAUTOWAH_PEAKLEVEL: defer<PeakLevelValidator>(call, props.lPeakLevel); break;
default: fail_unknown_property_id();
}
}

View file

@ -1,19 +1,17 @@
#include "config.h"
#include <optional>
#include <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "core/logging.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
@ -27,16 +25,16 @@ static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too sm
static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch");
static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch");
inline al::optional<ChorusWaveform> WaveformFromEnum(ALenum type)
constexpr std::optional<ChorusWaveform> WaveformFromEnum(ALenum type) noexcept
{
switch(type)
{
case AL_CHORUS_WAVEFORM_SINUSOID: return ChorusWaveform::Sinusoid;
case AL_CHORUS_WAVEFORM_TRIANGLE: return ChorusWaveform::Triangle;
}
return al::nullopt;
return std::nullopt;
}
inline ALenum EnumFromWaveform(ChorusWaveform type)
constexpr ALenum EnumFromWaveform(ChorusWaveform type)
{
switch(type)
{
@ -46,13 +44,41 @@ inline ALenum EnumFromWaveform(ChorusWaveform type)
throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast<int>(type))};
}
void Chorus_setParami(EffectProps *props, ALenum param, int val)
constexpr EffectProps genDefaultChorusProps() noexcept
{
ChorusProps props{};
props.Waveform = WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM).value();
props.Phase = AL_CHORUS_DEFAULT_PHASE;
props.Rate = AL_CHORUS_DEFAULT_RATE;
props.Depth = AL_CHORUS_DEFAULT_DEPTH;
props.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
props.Delay = AL_CHORUS_DEFAULT_DELAY;
return props;
}
constexpr EffectProps genDefaultFlangerProps() noexcept
{
ChorusProps props{};
props.Waveform = WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM).value();
props.Phase = AL_FLANGER_DEFAULT_PHASE;
props.Rate = AL_FLANGER_DEFAULT_RATE;
props.Depth = AL_FLANGER_DEFAULT_DEPTH;
props.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
props.Delay = AL_FLANGER_DEFAULT_DELAY;
return props;
}
} // namespace
const EffectProps ChorusEffectProps{genDefaultChorusProps()};
void ChorusEffectHandler::SetParami(ChorusProps &props, ALenum param, int val)
{
switch(param)
{
case AL_CHORUS_WAVEFORM:
if(auto formopt = WaveformFromEnum(val))
props->Chorus.Waveform = *formopt;
props.Waveform = *formopt;
else
throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val};
break;
@ -60,115 +86,89 @@ void Chorus_setParami(EffectProps *props, ALenum param, int val)
case AL_CHORUS_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};
props->Chorus.Phase = val;
props.Phase = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param};
}
}
void Chorus_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Chorus_setParami(props, param, vals[0]); }
void Chorus_setParamf(EffectProps *props, ALenum param, float val)
void ChorusEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); }
void ChorusEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val)
{
switch(param)
{
case AL_CHORUS_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};
props->Chorus.Rate = val;
props.Rate = val;
break;
case AL_CHORUS_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};
props->Chorus.Depth = val;
props.Depth = val;
break;
case AL_CHORUS_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};
props->Chorus.Feedback = val;
props.Feedback = val;
break;
case AL_CHORUS_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};
props->Chorus.Delay = val;
props.Delay = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param};
}
}
void Chorus_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Chorus_setParamf(props, param, vals[0]); }
void ChorusEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void Chorus_getParami(const EffectProps *props, ALenum param, int *val)
void ChorusEffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val)
{
switch(param)
{
case AL_CHORUS_WAVEFORM:
*val = EnumFromWaveform(props->Chorus.Waveform);
break;
case AL_CHORUS_PHASE:
*val = props->Chorus.Phase;
break;
case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break;
case AL_CHORUS_PHASE: *val = props.Phase; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param};
}
}
void Chorus_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Chorus_getParami(props, param, vals); }
void Chorus_getParamf(const EffectProps *props, ALenum param, float *val)
void ChorusEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); }
void ChorusEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_CHORUS_RATE:
*val = props->Chorus.Rate;
break;
case AL_CHORUS_DEPTH:
*val = props->Chorus.Depth;
break;
case AL_CHORUS_FEEDBACK:
*val = props->Chorus.Feedback;
break;
case AL_CHORUS_DELAY:
*val = props->Chorus.Delay;
break;
case AL_CHORUS_RATE: *val = props.Rate; break;
case AL_CHORUS_DEPTH: *val = props.Depth; break;
case AL_CHORUS_FEEDBACK: *val = props.Feedback; break;
case AL_CHORUS_DELAY: *val = props.Delay; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param};
}
}
void Chorus_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Chorus_getParamf(props, param, vals); }
EffectProps genDefaultChorusProps() noexcept
{
EffectProps props{};
props.Chorus.Waveform = *WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM);
props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE;
props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE;
props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH;
props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY;
return props;
}
void ChorusEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }
void Flanger_setParami(EffectProps *props, ALenum param, int val)
const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
void FlangerEffectHandler::SetParami(ChorusProps &props, ALenum param, int val)
{
switch(param)
{
case AL_FLANGER_WAVEFORM:
if(auto formopt = WaveformFromEnum(val))
props->Chorus.Waveform = *formopt;
props.Waveform = *formopt;
else
throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val};
break;
@ -176,127 +176,87 @@ void Flanger_setParami(EffectProps *props, ALenum param, int val)
case AL_FLANGER_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};
props->Chorus.Phase = val;
props.Phase = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param};
}
}
void Flanger_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Flanger_setParami(props, param, vals[0]); }
void Flanger_setParamf(EffectProps *props, ALenum param, float val)
void FlangerEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); }
void FlangerEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val)
{
switch(param)
{
case AL_FLANGER_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};
props->Chorus.Rate = val;
props.Rate = val;
break;
case AL_FLANGER_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};
props->Chorus.Depth = val;
props.Depth = val;
break;
case AL_FLANGER_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};
props->Chorus.Feedback = val;
props.Feedback = val;
break;
case AL_FLANGER_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};
props->Chorus.Delay = val;
props.Delay = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param};
}
}
void Flanger_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Flanger_setParamf(props, param, vals[0]); }
void FlangerEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void Flanger_getParami(const EffectProps *props, ALenum param, int *val)
void FlangerEffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val)
{
switch(param)
{
case AL_FLANGER_WAVEFORM:
*val = EnumFromWaveform(props->Chorus.Waveform);
break;
case AL_FLANGER_PHASE:
*val = props->Chorus.Phase;
break;
case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break;
case AL_FLANGER_PHASE: *val = props.Phase; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param};
}
}
void Flanger_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Flanger_getParami(props, param, vals); }
void Flanger_getParamf(const EffectProps *props, ALenum param, float *val)
void FlangerEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); }
void FlangerEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_FLANGER_RATE:
*val = props->Chorus.Rate;
break;
case AL_FLANGER_DEPTH:
*val = props->Chorus.Depth;
break;
case AL_FLANGER_FEEDBACK:
*val = props->Chorus.Feedback;
break;
case AL_FLANGER_DELAY:
*val = props->Chorus.Delay;
break;
case AL_FLANGER_RATE: *val = props.Rate; break;
case AL_FLANGER_DEPTH: *val = props.Depth; break;
case AL_FLANGER_FEEDBACK: *val = props.Feedback; break;
case AL_FLANGER_DELAY: *val = props.Delay; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param};
}
}
void Flanger_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Flanger_getParamf(props, param, vals); }
EffectProps genDefaultFlangerProps() noexcept
{
EffectProps props{};
props.Chorus.Waveform = *WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM);
props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE;
props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE;
props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH;
props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY;
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Chorus);
const EffectProps ChorusEffectProps{genDefaultChorusProps()};
DEFINE_ALEFFECT_VTABLE(Flanger);
const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
void FlangerEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }
#ifdef ALSOFT_EAX
namespace {
struct EaxChorusTraits {
using Props = EAXCHORUSPROPERTIES;
using EaxProps = EAXCHORUSPROPERTIES;
using Committer = EaxChorusCommitter;
static constexpr auto Field = &EaxEffectProps::mChorus;
static constexpr auto eax_effect_type() { return EaxEffectType::Chorus; }
static constexpr auto efx_effect() { return AL_EFFECT_CHORUS; }
static constexpr auto eax_none_param_id() { return EAXCHORUS_NONE; }
@ -359,11 +319,9 @@ struct EaxChorusTraits {
}; // EaxChorusTraits
struct EaxFlangerTraits {
using Props = EAXFLANGERPROPERTIES;
using EaxProps = EAXFLANGERPROPERTIES;
using Committer = EaxFlangerCommitter;
static constexpr auto Field = &EaxEffectProps::mFlanger;
static constexpr auto eax_effect_type() { return EaxEffectType::Flanger; }
static constexpr auto efx_effect() { return AL_EFFECT_FLANGER; }
static constexpr auto eax_none_param_id() { return EAXFLANGER_NONE; }
@ -428,11 +386,10 @@ struct EaxFlangerTraits {
template<typename TTraits>
struct ChorusFlangerEffect {
using Traits = TTraits;
using EaxProps = typename Traits::EaxProps;
using Committer = typename Traits::Committer;
using Exception = typename Committer::Exception;
static constexpr auto Field = Traits::Field;
struct WaveformValidator {
void operator()(unsigned long ulWaveform) const
{
@ -500,7 +457,7 @@ struct ChorusFlangerEffect {
}; // DelayValidator
struct AllValidator {
void operator()(const typename Traits::Props& all) const
void operator()(const EaxProps& all) const
{
WaveformValidator{}(all.ulWaveform);
PhaseValidator{}(all.lPhase);
@ -514,8 +471,7 @@ struct ChorusFlangerEffect {
public:
static void SetDefaults(EaxEffectProps &props)
{
auto&& all = props.*Field;
props.mType = Traits::eax_effect_type();
auto&& all = props.emplace<EaxProps>();
all.ulWaveform = Traits::eax_default_waveform();
all.lPhase = Traits::eax_default_phase();
all.flRate = Traits::eax_default_rate();
@ -525,109 +481,83 @@ public:
}
static void Get(const EaxCall &call, const EaxEffectProps &props)
static void Get(const EaxCall &call, const EaxProps &all)
{
auto&& all = props.*Field;
switch(call.get_property_id())
{
case Traits::eax_none_param_id():
break;
case Traits::eax_allparameters_param_id():
call.template set_value<Exception>(all);
break;
case Traits::eax_waveform_param_id():
call.template set_value<Exception>(all.ulWaveform);
break;
case Traits::eax_phase_param_id():
call.template set_value<Exception>(all.lPhase);
break;
case Traits::eax_rate_param_id():
call.template set_value<Exception>(all.flRate);
break;
case Traits::eax_depth_param_id():
call.template set_value<Exception>(all.flDepth);
break;
case Traits::eax_feedback_param_id():
call.template set_value<Exception>(all.flFeedback);
break;
case Traits::eax_delay_param_id():
call.template set_value<Exception>(all.flDelay);
break;
default:
Committer::fail_unknown_property_id();
}
}
static void Set(const EaxCall &call, EaxEffectProps &props)
static void Set(const EaxCall &call, EaxProps &all)
{
auto&& all = props.*Field;
switch(call.get_property_id())
{
case Traits::eax_none_param_id():
break;
case Traits::eax_allparameters_param_id():
Committer::template defer<AllValidator>(call, all);
break;
case Traits::eax_waveform_param_id():
Committer::template defer<WaveformValidator>(call, all.ulWaveform);
break;
case Traits::eax_phase_param_id():
Committer::template defer<PhaseValidator>(call, all.lPhase);
break;
case Traits::eax_rate_param_id():
Committer::template defer<RateValidator>(call, all.flRate);
break;
case Traits::eax_depth_param_id():
Committer::template defer<DepthValidator>(call, all.flDepth);
break;
case Traits::eax_feedback_param_id():
Committer::template defer<FeedbackValidator>(call, all.flFeedback);
break;
case Traits::eax_delay_param_id():
Committer::template defer<DelayValidator>(call, all.flDelay);
break;
default:
Committer::fail_unknown_property_id();
}
}
static bool Commit(const EaxEffectProps &props, EaxEffectProps &props_, EffectProps &al_props_)
static bool Commit(const EaxProps &props, EaxEffectProps &props_, ChorusProps &al_props_)
{
if(props.mType == props_.mType)
{
auto&& src = props_.*Field;
auto&& dst = props.*Field;
if(dst.ulWaveform == src.ulWaveform && dst.lPhase == src.lPhase
&& dst.flRate == src.flRate && dst.flDepth == src.flDepth
&& dst.flFeedback == src.flFeedback && dst.flDelay == src.flDelay)
return false;
}
if(auto *cur = std::get_if<EaxProps>(&props_); cur && *cur == props)
return false;
props_ = props;
auto&& dst = props.*Field;
al_props_.Chorus.Waveform = Traits::eax_waveform(dst.ulWaveform);
al_props_.Chorus.Phase = static_cast<int>(dst.lPhase);
al_props_.Chorus.Rate = dst.flRate;
al_props_.Chorus.Depth = dst.flDepth;
al_props_.Chorus.Feedback = dst.flFeedback;
al_props_.Chorus.Delay = dst.flDelay;
al_props_.Waveform = Traits::eax_waveform(props.ulWaveform);
al_props_.Phase = static_cast<int>(props.lPhase);
al_props_.Rate = props.flRate;
al_props_.Depth = props.flDepth;
al_props_.Feedback = props.flFeedback;
al_props_.Delay = props.flDelay;
return true;
}
@ -652,29 +582,25 @@ template<>
throw Exception{message};
}
template<>
bool ChorusCommitter::commit(const EaxEffectProps &props)
bool EaxChorusCommitter::commit(const EAXCHORUSPROPERTIES &props)
{
using Committer = ChorusFlangerEffect<EaxChorusTraits>;
return Committer::Commit(props, mEaxProps, mAlProps);
return Committer::Commit(props, mEaxProps, mAlProps.emplace<ChorusProps>());
}
template<>
void ChorusCommitter::SetDefaults(EaxEffectProps &props)
void EaxChorusCommitter::SetDefaults(EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxChorusTraits>;
Committer::SetDefaults(props);
}
template<>
void ChorusCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxChorusCommitter::Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props)
{
using Committer = ChorusFlangerEffect<EaxChorusTraits>;
Committer::Get(call, props);
}
template<>
void ChorusCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxChorusCommitter::Set(const EaxCall &call, EAXCHORUSPROPERTIES &props)
{
using Committer = ChorusFlangerEffect<EaxChorusTraits>;
Committer::Set(call, props);
@ -693,29 +619,25 @@ template<>
throw Exception{message};
}
template<>
bool FlangerCommitter::commit(const EaxEffectProps &props)
bool EaxFlangerCommitter::commit(const EAXFLANGERPROPERTIES &props)
{
using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
return Committer::Commit(props, mEaxProps, mAlProps);
return Committer::Commit(props, mEaxProps, mAlProps.emplace<ChorusProps>());
}
template<>
void FlangerCommitter::SetDefaults(EaxEffectProps &props)
void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
Committer::SetDefaults(props);
}
template<>
void FlangerCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxFlangerCommitter::Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props)
{
using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
Committer::Get(call, props);
}
template<>
void FlangerCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxFlangerCommitter::Set(const EaxCall &call, EAXFLANGERPROPERTIES &props)
{
using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
Committer::Set(call, props);

View file

@ -9,6 +9,7 @@
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
@ -16,14 +17,25 @@
namespace {
void Compressor_setParami(EffectProps *props, ALenum param, int val)
constexpr EffectProps genDefaultProps() noexcept
{
CompressorProps props{};
props.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
return props;
}
} // namespace
const EffectProps CompressorEffectProps{genDefaultProps()};
void CompressorEffectHandler::SetParami(CompressorProps &props, ALenum param, int val)
{
switch(param)
{
case AL_COMPRESSOR_ONOFF:
if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF))
throw effect_exception{AL_INVALID_VALUE, "Compressor state out of range"};
props->Compressor.OnOff = (val != AL_FALSE);
props.OnOff = (val != AL_FALSE);
break;
default:
@ -31,51 +43,36 @@ void Compressor_setParami(EffectProps *props, ALenum param, int val)
param};
}
}
void Compressor_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Compressor_setParami(props, param, vals[0]); }
void Compressor_setParamf(EffectProps*, ALenum param, float)
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 Compressor_setParamfv(EffectProps*, ALenum param, const float*)
void CompressorEffectHandler::SetParamfv(CompressorProps&, ALenum param, const float*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x",
param};
}
void Compressor_getParami(const EffectProps *props, ALenum param, int *val)
void CompressorEffectHandler::GetParami(const CompressorProps &props, ALenum param, int *val)
{
switch(param)
{
case AL_COMPRESSOR_ONOFF:
*val = props->Compressor.OnOff;
break;
case AL_COMPRESSOR_ONOFF: *val = props.OnOff; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
param};
}
}
void Compressor_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Compressor_getParami(props, param, vals); }
void Compressor_getParamf(const EffectProps*, ALenum param, float*)
void CompressorEffectHandler::GetParamiv(const CompressorProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); }
void CompressorEffectHandler::GetParamf(const CompressorProps&, ALenum param, float*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; }
void Compressor_getParamfv(const EffectProps*, ALenum param, float*)
void CompressorEffectHandler::GetParamfv(const CompressorProps&, ALenum param, float*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x",
param};
}
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Compressor);
const EffectProps CompressorEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
@ -115,46 +112,40 @@ template<>
throw Exception{message};
}
template<>
bool CompressorCommitter::commit(const EaxEffectProps &props)
bool EaxCompressorCommitter::commit(const EAXAGCCOMPRESSORPROPERTIES &props)
{
if(props.mType == mEaxProps.mType
&& props.mCompressor.ulOnOff == mEaxProps.mCompressor.ulOnOff)
if(auto *cur = std::get_if<EAXAGCCOMPRESSORPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
mAlProps = CompressorProps{props.ulOnOff != 0};
mAlProps.Compressor.OnOff = (props.mCompressor.ulOnOff != 0);
return true;
}
template<>
void CompressorCommitter::SetDefaults(EaxEffectProps &props)
void EaxCompressorCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Compressor;
props.mCompressor.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF;
props = EAXAGCCOMPRESSORPROPERTIES{EAXAGCCOMPRESSOR_DEFAULTONOFF};
}
template<>
void CompressorCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxCompressorCommitter::Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXAGCCOMPRESSOR_NONE: break;
case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value<Exception>(props.mCompressor); break;
case EAXAGCCOMPRESSOR_ONOFF: call.set_value<Exception>(props.mCompressor.ulOnOff); break;
case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXAGCCOMPRESSOR_ONOFF: call.set_value<Exception>(props.ulOnOff); break;
default: fail_unknown_property_id();
}
}
template<>
void CompressorCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxCompressorCommitter::Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXAGCCOMPRESSOR_NONE: break;
case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer<AllValidator>(call, props.mCompressor); break;
case EAXAGCCOMPRESSOR_ONOFF: defer<OnOffValidator>(call, props.mCompressor.ulOnOff); break;
case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXAGCCOMPRESSOR_ONOFF: defer<OnOffValidator>(call, props.ulOnOff); break;
default: fail_unknown_property_id();
}
}

View file

@ -1,93 +1,117 @@
#include "config.h"
#include "AL/al.h"
#include "alc/inprogext.h"
#include <algorithm>
#include <array>
#include <cmath>
#include "alc/effects/base.h"
#include "AL/al.h"
#include "alc/inprogext.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/effects/base.h"
#include "effects.h"
namespace {
void Convolution_setParami(EffectProps* /*props*/, ALenum param, int /*val*/)
constexpr EffectProps genDefaultProps() noexcept
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
param};
}
}
void Convolution_setParamiv(EffectProps *props, ALenum param, const int *vals)
{
switch(param)
{
default:
Convolution_setParami(props, param, vals[0]);
}
}
void Convolution_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
param};
}
}
void Convolution_setParamfv(EffectProps *props, ALenum param, const float *vals)
{
switch(param)
{
default:
Convolution_setParamf(props, param, vals[0]);
}
}
void Convolution_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
param};
}
}
void Convolution_getParamiv(const EffectProps *props, ALenum param, int *vals)
{
switch(param)
{
default:
Convolution_getParami(props, param, vals);
}
}
void Convolution_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
param};
}
}
void Convolution_getParamfv(const EffectProps *props, ALenum param, float *vals)
{
switch(param)
{
default:
Convolution_getParamf(props, param, vals);
}
}
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
ConvolutionProps props{};
props.OrientAt = {0.0f, 0.0f, -1.0f};
props.OrientUp = {0.0f, 1.0f, 0.0f};
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Convolution);
const EffectProps ConvolutionEffectProps{genDefaultProps()};
void ConvolutionEffectHandler::SetParami(ConvolutionProps& /*props*/, ALenum param, int /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x",
param};
}
}
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); };
al::span<const float> vals;
switch(param)
{
case AL_CONVOLUTION_ORIENTATION_SOFT:
vals = {values, 6_uz};
if(!std::all_of(vals.cbegin(), vals.cend(), finite_checker))
throw effect_exception{AL_INVALID_VALUE, "Property 0x%04x value out of range", param};
std::copy_n(vals.cbegin(), props.OrientAt.size(), props.OrientAt.begin());
std::copy_n(vals.cbegin()+3, props.OrientUp.size(), props.OrientUp.begin());
break;
default:
SetParamf(props, param, *values);
}
}
void ConvolutionEffectHandler::GetParami(const ConvolutionProps& /*props*/, ALenum param, int* /*val*/)
{
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)
{
case AL_CONVOLUTION_ORIENTATION_SOFT:
vals = {values, 6_uz};
std::copy(props.OrientAt.cbegin(), props.OrientAt.cend(), vals.begin());
std::copy(props.OrientUp.cbegin(), props.OrientUp.cend(), vals.begin()+3);
break;
default:
GetParamf(props, param, values);
}
}

View file

@ -12,61 +12,111 @@
namespace {
void Dedicated_setParami(EffectProps*, ALenum param, int)
constexpr EffectProps genDefaultDialogProps() noexcept
{
DedicatedProps props{};
props.Target = DedicatedProps::Dialog;
props.Gain = 1.0f;
return props;
}
constexpr EffectProps genDefaultLfeProps() noexcept
{
DedicatedProps props{};
props.Target = DedicatedProps::Lfe;
props.Gain = 1.0f;
return props;
}
} // namespace
const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()};
void DedicatedDialogEffectHandler::SetParami(DedicatedProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
void Dedicated_setParamiv(EffectProps*, ALenum param, const int*)
void DedicatedDialogEffectHandler::SetParamiv(DedicatedProps&, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
param};
}
void Dedicated_setParamf(EffectProps *props, ALenum param, float val)
void DedicatedDialogEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val)
{
switch(param)
{
case AL_DEDICATED_GAIN:
if(!(val >= 0.0f && std::isfinite(val)))
throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"};
props->Dedicated.Gain = val;
props.Gain = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
}
}
void Dedicated_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Dedicated_setParamf(props, param, vals[0]); }
void DedicatedDialogEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void Dedicated_getParami(const EffectProps*, ALenum param, int*)
void DedicatedDialogEffectHandler::GetParami(const DedicatedProps&, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
void Dedicated_getParamiv(const EffectProps*, ALenum param, int*)
void DedicatedDialogEffectHandler::GetParamiv(const DedicatedProps&, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
param};
}
void Dedicated_getParamf(const EffectProps *props, ALenum param, float *val)
void DedicatedDialogEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_DEDICATED_GAIN: *val = props.Gain; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
}
}
void DedicatedDialogEffectHandler::GetParamfv(const DedicatedProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }
const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()};
void DedicatedLfeEffectHandler::SetParami(DedicatedProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
void DedicatedLfeEffectHandler::SetParamiv(DedicatedProps&, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
param};
}
void DedicatedLfeEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val)
{
switch(param)
{
case AL_DEDICATED_GAIN:
*val = props->Dedicated.Gain;
if(!(val >= 0.0f && std::isfinite(val)))
throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"};
props.Gain = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
}
}
void Dedicated_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Dedicated_getParamf(props, param, vals); }
void DedicatedLfeEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
EffectProps genDefaultProps() noexcept
void DedicatedLfeEffectHandler::GetParami(const DedicatedProps&, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
void DedicatedLfeEffectHandler::GetParamiv(const DedicatedProps&, ALenum param, int*)
{
EffectProps props{};
props.Dedicated.Gain = 1.0f;
return props;
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
param};
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Dedicated);
const EffectProps DedicatedEffectProps{genDefaultProps()};
void DedicatedLfeEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_DEDICATED_GAIN: *val = props.Gain; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
}
}
void DedicatedLfeEffectHandler::GetParamfv(const DedicatedProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }

View file

@ -9,6 +9,7 @@
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
@ -16,108 +17,93 @@
namespace {
void Distortion_setParami(EffectProps*, ALenum param, int)
constexpr EffectProps genDefaultProps() noexcept
{
DistortionProps props{};
props.Edge = AL_DISTORTION_DEFAULT_EDGE;
props.Gain = AL_DISTORTION_DEFAULT_GAIN;
props.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
props.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
props.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
return props;
}
} // namespace
const EffectProps DistortionEffectProps{genDefaultProps()};
void DistortionEffectHandler::SetParami(DistortionProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; }
void Distortion_setParamiv(EffectProps*, ALenum param, const int*)
void DistortionEffectHandler::SetParamiv(DistortionProps&, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
param};
}
void Distortion_setParamf(EffectProps *props, ALenum param, float val)
void DistortionEffectHandler::SetParamf(DistortionProps &props, ALenum param, float val)
{
switch(param)
{
case AL_DISTORTION_EDGE:
if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE))
throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"};
props->Distortion.Edge = val;
props.Edge = val;
break;
case AL_DISTORTION_GAIN:
if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"};
props->Distortion.Gain = val;
props.Gain = val;
break;
case AL_DISTORTION_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"};
props->Distortion.LowpassCutoff = val;
props.LowpassCutoff = val;
break;
case AL_DISTORTION_EQCENTER:
if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER))
throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"};
props->Distortion.EQCenter = val;
props.EQCenter = val;
break;
case AL_DISTORTION_EQBANDWIDTH:
if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH))
throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"};
props->Distortion.EQBandwidth = val;
props.EQBandwidth = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
}
}
void Distortion_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Distortion_setParamf(props, param, vals[0]); }
void DistortionEffectHandler::SetParamfv(DistortionProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void Distortion_getParami(const EffectProps*, ALenum param, int*)
void DistortionEffectHandler::GetParami(const DistortionProps&, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; }
void Distortion_getParamiv(const EffectProps*, ALenum param, int*)
void DistortionEffectHandler::GetParamiv(const DistortionProps&, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
param};
}
void Distortion_getParamf(const EffectProps *props, ALenum param, float *val)
void DistortionEffectHandler::GetParamf(const DistortionProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_DISTORTION_EDGE:
*val = props->Distortion.Edge;
break;
case AL_DISTORTION_GAIN:
*val = props->Distortion.Gain;
break;
case AL_DISTORTION_LOWPASS_CUTOFF:
*val = props->Distortion.LowpassCutoff;
break;
case AL_DISTORTION_EQCENTER:
*val = props->Distortion.EQCenter;
break;
case AL_DISTORTION_EQBANDWIDTH:
*val = props->Distortion.EQBandwidth;
break;
case AL_DISTORTION_EDGE: *val = props.Edge; break;
case AL_DISTORTION_GAIN: *val = props.Gain; break;
case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; break;
case AL_DISTORTION_EQCENTER: *val = props.EQCenter; break;
case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
}
}
void Distortion_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Distortion_getParamf(props, param, vals); }
void DistortionEffectHandler::GetParamfv(const DistortionProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE;
props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN;
props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Distortion);
const EffectProps DistortionEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
@ -204,66 +190,66 @@ template<>
throw Exception{message};
}
template<>
bool DistortionCommitter::commit(const EaxEffectProps &props)
bool EaxDistortionCommitter::commit(const EAXDISTORTIONPROPERTIES &props)
{
if(props.mType == mEaxProps.mType && mEaxProps.mDistortion.flEdge == props.mDistortion.flEdge
&& mEaxProps.mDistortion.lGain == props.mDistortion.lGain
&& mEaxProps.mDistortion.flLowPassCutOff == props.mDistortion.flLowPassCutOff
&& mEaxProps.mDistortion.flEQCenter == props.mDistortion.flEQCenter
&& mEaxProps.mDistortion.flEQBandwidth == props.mDistortion.flEQBandwidth)
if(auto *cur = std::get_if<EAXDISTORTIONPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
mAlProps.Distortion.Edge = props.mDistortion.flEdge;
mAlProps.Distortion.Gain = level_mb_to_gain(static_cast<float>(props.mDistortion.lGain));
mAlProps.Distortion.LowpassCutoff = props.mDistortion.flLowPassCutOff;
mAlProps.Distortion.EQCenter = props.mDistortion.flEQCenter;
mAlProps.Distortion.EQBandwidth = props.mDistortion.flEdge;
mAlProps = [&]{
DistortionProps ret{};
ret.Edge = props.flEdge;
ret.Gain = level_mb_to_gain(static_cast<float>(props.lGain));
ret.LowpassCutoff = props.flLowPassCutOff;
ret.EQCenter = props.flEQCenter;
ret.EQBandwidth = props.flEdge;
return ret;
}();
return true;
}
template<>
void DistortionCommitter::SetDefaults(EaxEffectProps &props)
void EaxDistortionCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Distortion;
props.mDistortion.flEdge = EAXDISTORTION_DEFAULTEDGE;
props.mDistortion.lGain = EAXDISTORTION_DEFAULTGAIN;
props.mDistortion.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF;
props.mDistortion.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER;
props.mDistortion.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH;
static constexpr EAXDISTORTIONPROPERTIES defprops{[]
{
EAXDISTORTIONPROPERTIES ret{};
ret.flEdge = EAXDISTORTION_DEFAULTEDGE;
ret.lGain = EAXDISTORTION_DEFAULTGAIN;
ret.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF;
ret.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER;
ret.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH;
return ret;
}()};
props = defprops;
}
template<>
void DistortionCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxDistortionCommitter::Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXDISTORTION_NONE: break;
case EAXDISTORTION_ALLPARAMETERS: call.set_value<Exception>(props.mDistortion); break;
case EAXDISTORTION_EDGE: call.set_value<Exception>(props.mDistortion.flEdge); break;
case EAXDISTORTION_GAIN: call.set_value<Exception>(props.mDistortion.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: call.set_value<Exception>(props.mDistortion.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: call.set_value<Exception>(props.mDistortion.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: call.set_value<Exception>(props.mDistortion.flEQBandwidth); break;
case EAXDISTORTION_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXDISTORTION_EDGE: call.set_value<Exception>(props.flEdge); break;
case EAXDISTORTION_GAIN: call.set_value<Exception>(props.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: call.set_value<Exception>(props.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: call.set_value<Exception>(props.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: call.set_value<Exception>(props.flEQBandwidth); break;
default: fail_unknown_property_id();
}
}
template<>
void DistortionCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxDistortionCommitter::Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXDISTORTION_NONE: break;
case EAXDISTORTION_ALLPARAMETERS: defer<AllValidator>(call, props.mDistortion); break;
case EAXDISTORTION_EDGE: defer<EdgeValidator>(call, props.mDistortion.flEdge); break;
case EAXDISTORTION_GAIN: defer<GainValidator>(call, props.mDistortion.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: defer<LowPassCutOffValidator>(call, props.mDistortion.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: defer<EqCenterValidator>(call, props.mDistortion.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: defer<EqBandwidthValidator>(call, props.mDistortion.flEQBandwidth); break;
case EAXDISTORTION_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXDISTORTION_EDGE: defer<EdgeValidator>(call, props.flEdge); break;
case EAXDISTORTION_GAIN: defer<GainValidator>(call, props.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: defer<LowPassCutOffValidator>(call, props.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: defer<EqCenterValidator>(call, props.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: defer<EqBandwidthValidator>(call, props.flEQBandwidth); break;
default: fail_unknown_property_id();
}
}

View file

@ -9,6 +9,7 @@
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
@ -19,102 +20,87 @@ namespace {
static_assert(EchoMaxDelay >= AL_ECHO_MAX_DELAY, "Echo max delay too short");
static_assert(EchoMaxLRDelay >= AL_ECHO_MAX_LRDELAY, "Echo max left-right delay too short");
void Echo_setParami(EffectProps*, ALenum param, int)
constexpr EffectProps genDefaultProps() noexcept
{
EchoProps props{};
props.Delay = AL_ECHO_DEFAULT_DELAY;
props.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
props.Damping = AL_ECHO_DEFAULT_DAMPING;
props.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
props.Spread = AL_ECHO_DEFAULT_SPREAD;
return props;
}
} // namespace
const EffectProps EchoEffectProps{genDefaultProps()};
void EchoEffectHandler::SetParami(EchoProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; }
void Echo_setParamiv(EffectProps*, ALenum param, const int*)
void EchoEffectHandler::SetParamiv(EchoProps&, ALenum param, const int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; }
void Echo_setParamf(EffectProps *props, ALenum param, float val)
void EchoEffectHandler::SetParamf(EchoProps &props, ALenum param, float val)
{
switch(param)
{
case AL_ECHO_DELAY:
if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
throw effect_exception{AL_INVALID_VALUE, "Echo delay out of range"};
props->Echo.Delay = val;
props.Delay = val;
break;
case AL_ECHO_LRDELAY:
if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY))
throw effect_exception{AL_INVALID_VALUE, "Echo LR delay out of range"};
props->Echo.LRDelay = val;
props.LRDelay = val;
break;
case AL_ECHO_DAMPING:
if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING))
throw effect_exception{AL_INVALID_VALUE, "Echo damping out of range"};
props->Echo.Damping = val;
props.Damping = val;
break;
case AL_ECHO_FEEDBACK:
if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK))
throw effect_exception{AL_INVALID_VALUE, "Echo feedback out of range"};
props->Echo.Feedback = val;
props.Feedback = val;
break;
case AL_ECHO_SPREAD:
if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD))
throw effect_exception{AL_INVALID_VALUE, "Echo spread out of range"};
props->Echo.Spread = val;
props.Spread = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param};
}
}
void Echo_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Echo_setParamf(props, param, vals[0]); }
void EchoEffectHandler::SetParamfv(EchoProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void Echo_getParami(const EffectProps*, ALenum param, int*)
void EchoEffectHandler::GetParami(const EchoProps&, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; }
void Echo_getParamiv(const EffectProps*, ALenum param, int*)
void EchoEffectHandler::GetParamiv(const EchoProps&, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; }
void Echo_getParamf(const EffectProps *props, ALenum param, float *val)
void EchoEffectHandler::GetParamf(const EchoProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_ECHO_DELAY:
*val = props->Echo.Delay;
break;
case AL_ECHO_LRDELAY:
*val = props->Echo.LRDelay;
break;
case AL_ECHO_DAMPING:
*val = props->Echo.Damping;
break;
case AL_ECHO_FEEDBACK:
*val = props->Echo.Feedback;
break;
case AL_ECHO_SPREAD:
*val = props->Echo.Spread;
break;
case AL_ECHO_DELAY: *val = props.Delay; break;
case AL_ECHO_LRDELAY: *val = props.LRDelay; break;
case AL_ECHO_DAMPING: *val = props.Damping; break;
case AL_ECHO_FEEDBACK: *val = props.Feedback; break;
case AL_ECHO_SPREAD: *val = props.Spread; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param};
}
}
void Echo_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Echo_getParamf(props, param, vals); }
void EchoEffectHandler::GetParamfv(const EchoProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Echo.Delay = AL_ECHO_DEFAULT_DELAY;
props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING;
props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD;
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Echo);
const EffectProps EchoEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
@ -201,66 +187,66 @@ template<>
throw Exception{message};
}
template<>
bool EchoCommitter::commit(const EaxEffectProps &props)
bool EaxEchoCommitter::commit(const EAXECHOPROPERTIES &props)
{
if(props.mType == mEaxProps.mType && mEaxProps.mEcho.flDelay == props.mEcho.flDelay
&& mEaxProps.mEcho.flLRDelay == props.mEcho.flLRDelay
&& mEaxProps.mEcho.flDamping == props.mEcho.flDamping
&& mEaxProps.mEcho.flFeedback == props.mEcho.flFeedback
&& mEaxProps.mEcho.flSpread == props.mEcho.flSpread)
if(auto *cur = std::get_if<EAXECHOPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
mAlProps.Echo.Delay = props.mEcho.flDelay;
mAlProps.Echo.LRDelay = props.mEcho.flLRDelay;
mAlProps.Echo.Damping = props.mEcho.flDamping;
mAlProps.Echo.Feedback = props.mEcho.flFeedback;
mAlProps.Echo.Spread = props.mEcho.flSpread;
mAlProps = [&]{
EchoProps ret{};
ret.Delay = props.flDelay;
ret.LRDelay = props.flLRDelay;
ret.Damping = props.flDamping;
ret.Feedback = props.flFeedback;
ret.Spread = props.flSpread;
return ret;
}();
return true;
}
template<>
void EchoCommitter::SetDefaults(EaxEffectProps &props)
void EaxEchoCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Echo;
props.mEcho.flDelay = EAXECHO_DEFAULTDELAY;
props.mEcho.flLRDelay = EAXECHO_DEFAULTLRDELAY;
props.mEcho.flDamping = EAXECHO_DEFAULTDAMPING;
props.mEcho.flFeedback = EAXECHO_DEFAULTFEEDBACK;
props.mEcho.flSpread = EAXECHO_DEFAULTSPREAD;
static constexpr EAXECHOPROPERTIES defprops{[]
{
EAXECHOPROPERTIES ret{};
ret.flDelay = EAXECHO_DEFAULTDELAY;
ret.flLRDelay = EAXECHO_DEFAULTLRDELAY;
ret.flDamping = EAXECHO_DEFAULTDAMPING;
ret.flFeedback = EAXECHO_DEFAULTFEEDBACK;
ret.flSpread = EAXECHO_DEFAULTSPREAD;
return ret;
}()};
props = defprops;
}
template<>
void EchoCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxEchoCommitter::Get(const EaxCall &call, const EAXECHOPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXECHO_NONE: break;
case EAXECHO_ALLPARAMETERS: call.set_value<Exception>(props.mEcho); break;
case EAXECHO_DELAY: call.set_value<Exception>(props.mEcho.flDelay); break;
case EAXECHO_LRDELAY: call.set_value<Exception>(props.mEcho.flLRDelay); break;
case EAXECHO_DAMPING: call.set_value<Exception>(props.mEcho.flDamping); break;
case EAXECHO_FEEDBACK: call.set_value<Exception>(props.mEcho.flFeedback); break;
case EAXECHO_SPREAD: call.set_value<Exception>(props.mEcho.flSpread); break;
case EAXECHO_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXECHO_DELAY: call.set_value<Exception>(props.flDelay); break;
case EAXECHO_LRDELAY: call.set_value<Exception>(props.flLRDelay); break;
case EAXECHO_DAMPING: call.set_value<Exception>(props.flDamping); break;
case EAXECHO_FEEDBACK: call.set_value<Exception>(props.flFeedback); break;
case EAXECHO_SPREAD: call.set_value<Exception>(props.flSpread); break;
default: fail_unknown_property_id();
}
}
template<>
void EchoCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxEchoCommitter::Set(const EaxCall &call, EAXECHOPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXECHO_NONE: break;
case EAXECHO_ALLPARAMETERS: defer<AllValidator>(call, props.mEcho); break;
case EAXECHO_DELAY: defer<DelayValidator>(call, props.mEcho.flDelay); break;
case EAXECHO_LRDELAY: defer<LrDelayValidator>(call, props.mEcho.flLRDelay); break;
case EAXECHO_DAMPING: defer<DampingValidator>(call, props.mEcho.flDamping); break;
case EAXECHO_FEEDBACK: defer<FeedbackValidator>(call, props.mEcho.flFeedback); break;
case EAXECHO_SPREAD: defer<SpreadValidator>(call, props.mEcho.flSpread); break;
case EAXECHO_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXECHO_DELAY: defer<DelayValidator>(call, props.flDelay); break;
case EAXECHO_LRDELAY: defer<LrDelayValidator>(call, props.flLRDelay); break;
case EAXECHO_DAMPING: defer<DampingValidator>(call, props.flDamping); break;
case EAXECHO_FEEDBACK: defer<FeedbackValidator>(call, props.flFeedback); break;
case EAXECHO_SPREAD: defer<SpreadValidator>(call, props.flSpread); break;
default: fail_unknown_property_id();
}
}

View file

@ -1,9 +1,3 @@
#include "config.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "AL/efx.h"
#include "effects.h"
#endif // ALSOFT_EAX

View file

@ -1,52 +1,47 @@
#ifndef AL_EFFECTS_EFFECTS_H
#define AL_EFFECTS_EFFECTS_H
#include <variant>
#include "AL/al.h"
#include "core/except.h"
#include "al/error.h"
#include "core/effects/base.h"
#ifdef ALSOFT_EAX
#include "al/eax/effect.h"
#endif // ALSOFT_EAX
union EffectProps;
class effect_exception final : public al::base_exception {
ALenum mErrorCode;
public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
effect_exception(ALenum code, const char *msg, ...);
~effect_exception() override;
ALenum errorCode() const noexcept { return mErrorCode; }
#define DECL_HANDLER(N, T) \
struct N { \
using prop_type = T; \
\
static void SetParami(prop_type &props, ALenum param, int val); \
static void SetParamiv(prop_type &props, ALenum param, const int *vals); \
static void SetParamf(prop_type &props, ALenum param, float val); \
static void SetParamfv(prop_type &props, ALenum param, const float *vals);\
static void GetParami(const prop_type &props, ALenum param, int *val); \
static void GetParamiv(const prop_type &props, ALenum param, int *vals); \
static void GetParamf(const prop_type &props, ALenum param, float *val); \
static void GetParamfv(const prop_type &props, ALenum param, float *vals);\
};
DECL_HANDLER(NullEffectHandler, std::monostate)
DECL_HANDLER(ReverbEffectHandler, ReverbProps)
DECL_HANDLER(StdReverbEffectHandler, ReverbProps)
DECL_HANDLER(AutowahEffectHandler, AutowahProps)
DECL_HANDLER(ChorusEffectHandler, ChorusProps)
DECL_HANDLER(CompressorEffectHandler, CompressorProps)
DECL_HANDLER(DistortionEffectHandler, DistortionProps)
DECL_HANDLER(EchoEffectHandler, EchoProps)
DECL_HANDLER(EqualizerEffectHandler, EqualizerProps)
DECL_HANDLER(FlangerEffectHandler, ChorusProps)
DECL_HANDLER(FshifterEffectHandler, FshifterProps)
DECL_HANDLER(ModulatorEffectHandler, ModulatorProps)
DECL_HANDLER(PshifterEffectHandler, PshifterProps)
DECL_HANDLER(VmorpherEffectHandler, VmorpherProps)
DECL_HANDLER(DedicatedDialogEffectHandler, DedicatedProps)
DECL_HANDLER(DedicatedLfeEffectHandler, DedicatedProps)
DECL_HANDLER(ConvolutionEffectHandler, ConvolutionProps)
#undef DECL_HANDLER
struct EffectVtable {
void (*const setParami)(EffectProps *props, ALenum param, int val);
void (*const setParamiv)(EffectProps *props, ALenum param, const int *vals);
void (*const setParamf)(EffectProps *props, ALenum param, float val);
void (*const setParamfv)(EffectProps *props, ALenum param, const float *vals);
void (*const getParami)(const EffectProps *props, ALenum param, int *val);
void (*const getParamiv)(const EffectProps *props, ALenum param, int *vals);
void (*const getParamf)(const EffectProps *props, ALenum param, float *val);
void (*const getParamfv)(const EffectProps *props, ALenum param, float *vals);
};
#define DEFINE_ALEFFECT_VTABLE(T) \
const EffectVtable T##EffectVtable = { \
T##_setParami, T##_setParamiv, \
T##_setParamf, T##_setParamfv, \
T##_getParami, T##_getParamiv, \
T##_getParamf, T##_getParamfv, \
}
using effect_exception = al::context_error;
/* Default properties for the given effect types. */
@ -64,25 +59,8 @@ extern const EffectProps FshifterEffectProps;
extern const EffectProps ModulatorEffectProps;
extern const EffectProps PshifterEffectProps;
extern const EffectProps VmorpherEffectProps;
extern const EffectProps DedicatedEffectProps;
extern const EffectProps DedicatedDialogEffectProps;
extern const EffectProps DedicatedLfeEffectProps;
extern const EffectProps ConvolutionEffectProps;
/* Vtables to get/set properties for the given effect types. */
extern const EffectVtable NullEffectVtable;
extern const EffectVtable ReverbEffectVtable;
extern const EffectVtable StdReverbEffectVtable;
extern const EffectVtable AutowahEffectVtable;
extern const EffectVtable ChorusEffectVtable;
extern const EffectVtable CompressorEffectVtable;
extern const EffectVtable DistortionEffectVtable;
extern const EffectVtable EchoEffectVtable;
extern const EffectVtable EqualizerEffectVtable;
extern const EffectVtable FlangerEffectVtable;
extern const EffectVtable FshifterEffectVtable;
extern const EffectVtable ModulatorEffectVtable;
extern const EffectVtable PshifterEffectVtable;
extern const EffectVtable VmorpherEffectVtable;
extern const EffectVtable DedicatedEffectVtable;
extern const EffectVtable ConvolutionEffectVtable;
#endif /* AL_EFFECTS_EFFECTS_H */

View file

@ -9,6 +9,7 @@
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
@ -16,163 +17,133 @@
namespace {
void Equalizer_setParami(EffectProps*, ALenum param, int)
constexpr EffectProps genDefaultProps() noexcept
{
EqualizerProps props{};
props.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
props.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
props.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
props.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
props.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
props.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
props.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
props.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
props.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
props.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
return props;
}
} // namespace
const EffectProps EqualizerEffectProps{genDefaultProps()};
void EqualizerEffectHandler::SetParami(EqualizerProps&, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; }
void Equalizer_setParamiv(EffectProps*, ALenum param, const int*)
void EqualizerEffectHandler::SetParamiv(EqualizerProps&, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x",
param};
}
void Equalizer_setParamf(EffectProps *props, ALenum param, float val)
void EqualizerEffectHandler::SetParamf(EqualizerProps &props, ALenum param, float val)
{
switch(param)
{
case AL_EQUALIZER_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"};
props->Equalizer.LowGain = val;
props.LowGain = val;
break;
case AL_EQUALIZER_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"};
props->Equalizer.LowCutoff = val;
props.LowCutoff = val;
break;
case AL_EQUALIZER_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"};
props->Equalizer.Mid1Gain = val;
props.Mid1Gain = val;
break;
case AL_EQUALIZER_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"};
props->Equalizer.Mid1Center = val;
props.Mid1Center = val;
break;
case AL_EQUALIZER_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"};
props->Equalizer.Mid1Width = val;
props.Mid1Width = val;
break;
case AL_EQUALIZER_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"};
props->Equalizer.Mid2Gain = val;
props.Mid2Gain = val;
break;
case AL_EQUALIZER_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"};
props->Equalizer.Mid2Center = val;
props.Mid2Center = val;
break;
case AL_EQUALIZER_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"};
props->Equalizer.Mid2Width = val;
props.Mid2Width = val;
break;
case AL_EQUALIZER_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"};
props->Equalizer.HighGain = val;
props.HighGain = val;
break;
case AL_EQUALIZER_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"};
props->Equalizer.HighCutoff = val;
props.HighCutoff = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param};
}
}
void Equalizer_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Equalizer_setParamf(props, param, vals[0]); }
void EqualizerEffectHandler::SetParamfv(EqualizerProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void Equalizer_getParami(const EffectProps*, ALenum param, int*)
void EqualizerEffectHandler::GetParami(const EqualizerProps&, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; }
void Equalizer_getParamiv(const EffectProps*, ALenum param, int*)
void EqualizerEffectHandler::GetParamiv(const EqualizerProps&, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x",
param};
}
void Equalizer_getParamf(const EffectProps *props, ALenum param, float *val)
void EqualizerEffectHandler::GetParamf(const EqualizerProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_EQUALIZER_LOW_GAIN:
*val = props->Equalizer.LowGain;
break;
case AL_EQUALIZER_LOW_CUTOFF:
*val = props->Equalizer.LowCutoff;
break;
case AL_EQUALIZER_MID1_GAIN:
*val = props->Equalizer.Mid1Gain;
break;
case AL_EQUALIZER_MID1_CENTER:
*val = props->Equalizer.Mid1Center;
break;
case AL_EQUALIZER_MID1_WIDTH:
*val = props->Equalizer.Mid1Width;
break;
case AL_EQUALIZER_MID2_GAIN:
*val = props->Equalizer.Mid2Gain;
break;
case AL_EQUALIZER_MID2_CENTER:
*val = props->Equalizer.Mid2Center;
break;
case AL_EQUALIZER_MID2_WIDTH:
*val = props->Equalizer.Mid2Width;
break;
case AL_EQUALIZER_HIGH_GAIN:
*val = props->Equalizer.HighGain;
break;
case AL_EQUALIZER_HIGH_CUTOFF:
*val = props->Equalizer.HighCutoff;
break;
case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; break;
case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; break;
case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; break;
case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; break;
case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; break;
case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; break;
case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; break;
case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; break;
case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; break;
case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param};
}
}
void Equalizer_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Equalizer_getParamf(props, param, vals); }
void EqualizerEffectHandler::GetParamfv(const EqualizerProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Equalizer);
const EffectProps EqualizerEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
@ -319,91 +290,86 @@ template<>
throw Exception{message};
}
template<>
bool EqualizerCommitter::commit(const EaxEffectProps &props)
bool EaxEqualizerCommitter::commit(const EAXEQUALIZERPROPERTIES &props)
{
if(props.mType == mEaxProps.mType && mEaxProps.mEqualizer.lLowGain == props.mEqualizer.lLowGain
&& mEaxProps.mEqualizer.flLowCutOff == props.mEqualizer.flLowCutOff
&& mEaxProps.mEqualizer.lMid1Gain == props.mEqualizer.lMid1Gain
&& mEaxProps.mEqualizer.flMid1Center == props.mEqualizer.flMid1Center
&& mEaxProps.mEqualizer.flMid1Width == props.mEqualizer.flMid1Width
&& mEaxProps.mEqualizer.lMid2Gain == props.mEqualizer.lMid2Gain
&& mEaxProps.mEqualizer.flMid2Center == props.mEqualizer.flMid2Center
&& mEaxProps.mEqualizer.flMid2Width == props.mEqualizer.flMid2Width
&& mEaxProps.mEqualizer.lHighGain == props.mEqualizer.lHighGain
&& mEaxProps.mEqualizer.flHighCutOff == props.mEqualizer.flHighCutOff)
if(auto *cur = std::get_if<EAXEQUALIZERPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
mAlProps.Equalizer.LowGain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lLowGain));
mAlProps.Equalizer.LowCutoff = props.mEqualizer.flLowCutOff;
mAlProps.Equalizer.Mid1Gain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lMid1Gain));
mAlProps.Equalizer.Mid1Center = props.mEqualizer.flMid1Center;
mAlProps.Equalizer.Mid1Width = props.mEqualizer.flMid1Width;
mAlProps.Equalizer.Mid2Gain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lMid2Gain));
mAlProps.Equalizer.Mid2Center = props.mEqualizer.flMid2Center;
mAlProps.Equalizer.Mid2Width = props.mEqualizer.flMid2Width;
mAlProps.Equalizer.HighGain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lHighGain));
mAlProps.Equalizer.HighCutoff = props.mEqualizer.flHighCutOff;
mAlProps = [&]{
EqualizerProps ret{};
ret.LowGain = level_mb_to_gain(static_cast<float>(props.lLowGain));
ret.LowCutoff = props.flLowCutOff;
ret.Mid1Gain = level_mb_to_gain(static_cast<float>(props.lMid1Gain));
ret.Mid1Center = props.flMid1Center;
ret.Mid1Width = props.flMid1Width;
ret.Mid2Gain = level_mb_to_gain(static_cast<float>(props.lMid2Gain));
ret.Mid2Center = props.flMid2Center;
ret.Mid2Width = props.flMid2Width;
ret.HighGain = level_mb_to_gain(static_cast<float>(props.lHighGain));
ret.HighCutoff = props.flHighCutOff;
return ret;
}();
return true;
}
template<>
void EqualizerCommitter::SetDefaults(EaxEffectProps &props)
void EaxEqualizerCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Equalizer;
props.mEqualizer.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN;
props.mEqualizer.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF;
props.mEqualizer.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN;
props.mEqualizer.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER;
props.mEqualizer.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH;
props.mEqualizer.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN;
props.mEqualizer.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER;
props.mEqualizer.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH;
props.mEqualizer.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN;
props.mEqualizer.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF;
static constexpr EAXEQUALIZERPROPERTIES defprops{[]
{
EAXEQUALIZERPROPERTIES ret{};
ret.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN;
ret.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF;
ret.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN;
ret.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER;
ret.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH;
ret.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN;
ret.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER;
ret.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH;
ret.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN;
ret.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF;
return ret;
}()};
props = defprops;
}
template<>
void EqualizerCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxEqualizerCommitter::Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXEQUALIZER_NONE: break;
case EAXEQUALIZER_ALLPARAMETERS: call.set_value<Exception>(props.mEqualizer); break;
case EAXEQUALIZER_LOWGAIN: call.set_value<Exception>(props.mEqualizer.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: call.set_value<Exception>(props.mEqualizer.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: call.set_value<Exception>(props.mEqualizer.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: call.set_value<Exception>(props.mEqualizer.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: call.set_value<Exception>(props.mEqualizer.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: call.set_value<Exception>(props.mEqualizer.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: call.set_value<Exception>(props.mEqualizer.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: call.set_value<Exception>(props.mEqualizer.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: call.set_value<Exception>(props.mEqualizer.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: call.set_value<Exception>(props.mEqualizer.flHighCutOff); break;
case EAXEQUALIZER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXEQUALIZER_LOWGAIN: call.set_value<Exception>(props.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: call.set_value<Exception>(props.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: call.set_value<Exception>(props.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: call.set_value<Exception>(props.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: call.set_value<Exception>(props.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: call.set_value<Exception>(props.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: call.set_value<Exception>(props.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: call.set_value<Exception>(props.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: call.set_value<Exception>(props.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: call.set_value<Exception>(props.flHighCutOff); break;
default: fail_unknown_property_id();
}
}
template<>
void EqualizerCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxEqualizerCommitter::Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXEQUALIZER_NONE: break;
case EAXEQUALIZER_ALLPARAMETERS: defer<AllValidator>(call, props.mEqualizer); break;
case EAXEQUALIZER_LOWGAIN: defer<LowGainValidator>(call, props.mEqualizer.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: defer<LowCutOffValidator>(call, props.mEqualizer.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: defer<Mid1GainValidator>(call, props.mEqualizer.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: defer<Mid1CenterValidator>(call, props.mEqualizer.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: defer<Mid1WidthValidator>(call, props.mEqualizer.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: defer<Mid2GainValidator>(call, props.mEqualizer.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: defer<Mid2CenterValidator>(call, props.mEqualizer.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: defer<Mid2WidthValidator>(call, props.mEqualizer.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: defer<HighGainValidator>(call, props.mEqualizer.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: defer<HighCutOffValidator>(call, props.mEqualizer.flHighCutOff); break;
case EAXEQUALIZER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXEQUALIZER_LOWGAIN: defer<LowGainValidator>(call, props.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: defer<LowCutOffValidator>(call, props.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: defer<Mid1GainValidator>(call, props.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: defer<Mid1CenterValidator>(call, props.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: defer<Mid1WidthValidator>(call, props.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: defer<Mid2GainValidator>(call, props.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: defer<Mid2CenterValidator>(call, props.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: defer<Mid2WidthValidator>(call, props.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: defer<HighGainValidator>(call, props.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: defer<HighCutOffValidator>(call, props.flHighCutOff); break;
default: fail_unknown_property_id();
}
}

View file

@ -1,18 +1,19 @@
#include "config.h"
#include <optional>
#include <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
@ -20,7 +21,7 @@
namespace {
al::optional<FShifterDirection> DirectionFromEmum(ALenum value)
constexpr std::optional<FShifterDirection> DirectionFromEmum(ALenum value) noexcept
{
switch(value)
{
@ -28,9 +29,9 @@ al::optional<FShifterDirection> DirectionFromEmum(ALenum value)
case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return FShifterDirection::Up;
case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return FShifterDirection::Off;
}
return al::nullopt;
return std::nullopt;
}
ALenum EnumFromDirection(FShifterDirection dir)
constexpr ALenum EnumFromDirection(FShifterDirection dir)
{
switch(dir)
{
@ -41,31 +42,26 @@ ALenum EnumFromDirection(FShifterDirection dir)
throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast<int>(dir))};
}
void Fshifter_setParamf(EffectProps *props, ALenum param, float val)
constexpr EffectProps genDefaultProps() noexcept
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_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"};
props->Fshifter.Frequency = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
param};
}
FshifterProps props{};
props.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
props.LeftDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION).value();
props.RightDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION).value();
return props;
}
void Fshifter_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Fshifter_setParamf(props, param, vals[0]); }
void Fshifter_setParami(EffectProps *props, ALenum param, int val)
} // namespace
const EffectProps FshifterEffectProps{genDefaultProps()};
void FshifterEffectHandler::SetParami(FshifterProps &props, ALenum param, int val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
if(auto diropt = DirectionFromEmum(val))
props->Fshifter.LeftDirection = *diropt;
props.LeftDirection = *diropt;
else
throw effect_exception{AL_INVALID_VALUE,
"Unsupported frequency shifter left direction: 0x%04x", val};
@ -73,7 +69,7 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val)
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
if(auto diropt = DirectionFromEmum(val))
props->Fshifter.RightDirection = *diropt;
props.RightDirection = *diropt;
else
throw effect_exception{AL_INVALID_VALUE,
"Unsupported frequency shifter right direction: 0x%04x", val};
@ -84,33 +80,17 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val)
"Invalid frequency shifter integer property 0x%04x", param};
}
}
void Fshifter_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Fshifter_setParami(props, param, vals[0]); }
void FshifterEffectHandler::SetParamiv(FshifterProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); }
void Fshifter_getParami(const EffectProps *props, ALenum param, int *val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
*val = EnumFromDirection(props->Fshifter.LeftDirection);
break;
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
*val = EnumFromDirection(props->Fshifter.RightDirection);
break;
default:
throw effect_exception{AL_INVALID_ENUM,
"Invalid frequency shifter integer property 0x%04x", param};
}
}
void Fshifter_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Fshifter_getParami(props, param, vals); }
void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val)
void FshifterEffectHandler::SetParamf(FshifterProps &props, ALenum param, float val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_FREQUENCY:
*val = props->Fshifter.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"};
props.Frequency = val;
break;
default:
@ -118,23 +98,44 @@ void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val)
param};
}
}
void Fshifter_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Fshifter_getParamf(props, param, vals); }
void FshifterEffectHandler::SetParamfv(FshifterProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
EffectProps genDefaultProps() noexcept
void FshifterEffectHandler::GetParami(const FshifterProps &props, ALenum param, int *val)
{
EffectProps props{};
props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
props.Fshifter.LeftDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION);
props.Fshifter.RightDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION);
return props;
switch(param)
{
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
*val = EnumFromDirection(props.LeftDirection);
break;
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
*val = EnumFromDirection(props.RightDirection);
break;
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); }
} // namespace
void FshifterEffectHandler::GetParamf(const FshifterProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_FREQUENCY:
*val = props.Frequency;
break;
DEFINE_ALEFFECT_VTABLE(Fshifter);
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
param};
}
}
void FshifterEffectHandler::GetParamfv(const FshifterProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }
const EffectProps FshifterEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
@ -197,13 +198,9 @@ template<>
throw Exception{message};
}
template<>
bool FrequencyShifterCommitter::commit(const EaxEffectProps &props)
bool EaxFrequencyShifterCommitter::commit(const EAXFREQUENCYSHIFTERPROPERTIES &props)
{
if(props.mType == mEaxProps.mType
&& mEaxProps.mFrequencyShifter.flFrequency == props.mFrequencyShifter.flFrequency
&& mEaxProps.mFrequencyShifter.ulLeftDirection == props.mFrequencyShifter.ulLeftDirection
&& mEaxProps.mFrequencyShifter.ulRightDirection == props.mFrequencyShifter.ulRightDirection)
if(auto *cur = std::get_if<EAXFREQUENCYSHIFTERPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
@ -217,46 +214,52 @@ bool FrequencyShifterCommitter::commit(const EaxEffectProps &props)
return FShifterDirection::Off;
};
mAlProps.Fshifter.Frequency = props.mFrequencyShifter.flFrequency;
mAlProps.Fshifter.LeftDirection = get_direction(props.mFrequencyShifter.ulLeftDirection);
mAlProps.Fshifter.RightDirection = get_direction(props.mFrequencyShifter.ulRightDirection);
mAlProps = [&]{
FshifterProps ret{};
ret.Frequency = props.flFrequency;
ret.LeftDirection = get_direction(props.ulLeftDirection);
ret.RightDirection = get_direction(props.ulRightDirection);
return ret;
}();
return true;
}
template<>
void FrequencyShifterCommitter::SetDefaults(EaxEffectProps &props)
void EaxFrequencyShifterCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::FrequencyShifter;
props.mFrequencyShifter.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY;
props.mFrequencyShifter.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION;
props.mFrequencyShifter.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION;
static constexpr EAXFREQUENCYSHIFTERPROPERTIES defprops{[]
{
EAXFREQUENCYSHIFTERPROPERTIES ret{};
ret.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY;
ret.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION;
ret.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION;
return ret;
}()};
props = defprops;
}
template<>
void FrequencyShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxFrequencyShifterCommitter::Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXFREQUENCYSHIFTER_NONE: break;
case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props.mFrequencyShifter); break;
case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value<Exception>(props.mFrequencyShifter.flFrequency); break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value<Exception>(props.mFrequencyShifter.ulLeftDirection); break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value<Exception>(props.mFrequencyShifter.ulRightDirection); break;
case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value<Exception>(props.flFrequency); break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value<Exception>(props.ulLeftDirection); break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value<Exception>(props.ulRightDirection); break;
default: fail_unknown_property_id();
}
}
template<>
void FrequencyShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxFrequencyShifterCommitter::Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXFREQUENCYSHIFTER_NONE: break;
case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props.mFrequencyShifter); break;
case EAXFREQUENCYSHIFTER_FREQUENCY: defer<FrequencyValidator>(call, props.mFrequencyShifter.flFrequency); break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer<LeftDirectionValidator>(call, props.mFrequencyShifter.ulLeftDirection); break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer<RightDirectionValidator>(call, props.mFrequencyShifter.ulRightDirection); break;
case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXFREQUENCYSHIFTER_FREQUENCY: defer<FrequencyValidator>(call, props.flFrequency); break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer<LeftDirectionValidator>(call, props.ulLeftDirection); break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer<RightDirectionValidator>(call, props.ulRightDirection); break;
default: fail_unknown_property_id();
}
}

View file

@ -1,18 +1,19 @@
#include "config.h"
#include <optional>
#include <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
@ -20,7 +21,7 @@
namespace {
al::optional<ModulatorWaveform> WaveformFromEmum(ALenum value)
constexpr std::optional<ModulatorWaveform> WaveformFromEmum(ALenum value) noexcept
{
switch(value)
{
@ -28,9 +29,9 @@ al::optional<ModulatorWaveform> WaveformFromEmum(ALenum value)
case AL_RING_MODULATOR_SAWTOOTH: return ModulatorWaveform::Sawtooth;
case AL_RING_MODULATOR_SQUARE: return ModulatorWaveform::Square;
}
return al::nullopt;
return std::nullopt;
}
ALenum EnumFromWaveform(ModulatorWaveform type)
constexpr ALenum EnumFromWaveform(ModulatorWaveform type)
{
switch(type)
{
@ -42,40 +43,31 @@ ALenum EnumFromWaveform(ModulatorWaveform type)
std::to_string(static_cast<int>(type))};
}
void Modulator_setParamf(EffectProps *props, ALenum param, float val)
constexpr EffectProps genDefaultProps() noexcept
{
switch(param)
{
case AL_RING_MODULATOR_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};
props->Modulator.Frequency = val;
break;
case AL_RING_MODULATOR_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};
props->Modulator.HighPassCutoff = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param};
}
ModulatorProps props{};
props.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
props.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
props.Waveform = WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM).value();
return props;
}
void Modulator_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Modulator_setParamf(props, param, vals[0]); }
void Modulator_setParami(EffectProps *props, ALenum param, int val)
} // namespace
const EffectProps ModulatorEffectProps{genDefaultProps()};
void ModulatorEffectHandler::SetParami(ModulatorProps &props, ALenum param, int val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
Modulator_setParamf(props, param, static_cast<float>(val));
SetParamf(props, param, static_cast<float>(val));
break;
case AL_RING_MODULATOR_WAVEFORM:
if(auto formopt = WaveformFromEmum(val))
props->Modulator.Waveform = *formopt;
props.Waveform = *formopt;
else
throw effect_exception{AL_INVALID_VALUE, "Invalid modulator waveform: 0x%04x", val};
break;
@ -85,62 +77,61 @@ void Modulator_setParami(EffectProps *props, ALenum param, int val)
param};
}
}
void Modulator_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Modulator_setParami(props, param, vals[0]); }
void ModulatorEffectHandler::SetParamiv(ModulatorProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); }
void Modulator_getParami(const EffectProps *props, ALenum param, int *val)
void ModulatorEffectHandler::SetParamf(ModulatorProps &props, ALenum param, float val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
*val = static_cast<int>(props->Modulator.Frequency);
break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
*val = static_cast<int>(props->Modulator.HighPassCutoff);
break;
case AL_RING_MODULATOR_WAVEFORM:
*val = EnumFromWaveform(props->Modulator.Waveform);
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};
props.Frequency = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x",
param};
}
}
void Modulator_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Modulator_getParami(props, param, vals); }
void Modulator_getParamf(const EffectProps *props, ALenum param, float *val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
*val = props->Modulator.Frequency;
break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
*val = props->Modulator.HighPassCutoff;
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};
props.HighPassCutoff = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param};
}
}
void Modulator_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Modulator_getParamf(props, param, vals); }
void ModulatorEffectHandler::SetParamfv(ModulatorProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
EffectProps genDefaultProps() noexcept
void ModulatorEffectHandler::GetParami(const ModulatorProps &props, ALenum param, int *val)
{
EffectProps props{};
props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
props.Modulator.Waveform = *WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM);
return props;
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY: *val = static_cast<int>(props.Frequency); break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast<int>(props.HighPassCutoff); break;
case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x",
param};
}
}
void ModulatorEffectHandler::GetParamiv(const ModulatorProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); }
void ModulatorEffectHandler::GetParamf(const ModulatorProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY: *val = props.Frequency; break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = props.HighPassCutoff; break;
} // namespace
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param};
}
}
void ModulatorEffectHandler::GetParamfv(const ModulatorProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }
DEFINE_ALEFFECT_VTABLE(Modulator);
const EffectProps ModulatorEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
@ -203,13 +194,9 @@ template<>
throw Exception{message};
}
template<>
bool ModulatorCommitter::commit(const EaxEffectProps &props)
bool EaxModulatorCommitter::commit(const EAXRINGMODULATORPROPERTIES &props)
{
if(props.mType == mEaxProps.mType
&& mEaxProps.mModulator.flFrequency == props.mModulator.flFrequency
&& mEaxProps.mModulator.flHighPassCutOff == props.mModulator.flHighPassCutOff
&& mEaxProps.mModulator.ulWaveform == props.mModulator.ulWaveform)
if(auto *cur = std::get_if<EAXRINGMODULATORPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
@ -225,46 +212,52 @@ bool ModulatorCommitter::commit(const EaxEffectProps &props)
return ModulatorWaveform::Sinusoid;
};
mAlProps.Modulator.Frequency = props.mModulator.flFrequency;
mAlProps.Modulator.HighPassCutoff = props.mModulator.flHighPassCutOff;
mAlProps.Modulator.Waveform = get_waveform(props.mModulator.ulWaveform);
mAlProps = [&]{
ModulatorProps ret{};
ret.Frequency = props.flFrequency;
ret.HighPassCutoff = props.flHighPassCutOff;
ret.Waveform = get_waveform(props.ulWaveform);
return ret;
}();
return true;
}
template<>
void ModulatorCommitter::SetDefaults(EaxEffectProps &props)
void EaxModulatorCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Modulator;
props.mModulator.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY;
props.mModulator.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF;
props.mModulator.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM;
static constexpr EAXRINGMODULATORPROPERTIES defprops{[]
{
EAXRINGMODULATORPROPERTIES ret{};
ret.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY;
ret.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF;
ret.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM;
return ret;
}()};
props = defprops;
}
template<>
void ModulatorCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxModulatorCommitter::Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXRINGMODULATOR_NONE: break;
case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value<Exception>(props.mModulator); break;
case EAXRINGMODULATOR_FREQUENCY: call.set_value<Exception>(props.mModulator.flFrequency); break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value<Exception>(props.mModulator.flHighPassCutOff); break;
case EAXRINGMODULATOR_WAVEFORM: call.set_value<Exception>(props.mModulator.ulWaveform); break;
case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXRINGMODULATOR_FREQUENCY: call.set_value<Exception>(props.flFrequency); break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value<Exception>(props.flHighPassCutOff); break;
case EAXRINGMODULATOR_WAVEFORM: call.set_value<Exception>(props.ulWaveform); break;
default: fail_unknown_property_id();
}
}
template<>
void ModulatorCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxModulatorCommitter::Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props)
{
switch (call.get_property_id())
switch(call.get_property_id())
{
case EAXRINGMODULATOR_NONE: break;
case EAXRINGMODULATOR_ALLPARAMETERS: defer<AllValidator>(call, props.mModulator); break;
case EAXRINGMODULATOR_FREQUENCY: defer<FrequencyValidator>(call, props.mModulator.flFrequency); break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer<HighPassCutOffValidator>(call, props.mModulator.flHighPassCutOff); break;
case EAXRINGMODULATOR_WAVEFORM: defer<WaveformValidator>(call, props.mModulator.ulWaveform); break;
case EAXRINGMODULATOR_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXRINGMODULATOR_FREQUENCY: defer<FrequencyValidator>(call, props.flFrequency); break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer<HighPassCutOffValidator>(call, props.flHighPassCutOff); break;
case EAXRINGMODULATOR_WAVEFORM: defer<WaveformValidator>(call, props.ulWaveform); break;
default: fail_unknown_property_id();
}
}

View file

@ -8,94 +8,92 @@
#include "effects.h"
#ifdef ALSOFT_EAX
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#endif // ALSOFT_EAX
namespace {
void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/)
constexpr EffectProps genDefaultProps() noexcept
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
param};
}
}
void Null_setParamiv(EffectProps *props, ALenum param, const int *vals)
{
switch(param)
{
default:
Null_setParami(props, param, vals[0]);
}
}
void Null_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
param};
}
}
void Null_setParamfv(EffectProps *props, ALenum param, const float *vals)
{
switch(param)
{
default:
Null_setParamf(props, param, vals[0]);
}
}
void Null_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
param};
}
}
void Null_getParamiv(const EffectProps *props, ALenum param, int *vals)
{
switch(param)
{
default:
Null_getParami(props, param, vals);
}
}
void Null_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
param};
}
}
void Null_getParamfv(const EffectProps *props, ALenum param, float *vals)
{
switch(param)
{
default:
Null_getParamf(props, param, vals);
}
}
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
return props;
return std::monostate{};
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Null);
const EffectProps NullEffectProps{genDefaultProps()};
void NullEffectHandler::SetParami(std::monostate& /*props*/, ALenum param, int /*val*/)
{
switch(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)
{
switch(param)
{
default:
SetParami(props, param, *vals);
}
}
void NullEffectHandler::SetParamf(std::monostate& /*props*/, ALenum param, float /*val*/)
{
switch(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)
{
switch(param)
{
default:
SetParamf(props, param, *vals);
}
}
void NullEffectHandler::GetParami(const std::monostate& /*props*/, ALenum param, int* /*val*/)
{
switch(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)
{
switch(param)
{
default:
GetParami(props, param, vals);
}
}
void NullEffectHandler::GetParamf(const std::monostate& /*props*/, ALenum param, float* /*val*/)
{
switch(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)
{
switch(param)
{
default:
GetParamf(props, param, vals);
}
}
#ifdef ALSOFT_EAX
namespace {
@ -117,30 +115,26 @@ template<>
throw Exception{message};
}
template<>
bool NullCommitter::commit(const EaxEffectProps &props)
bool EaxNullCommitter::commit(const std::monostate &props)
{
const bool ret{props.mType != mEaxProps.mType};
const bool ret{std::holds_alternative<std::monostate>(mEaxProps)};
mEaxProps = props;
mAlProps = std::monostate{};
return ret;
}
template<>
void NullCommitter::SetDefaults(EaxEffectProps &props)
void EaxNullCommitter::SetDefaults(EaxEffectProps &props)
{
props = EaxEffectProps{};
props.mType = EaxEffectType::None;
props = std::monostate{};
}
template<>
void NullCommitter::Get(const EaxCall &call, const EaxEffectProps&)
void EaxNullCommitter::Get(const EaxCall &call, const std::monostate&)
{
if(call.get_property_id() != 0)
fail_unknown_property_id();
}
template<>
void NullCommitter::Set(const EaxCall &call, EaxEffectProps&)
void EaxNullCommitter::Set(const EaxCall &call, std::monostate&)
{
if(call.get_property_id() != 0)
fail_unknown_property_id();

View file

@ -9,6 +9,7 @@
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
@ -16,28 +17,32 @@
namespace {
void Pshifter_setParamf(EffectProps*, ALenum param, float)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; }
void Pshifter_setParamfv(EffectProps*, ALenum param, const float*)
constexpr EffectProps genDefaultProps() noexcept
{
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x",
param};
PshifterProps props{};
props.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
props.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
return props;
}
void Pshifter_setParami(EffectProps *props, ALenum param, int val)
} // namespace
const EffectProps PshifterEffectProps{genDefaultProps()};
void PshifterEffectHandler::SetParami(PshifterProps &props, ALenum param, int val)
{
switch(param)
{
case AL_PITCH_SHIFTER_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"};
props->Pshifter.CoarseTune = val;
props.CoarseTune = val;
break;
case AL_PITCH_SHIFTER_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"};
props->Pshifter.FineTune = val;
props.FineTune = val;
break;
default:
@ -45,49 +50,40 @@ void Pshifter_setParami(EffectProps *props, ALenum param, int val)
param};
}
}
void Pshifter_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Pshifter_setParami(props, param, vals[0]); }
void PshifterEffectHandler::SetParamiv(PshifterProps &props, ALenum param, const int *vals)
{ SetParami(props, param, *vals); }
void Pshifter_getParami(const EffectProps *props, ALenum param, int *val)
void PshifterEffectHandler::SetParamf(PshifterProps&, ALenum param, float)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", 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::GetParami(const PshifterProps &props, ALenum param, int *val)
{
switch(param)
{
case AL_PITCH_SHIFTER_COARSE_TUNE:
*val = props->Pshifter.CoarseTune;
break;
case AL_PITCH_SHIFTER_FINE_TUNE:
*val = props->Pshifter.FineTune;
break;
case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; break;
case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
param};
}
}
void Pshifter_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Pshifter_getParami(props, param, vals); }
void PshifterEffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals)
{ GetParami(props, param, vals); }
void Pshifter_getParamf(const EffectProps*, ALenum param, float*)
void PshifterEffectHandler::GetParamf(const PshifterProps&, ALenum param, float*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; }
void Pshifter_getParamfv(const EffectProps*, ALenum param, float*)
void PshifterEffectHandler::GetParamfv(const PshifterProps&, ALenum param, float*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x",
param};
}
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Pshifter);
const EffectProps PshifterEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
@ -138,52 +134,48 @@ template<>
throw Exception{message};
}
template<>
bool PitchShifterCommitter::commit(const EaxEffectProps &props)
bool EaxPitchShifterCommitter::commit(const EAXPITCHSHIFTERPROPERTIES &props)
{
if(props.mType == mEaxProps.mType
&& mEaxProps.mPitchShifter.lCoarseTune == props.mPitchShifter.lCoarseTune
&& mEaxProps.mPitchShifter.lFineTune == props.mPitchShifter.lFineTune)
if(auto *cur = std::get_if<EAXPITCHSHIFTERPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
mAlProps.Pshifter.CoarseTune = static_cast<int>(mEaxProps.mPitchShifter.lCoarseTune);
mAlProps.Pshifter.FineTune = static_cast<int>(mEaxProps.mPitchShifter.lFineTune);
mAlProps = [&]{
PshifterProps ret{};
ret.CoarseTune = static_cast<int>(props.lCoarseTune);
ret.FineTune = static_cast<int>(props.lFineTune);
return ret;
}();
return true;
}
template<>
void PitchShifterCommitter::SetDefaults(EaxEffectProps &props)
void EaxPitchShifterCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::PitchShifter;
props.mPitchShifter.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE;
props.mPitchShifter.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE;
props = EAXPITCHSHIFTERPROPERTIES{EAXPITCHSHIFTER_DEFAULTCOARSETUNE,
EAXPITCHSHIFTER_DEFAULTFINETUNE};
}
template<>
void PitchShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxPitchShifterCommitter::Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXPITCHSHIFTER_NONE: break;
case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props.mPitchShifter); break;
case EAXPITCHSHIFTER_COARSETUNE: call.set_value<Exception>(props.mPitchShifter.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: call.set_value<Exception>(props.mPitchShifter.lFineTune); break;
case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXPITCHSHIFTER_COARSETUNE: call.set_value<Exception>(props.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: call.set_value<Exception>(props.lFineTune); break;
default: fail_unknown_property_id();
}
}
template<>
void PitchShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxPitchShifterCommitter::Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXPITCHSHIFTER_NONE: break;
case EAXPITCHSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props.mPitchShifter); break;
case EAXPITCHSHIFTER_COARSETUNE: defer<CoarseTuneValidator>(call, props.mPitchShifter.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: defer<FineTuneValidator>(call, props.mPitchShifter.lFineTune); break;
case EAXPITCHSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXPITCHSHIFTER_COARSETUNE: defer<CoarseTuneValidator>(call, props.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: defer<FineTuneValidator>(call, props.lFineTune); break;
default: fail_unknown_property_id();
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,18 @@
#include "config.h"
#include <optional>
#include <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "core/effects/base.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
@ -20,7 +20,7 @@
namespace {
al::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val)
constexpr std::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val) noexcept
{
#define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \
return VMorpherPhenome::x
@ -57,10 +57,10 @@ al::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val)
HANDLE_PHENOME(V);
HANDLE_PHENOME(Z);
}
return al::nullopt;
return std::nullopt;
#undef HANDLE_PHENOME
}
ALenum EnumFromPhenome(VMorpherPhenome phenome)
constexpr ALenum EnumFromPhenome(VMorpherPhenome phenome)
{
#define HANDLE_PHENOME(x) case VMorpherPhenome::x: return AL_VOCAL_MORPHER_PHONEME_ ## x
switch(phenome)
@ -100,7 +100,7 @@ ALenum EnumFromPhenome(VMorpherPhenome phenome)
#undef HANDLE_PHENOME
}
al::optional<VMorpherWaveform> WaveformFromEmum(ALenum value)
constexpr std::optional<VMorpherWaveform> WaveformFromEmum(ALenum value) noexcept
{
switch(value)
{
@ -108,9 +108,9 @@ al::optional<VMorpherWaveform> WaveformFromEmum(ALenum value)
case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return VMorpherWaveform::Triangle;
case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return VMorpherWaveform::Sawtooth;
}
return al::nullopt;
return std::nullopt;
}
ALenum EnumFromWaveform(VMorpherWaveform type)
constexpr ALenum EnumFromWaveform(VMorpherWaveform type)
{
switch(type)
{
@ -122,13 +122,29 @@ ALenum EnumFromWaveform(VMorpherWaveform type)
std::to_string(static_cast<int>(type))};
}
void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
constexpr EffectProps genDefaultProps() noexcept
{
VmorpherProps props{};
props.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE;
props.PhonemeA = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA).value();
props.PhonemeB = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB).value();
props.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING;
props.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING;
props.Waveform = WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM).value();
return props;
}
} // namespace
const EffectProps VmorpherEffectProps{genDefaultProps()};
void VmorpherEffectHandler::SetParami(VmorpherProps &props, ALenum param, int val)
{
switch(param)
{
case AL_VOCAL_MORPHER_PHONEMEA:
if(auto phenomeopt = PhenomeFromEnum(val))
props->Vmorpher.PhonemeA = *phenomeopt;
props.PhonemeA = *phenomeopt;
else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val};
break;
@ -136,12 +152,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
case AL_VOCAL_MORPHER_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"};
props->Vmorpher.PhonemeACoarseTuning = val;
props.PhonemeACoarseTuning = val;
break;
case AL_VOCAL_MORPHER_PHONEMEB:
if(auto phenomeopt = PhenomeFromEnum(val))
props->Vmorpher.PhonemeB = *phenomeopt;
props.PhonemeB = *phenomeopt;
else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val};
break;
@ -149,12 +165,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
case AL_VOCAL_MORPHER_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"};
props->Vmorpher.PhonemeBCoarseTuning = val;
props.PhonemeBCoarseTuning = val;
break;
case AL_VOCAL_MORPHER_WAVEFORM:
if(auto formopt = WaveformFromEmum(val))
props->Vmorpher.Waveform = *formopt;
props.Waveform = *formopt;
else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val};
break;
@ -164,19 +180,19 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
param};
}
}
void Vmorpher_setParamiv(EffectProps*, ALenum param, const int*)
void VmorpherEffectHandler::SetParamiv(VmorpherProps&, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x",
param};
}
void Vmorpher_setParamf(EffectProps *props, ALenum param, float val)
void VmorpherEffectHandler::SetParamf(VmorpherProps &props, ALenum param, float val)
{
switch(param)
{
case AL_VOCAL_MORPHER_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"};
props->Vmorpher.Rate = val;
props.Rate = val;
break;
default:
@ -184,49 +200,35 @@ void Vmorpher_setParamf(EffectProps *props, ALenum param, float val)
param};
}
}
void Vmorpher_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Vmorpher_setParamf(props, param, vals[0]); }
void VmorpherEffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals)
{ SetParamf(props, param, *vals); }
void Vmorpher_getParami(const EffectProps *props, ALenum param, int* val)
void VmorpherEffectHandler::GetParami(const VmorpherProps &props, ALenum param, int* val)
{
switch(param)
{
case AL_VOCAL_MORPHER_PHONEMEA:
*val = EnumFromPhenome(props->Vmorpher.PhonemeA);
break;
case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
*val = props->Vmorpher.PhonemeACoarseTuning;
break;
case AL_VOCAL_MORPHER_PHONEMEB:
*val = EnumFromPhenome(props->Vmorpher.PhonemeB);
break;
case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
*val = props->Vmorpher.PhonemeBCoarseTuning;
break;
case AL_VOCAL_MORPHER_WAVEFORM:
*val = EnumFromWaveform(props->Vmorpher.Waveform);
break;
case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); break;
case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; break;
case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); break;
case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; break;
case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
param};
}
}
void Vmorpher_getParamiv(const EffectProps*, ALenum param, int*)
void VmorpherEffectHandler::GetParamiv(const VmorpherProps&, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x",
param};
}
void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val)
void VmorpherEffectHandler::GetParamf(const VmorpherProps &props, ALenum param, float *val)
{
switch(param)
{
case AL_VOCAL_MORPHER_RATE:
*val = props->Vmorpher.Rate;
*val = props.Rate;
break;
default:
@ -234,26 +236,9 @@ void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val)
param};
}
}
void Vmorpher_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Vmorpher_getParamf(props, param, vals); }
void VmorpherEffectHandler::GetParamfv(const VmorpherProps &props, ALenum param, float *vals)
{ GetParamf(props, param, vals); }
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE;
props.Vmorpher.PhonemeA = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA);
props.Vmorpher.PhonemeB = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB);
props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING;
props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING;
props.Vmorpher.Waveform = *WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM);
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Vmorpher);
const EffectProps VmorpherEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
@ -352,16 +337,9 @@ template<>
throw Exception{message};
}
template<>
bool VocalMorpherCommitter::commit(const EaxEffectProps &props)
bool EaxVocalMorpherCommitter::commit(const EAXVOCALMORPHERPROPERTIES &props)
{
if(props.mType == mEaxProps.mType
&& mEaxProps.mVocalMorpher.ulPhonemeA == props.mVocalMorpher.ulPhonemeA
&& mEaxProps.mVocalMorpher.lPhonemeACoarseTuning == props.mVocalMorpher.lPhonemeACoarseTuning
&& mEaxProps.mVocalMorpher.ulPhonemeB == props.mVocalMorpher.ulPhonemeB
&& mEaxProps.mVocalMorpher.lPhonemeBCoarseTuning == props.mVocalMorpher.lPhonemeBCoarseTuning
&& mEaxProps.mVocalMorpher.ulWaveform == props.mVocalMorpher.ulWaveform
&& mEaxProps.mVocalMorpher.flRate == props.mVocalMorpher.flRate)
if(auto *cur = std::get_if<EAXVOCALMORPHERPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
@ -413,107 +391,65 @@ bool VocalMorpherCommitter::commit(const EaxEffectProps &props)
return VMorpherWaveform::Sinusoid;
};
mAlProps.Vmorpher.PhonemeA = get_phoneme(props.mVocalMorpher.ulPhonemeA);
mAlProps.Vmorpher.PhonemeACoarseTuning = static_cast<int>(props.mVocalMorpher.lPhonemeACoarseTuning);
mAlProps.Vmorpher.PhonemeB = get_phoneme(props.mVocalMorpher.ulPhonemeB);
mAlProps.Vmorpher.PhonemeBCoarseTuning = static_cast<int>(props.mVocalMorpher.lPhonemeBCoarseTuning);
mAlProps.Vmorpher.Waveform = get_waveform(props.mVocalMorpher.ulWaveform);
mAlProps.Vmorpher.Rate = props.mVocalMorpher.flRate;
mAlProps = [&]{
VmorpherProps ret{};
ret.PhonemeA = get_phoneme(props.ulPhonemeA);
ret.PhonemeACoarseTuning = static_cast<int>(props.lPhonemeACoarseTuning);
ret.PhonemeB = get_phoneme(props.ulPhonemeB);
ret.PhonemeBCoarseTuning = static_cast<int>(props.lPhonemeBCoarseTuning);
ret.Waveform = get_waveform(props.ulWaveform);
ret.Rate = props.flRate;
return ret;
}();
return true;
}
template<>
void VocalMorpherCommitter::SetDefaults(EaxEffectProps &props)
void EaxVocalMorpherCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::VocalMorpher;
props.mVocalMorpher.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA;
props.mVocalMorpher.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING;
props.mVocalMorpher.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB;
props.mVocalMorpher.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING;
props.mVocalMorpher.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM;
props.mVocalMorpher.flRate = EAXVOCALMORPHER_DEFAULTRATE;
static constexpr EAXVOCALMORPHERPROPERTIES defprops{[]
{
EAXVOCALMORPHERPROPERTIES ret{};
ret.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA;
ret.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING;
ret.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB;
ret.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING;
ret.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM;
ret.flRate = EAXVOCALMORPHER_DEFAULTRATE;
return ret;
}()};
props = defprops;
}
template<>
void VocalMorpherCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
void EaxVocalMorpherCommitter::Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXVOCALMORPHER_NONE:
break;
case EAXVOCALMORPHER_ALLPARAMETERS:
call.set_value<Exception>(props.mVocalMorpher);
break;
case EAXVOCALMORPHER_PHONEMEA:
call.set_value<Exception>(props.mVocalMorpher.ulPhonemeA);
break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
call.set_value<Exception>(props.mVocalMorpher.lPhonemeACoarseTuning);
break;
case EAXVOCALMORPHER_PHONEMEB:
call.set_value<Exception>(props.mVocalMorpher.ulPhonemeB);
break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
call.set_value<Exception>(props.mVocalMorpher.lPhonemeBCoarseTuning);
break;
case EAXVOCALMORPHER_WAVEFORM:
call.set_value<Exception>(props.mVocalMorpher.ulWaveform);
break;
case EAXVOCALMORPHER_RATE:
call.set_value<Exception>(props.mVocalMorpher.flRate);
break;
default:
fail_unknown_property_id();
case EAXVOCALMORPHER_NONE: break;
case EAXVOCALMORPHER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXVOCALMORPHER_PHONEMEA: call.set_value<Exception>(props.ulPhonemeA); break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING: call.set_value<Exception>(props.lPhonemeACoarseTuning); break;
case EAXVOCALMORPHER_PHONEMEB: call.set_value<Exception>(props.ulPhonemeB); break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: call.set_value<Exception>(props.lPhonemeBCoarseTuning); break;
case EAXVOCALMORPHER_WAVEFORM: call.set_value<Exception>(props.ulWaveform); break;
case EAXVOCALMORPHER_RATE: call.set_value<Exception>(props.flRate); break;
default: fail_unknown_property_id();
}
}
template<>
void VocalMorpherCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxVocalMorpherCommitter::Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props)
{
switch(call.get_property_id())
{
case EAXVOCALMORPHER_NONE:
break;
case EAXVOCALMORPHER_ALLPARAMETERS:
defer<AllValidator>(call, props.mVocalMorpher);
break;
case EAXVOCALMORPHER_PHONEMEA:
defer<PhonemeAValidator>(call, props.mVocalMorpher.ulPhonemeA);
break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
defer<PhonemeACoarseTuningValidator>(call, props.mVocalMorpher.lPhonemeACoarseTuning);
break;
case EAXVOCALMORPHER_PHONEMEB:
defer<PhonemeBValidator>(call, props.mVocalMorpher.ulPhonemeB);
break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
defer<PhonemeBCoarseTuningValidator>(call, props.mVocalMorpher.lPhonemeBCoarseTuning);
break;
case EAXVOCALMORPHER_WAVEFORM:
defer<WaveformValidator>(call, props.mVocalMorpher.ulWaveform);
break;
case EAXVOCALMORPHER_RATE:
defer<RateValidator>(call, props.mVocalMorpher.flRate);
break;
default:
fail_unknown_property_id();
case EAXVOCALMORPHER_NONE: break;
case EAXVOCALMORPHER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXVOCALMORPHER_PHONEMEA: defer<PhonemeAValidator>(call, props.ulPhonemeA); break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING: defer<PhonemeACoarseTuningValidator>(call, props.lPhonemeACoarseTuning); break;
case EAXVOCALMORPHER_PHONEMEB: defer<PhonemeBValidator>(call, props.ulPhonemeB); break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: defer<PhonemeBCoarseTuningValidator>(call, props.lPhonemeBCoarseTuning); break;
case EAXVOCALMORPHER_WAVEFORM: defer<WaveformValidator>(call, props.ulWaveform); break;
case EAXVOCALMORPHER_RATE: defer<RateValidator>(call, props.flRate); break;
default: fail_unknown_property_id();
}
}

View file

@ -20,6 +20,8 @@
#include "config.h"
#include "error.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@ -29,27 +31,45 @@
#include <csignal>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <limits>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "AL/al.h"
#include "AL/alc.h"
#include "al/debug.h"
#include "alc/alconfig.h"
#include "alc/context.h"
#include "almalloc.h"
#include "core/except.h"
#include "alc/inprogext.h"
#include "core/logging.h"
#include "opthelpers.h"
#include "vector.h"
#include "strutils.h"
bool TrapALError{false};
namespace al {
context_error::context_error(ALenum 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) */
}
context_error::~context_error() = default;
} /* namespace al */
void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
{
auto message = al::vector<char>(256);
auto message = std::vector<char>(256);
va_list args, args2;
/* 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)};
@ -60,9 +80,15 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
}
va_end(args2);
va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
if(msglen >= 0) msg = message.data();
else msg = "<internal error constructing message>";
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);
@ -77,30 +103,55 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
#endif
}
ALenum curerr{AL_NO_ERROR};
mLastError.compare_exchange_strong(curerr, errorCode);
if(mLastThreadError.get() == AL_NO_ERROR)
mLastThreadError.set(errorCode);
debugMessage(DebugSource::API, DebugType::Error, 0, DebugSeverity::High,
{msg, static_cast<uint>(msglen)});
}
AL_API ALenum AL_APIENTRY alGetError(void)
START_API_FUNC
/* Special-case alGetError since it (potentially) raises a debug signal and
* returns a non-default value for a null context.
*/
AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY
{
static constexpr ALenum deferror{AL_INVALID_OPERATION};
WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror);
if(TrapALError)
{
#ifdef _WIN32
if(IsDebuggerPresent())
DebugBreak();
#elif defined(SIGTRAP)
raise(SIGTRAP);
#endif
}
return deferror;
}
if(auto context = GetContextRef()) LIKELY
return alGetErrorDirect(context.get());
return context->mLastError.exchange(AL_NO_ERROR);
auto get_value = [](const char *envname, const char *optname) -> ALenum
{
auto optstr = al::getenv(envname);
if(!optstr)
optstr = ConfigValueStr({}, "game_compat", optname);
if(optstr)
{
char *end{};
auto value = std::strtoul(optstr->c_str(), &end, 0);
if(end && *end == '\0' && value <= std::numeric_limits<ALenum>::max())
return static_cast<ALenum>(value);
ERR("Invalid default error value: \"%s\"", optstr->c_str());
}
return AL_INVALID_OPERATION;
};
static const ALenum deferror{get_value("__ALSOFT_DEFAULT_ERROR", "default-error")};
WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror);
if(TrapALError)
{
#ifdef _WIN32
if(IsDebuggerPresent())
DebugBreak();
#elif defined(SIGTRAP)
raise(SIGTRAP);
#endif
}
return deferror;
}
FORCE_ALIGN ALenum AL_APIENTRY alGetErrorDirect(ALCcontext *context) noexcept
{
ALenum ret{context->mLastThreadError.get()};
if(ret != AL_NO_ERROR) UNLIKELY
context->mLastThreadError.set(AL_NO_ERROR);
return ret;
}
END_API_FUNC

View file

@ -0,0 +1,27 @@
#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,35 +3,49 @@
#include "event.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cstring>
#include <bitset>
#include <exception>
#include <memory>
#include <mutex>
#include <new>
#include <optional>
#include <string>
#include <string_view>
#include <thread>
#include <tuple>
#include <utility>
#include <variant>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "albyte.h"
#include "alc/context.h"
#include "alc/effects/base.h"
#include "alc/inprogext.h"
#include "almalloc.h"
#include "alsem.h"
#include "alspan.h"
#include "core/async_event.h"
#include "core/except.h"
#include "core/context.h"
#include "core/effects/base.h"
#include "core/logging.h"
#include "core/voice_change.h"
#include "debug.h"
#include "direct_defs.h"
#include "error.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "ringbuffer.h"
#include "threads.h"
static int EventThread(ALCcontext *context)
namespace {
template<typename... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<typename... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
int EventThread(ALCcontext *context)
{
RingBuffer *ring{context->mAsyncEvents.get()};
bool quitnow{false};
@ -44,76 +58,98 @@ static int EventThread(ALCcontext *context)
continue;
}
std::lock_guard<std::mutex> _{context->mEventCbLock};
do {
auto *evt_ptr = reinterpret_cast<AsyncEvent*>(evt_data.buf);
evt_data.buf += sizeof(AsyncEvent);
evt_data.len -= 1;
AsyncEvent evt{*evt_ptr};
al::destroy_at(evt_ptr);
ring->readAdvance(1);
quitnow = evt.EnumType == AsyncEvent::KillThread;
std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
auto evt_span = al::span{std::launder(reinterpret_cast<AsyncEvent*>(evt_data.buf)),
evt_data.len};
for(auto &event : evt_span)
{
quitnow = std::holds_alternative<AsyncKillThread>(event);
if(quitnow) UNLIKELY break;
if(evt.EnumType == AsyncEvent::ReleaseEffectState)
{
al::intrusive_ptr<EffectState>{evt.u.mEffectState};
continue;
}
auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire);
if(!context->mEventCb || !enabledevts.test(evt.EnumType))
continue;
if(evt.EnumType == AsyncEvent::SourceStateChange)
auto proc_killthread = [](AsyncKillThread&) { };
auto proc_release = [](AsyncEffectReleaseEvent &evt)
{
al::intrusive_ptr<EffectState>{evt.mEffectState};
};
auto proc_srcstate = [context,enabledevts](AsyncSourceStateEvent &evt)
{
if(!context->mEventCb
|| !enabledevts.test(al::to_underlying(AsyncEnableBits::SourceState)))
return;
ALuint state{};
std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)};
std::string msg{"Source ID " + std::to_string(evt.mId)};
msg += " state has changed to ";
switch(evt.u.srcstate.state)
switch(evt.mState)
{
case AsyncEvent::SrcState::Reset:
case AsyncSrcState::Reset:
msg += "AL_INITIAL";
state = AL_INITIAL;
break;
case AsyncEvent::SrcState::Stop:
case AsyncSrcState::Stop:
msg += "AL_STOPPED";
state = AL_STOPPED;
break;
case AsyncEvent::SrcState::Play:
case AsyncSrcState::Play:
msg += "AL_PLAYING";
state = AL_PLAYING;
break;
case AsyncEvent::SrcState::Pause:
case AsyncSrcState::Pause:
msg += "AL_PAUSED";
state = AL_PAUSED;
break;
}
context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id,
state, static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam);
}
else if(evt.EnumType == AsyncEvent::BufferCompleted)
context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state,
static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam);
};
auto proc_buffercomp = [context,enabledevts](AsyncBufferCompleteEvent &evt)
{
std::string msg{std::to_string(evt.u.bufcomp.count)};
if(evt.u.bufcomp.count == 1) msg += " buffer completed";
if(!context->mEventCb
|| !enabledevts.test(al::to_underlying(AsyncEnableBits::BufferCompleted)))
return;
std::string msg{std::to_string(evt.mCount)};
if(evt.mCount == 1) msg += " buffer completed";
else msg += " buffers completed";
context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id,
evt.u.bufcomp.count, static_cast<ALsizei>(msg.length()), msg.c_str(),
context->mEventParam);
}
else if(evt.EnumType == AsyncEvent::Disconnected)
context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount,
static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam);
};
auto proc_disconnect = [context,enabledevts](AsyncDisconnectEvent &evt)
{
context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0,
static_cast<ALsizei>(strlen(evt.u.disconnect.msg)), evt.u.disconnect.msg,
context->mEventParam);
}
} while(evt_data.len != 0);
context->debugMessage(DebugSource::System, DebugType::Error, 0,
DebugSeverity::High, evt.msg);
if(context->mEventCb
&& enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected)))
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,
proc_killthread}, event);
}
std::destroy(evt_span.begin(), evt_span.end());
ring->readAdvance(evt_span.size());
}
return 0;
}
constexpr std::optional<AsyncEnableBits> GetEventType(ALenum etype) noexcept
{
switch(etype)
{
case AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT: return AsyncEnableBits::BufferCompleted;
case AL_EVENT_TYPE_DISCONNECTED_SOFT: return AsyncEnableBits::Disconnected;
case AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT: return AsyncEnableBits::SourceState;
}
return std::nullopt;
}
} // namespace
void StartEventThrd(ALCcontext *ctx)
{
try {
@ -138,7 +174,7 @@ void StopEventThrd(ALCcontext *ctx)
evt_data = ring->getWriteVector().first;
} while(evt_data.len == 0);
}
al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), AsyncEvent::KillThread);
std::ignore = InitAsyncEvent<AsyncKillThread>(evt_data.buf);
ring->writeAdvance(1);
ctx->mEventSem.post();
@ -146,34 +182,25 @@ void StopEventThrd(ALCcontext *ctx)
ctx->mEventThread.join();
}
AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
AL_API DECL_FUNCEXT3(void, alEventControl,SOFT, ALsizei,count, const ALenum*,types, ALboolean,enable)
FORCE_ALIGN void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count,
const ALenum *types, ALboolean enable) noexcept
try {
if(count < 0)
throw al::context_error{AL_INVALID_VALUE, "Controlling %d events", count};
if(count <= 0) UNLIKELY return;
if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count);
if(count <= 0) return;
if(!types) return context->setError(AL_INVALID_VALUE, "NULL pointer");
if(!types)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
ContextBase::AsyncEventBitset flags{};
const ALenum *types_end = types+count;
auto bad_type = std::find_if_not(types, types_end,
[&flags](ALenum type) noexcept -> bool
{
if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT)
flags.set(AsyncEvent::BufferCompleted);
else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT)
flags.set(AsyncEvent::SourceStateChange);
else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT)
flags.set(AsyncEvent::Disconnected);
else
return false;
return true;
}
);
if(bad_type != types_end)
return context->setError(AL_INVALID_ENUM, "Invalid event type 0x%04x", *bad_type);
for(ALenum evttype : al::span{types, static_cast<uint>(count)})
{
auto etype = GetEventType(evttype);
if(!etype)
throw al::context_error{AL_INVALID_ENUM, "Invalid event type 0x%04x", evttype};
flags.set(al::to_underlying(*etype));
}
if(enable)
{
@ -196,20 +223,18 @@ START_API_FUNC
/* Wait to ensure the event handler sees the changed flags before
* returning.
*/
std::lock_guard<std::mutex> _{context->mEventCbLock};
std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
}
}
END_API_FUNC
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam)
START_API_FUNC
AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam)
FORCE_ALIGN void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context,
ALEVENTPROCSOFT callback, void *userParam) noexcept
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> __{context->mEventCbLock};
std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
context->mEventCb = callback;
context->mEventParam = userParam;
}
END_API_FUNC

View file

@ -20,63 +20,59 @@
#include "config.h"
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <string_view>
#include <vector>
#include "AL/al.h"
#include "AL/alc.h"
#include "alc/context.h"
#include "alc/inprogext.h"
#include "alstring.h"
#include "core/except.h"
#include "direct_defs.h"
#include "opthelpers.h"
AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName)
START_API_FUNC
AL_API DECL_FUNC1(ALboolean, alIsExtensionPresent, const ALchar*,extName)
FORCE_ALIGN ALboolean AL_APIENTRY alIsExtensionPresentDirect(ALCcontext *context, const ALchar *extName) noexcept
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return AL_FALSE;
if(!extName) UNLIKELY
{
context->setError(AL_INVALID_VALUE, "NULL pointer");
return AL_FALSE;
}
size_t len{strlen(extName)};
const char *ptr{context->mExtensionList};
while(ptr && *ptr)
const std::string_view tofind{extName};
for(std::string_view ext : context->mExtensions)
{
if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len])))
if(al::case_compare(ext, tofind) == 0)
return AL_TRUE;
if((ptr=strchr(ptr, ' ')) != nullptr)
{
do {
++ptr;
} while(isspace(*ptr));
}
}
return AL_FALSE;
}
END_API_FUNC
AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName)
START_API_FUNC
AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName) noexcept
{
if(!funcName) return nullptr;
return alcGetProcAddress(nullptr, funcName);
}
END_API_FUNC
AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName)
START_API_FUNC
FORCE_ALIGN ALvoid* AL_APIENTRY alGetProcAddressDirect(ALCcontext*, const ALchar *funcName) noexcept
{
if(!enumName) return static_cast<ALenum>(0);
if(!funcName) return nullptr;
return alcGetProcAddress(nullptr, funcName);
}
AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName) noexcept
{
if(!enumName) return ALenum{0};
return alcGetEnumValue(nullptr, enumName);
}
FORCE_ALIGN ALenum AL_APIENTRY alGetEnumValueDirect(ALCcontext*, const ALchar *enumName) noexcept
{
if(!enumName) return ALenum{0};
return alcGetEnumValue(nullptr, enumName);
}
END_API_FUNC

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,40 @@
#ifndef AL_FILTER_H
#define AL_FILTER_H
#include <array>
#include <cstdint>
#include <string_view>
#include <utility>
#include <variant>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "AL/efx.h"
#include "almalloc.h"
#include "alnumeric.h"
#define LOWPASSFREQREF 5000.0f
#define HIGHPASSFREQREF 250.0f
inline constexpr float LowPassFreqRef{5000.0f};
inline constexpr float HighPassFreqRef{250.0f};
template<typename T>
struct FilterTable {
static void setParami(struct ALfilter*, ALenum, int);
static void setParamiv(struct ALfilter*, ALenum, const int*);
static void setParamf(struct ALfilter*, ALenum, float);
static void setParamfv(struct ALfilter*, ALenum, const float*);
static void getParami(const struct ALfilter*, ALenum, int*);
static void getParamiv(const struct ALfilter*, ALenum, int*);
static void getParamf(const struct ALfilter*, ALenum, float*);
static void getParamfv(const struct ALfilter*, ALenum, float*);
};
struct NullFilterTable : public FilterTable<NullFilterTable> { };
struct LowpassFilterTable : public FilterTable<LowpassFilterTable> { };
struct HighpassFilterTable : public FilterTable<HighpassFilterTable> { };
struct BandpassFilterTable : public FilterTable<BandpassFilterTable> { };
struct ALfilter {
@ -17,36 +42,35 @@ struct ALfilter {
float Gain{1.0f};
float GainHF{1.0f};
float HFReference{LOWPASSFREQREF};
float HFReference{LowPassFreqRef};
float GainLF{1.0f};
float LFReference{HIGHPASSFREQREF};
float LFReference{HighPassFreqRef};
struct Vtable {
void (*const setParami )(ALfilter *filter, ALenum param, int val);
void (*const setParamiv)(ALfilter *filter, ALenum param, const int *vals);
void (*const setParamf )(ALfilter *filter, ALenum param, float val);
void (*const setParamfv)(ALfilter *filter, ALenum param, const float *vals);
void (*const getParami )(const ALfilter *filter, ALenum param, int *val);
void (*const getParamiv)(const ALfilter *filter, ALenum param, int *vals);
void (*const getParamf )(const ALfilter *filter, ALenum param, float *val);
void (*const getParamfv)(const ALfilter *filter, ALenum param, float *vals);
};
const Vtable *vtab{nullptr};
using TableTypes = std::variant<NullFilterTable,LowpassFilterTable,HighpassFilterTable,
BandpassFilterTable>;
TableTypes mTypeVariant;
/* Self ID */
ALuint id{0};
void setParami(ALenum param, int value) { vtab->setParami(this, param, value); }
void setParamiv(ALenum param, const int *values) { vtab->setParamiv(this, param, values); }
void setParamf(ALenum param, float value) { vtab->setParamf(this, param, value); }
void setParamfv(ALenum param, const float *values) { vtab->setParamfv(this, param, values); }
void getParami(ALenum param, int *value) const { vtab->getParami(this, param, value); }
void getParamiv(ALenum param, int *values) const { vtab->getParamiv(this, param, values); }
void getParamf(ALenum param, float *value) const { vtab->getParamf(this, param, value); }
void getParamfv(ALenum param, float *values) const { vtab->getParamfv(this, param, values); }
static void SetName(ALCcontext *context, ALuint id, std::string_view name);
DISABLE_ALLOC()
DISABLE_ALLOC
};
struct FilterSubList {
uint64_t FreeMask{~0_u64};
gsl::owner<std::array<ALfilter,64>*> Filters{nullptr};
FilterSubList() noexcept = default;
FilterSubList(const FilterSubList&) = delete;
FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters}
{ rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; }
~FilterSubList();
FilterSubList& operator=(const FilterSubList&) = delete;
FilterSubList& operator=(FilterSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; }
};
#endif

View file

@ -22,6 +22,7 @@
#include "listener.h"
#include <algorithm>
#include <cmath>
#include <mutex>
@ -30,9 +31,10 @@
#include "AL/efx.h"
#include "alc/context.h"
#include "almalloc.h"
#include "atomic.h"
#include "core/except.h"
#include "alc/inprogext.h"
#include "alspan.h"
#include "direct_defs.h"
#include "error.h"
#include "opthelpers.h"
@ -68,377 +70,333 @@ inline void CommitAndUpdateProps(ALCcontext *context)
} // namespace
AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
AL_API DECL_FUNC2(void, alListenerf, ALenum,param, ALfloat,value)
FORCE_ALIGN void AL_APIENTRY alListenerfDirect(ALCcontext *context, ALenum param, ALfloat value) noexcept
try {
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> proplock{context->mPropLock};
switch(param)
{
case AL_GAIN:
if(!(value >= 0.0f && std::isfinite(value)))
return context->setError(AL_INVALID_VALUE, "Listener gain out of range");
throw al::context_error{AL_INVALID_VALUE, "Listener gain out of range"};
listener.Gain = value;
UpdateProps(context.get());
break;
UpdateProps(context);
return;
case AL_METERS_PER_UNIT:
if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
return context->setError(AL_INVALID_VALUE, "Listener meters per unit out of range");
throw al::context_error{AL_INVALID_VALUE, "Listener meters per unit out of range"};
listener.mMetersPerUnit = value;
UpdateProps(context.get());
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener float property");
UpdateProps(context);
return;
}
throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3)
FORCE_ALIGN void AL_APIENTRY alListener3fDirect(ALCcontext *context, ALenum param, ALfloat value1,
ALfloat value2, ALfloat value3) noexcept
try {
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> proplock{context->mPropLock};
switch(param)
{
case AL_POSITION:
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
return context->setError(AL_INVALID_VALUE, "Listener position out of range");
throw al::context_error{AL_INVALID_VALUE, "Listener position out of range"};
listener.Position[0] = value1;
listener.Position[1] = value2;
listener.Position[2] = value3;
CommitAndUpdateProps(context.get());
break;
CommitAndUpdateProps(context);
return;
case AL_VELOCITY:
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
return context->setError(AL_INVALID_VALUE, "Listener velocity out of range");
throw al::context_error{AL_INVALID_VALUE, "Listener velocity out of range"};
listener.Velocity[0] = value1;
listener.Velocity[1] = value2;
listener.Velocity[2] = value3;
CommitAndUpdateProps(context.get());
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
CommitAndUpdateProps(context);
return;
}
throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values)
START_API_FUNC
{
if(values)
AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values)
FORCE_ALIGN void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum param,
const ALfloat *values) noexcept
try {
if(!values)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
switch(param)
{
switch(param)
{
case AL_GAIN:
case AL_METERS_PER_UNIT:
alListenerf(param, values[0]);
return;
case AL_GAIN:
case AL_METERS_PER_UNIT:
alListenerfDirect(context, param, *values);
return;
case AL_POSITION:
case AL_VELOCITY:
alListener3f(param, values[0], values[1], values[2]);
return;
}
case AL_POSITION:
case AL_VELOCITY:
auto vals = al::span<const float,3>{values, 3_uz};
alListener3fDirect(context, param, vals[0], vals[1], vals[2]);
return;
}
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
if(!values) UNLIKELY
return context->setError(AL_INVALID_VALUE, "NULL pointer");
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> proplock{context->mPropLock};
switch(param)
{
case AL_ORIENTATION:
if(!(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) &&
std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])))
auto vals = al::span<const float,6>{values, 6_uz};
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");
/* AT then UP */
listener.OrientAt[0] = values[0];
listener.OrientAt[1] = values[1];
listener.OrientAt[2] = values[2];
listener.OrientUp[0] = values[3];
listener.OrientUp[1] = values[4];
listener.OrientUp[2] = values[5];
CommitAndUpdateProps(context.get());
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
std::copy_n(vals.cbegin(), 3, listener.OrientAt.begin());
std::copy_n(vals.cbegin()+3, 3, listener.OrientUp.begin());
CommitAndUpdateProps(context);
return;
}
throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param};
}
END_API_FUNC
AL_API void AL_APIENTRY alListeneri(ALenum param, ALint /*value*/)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3)
START_API_FUNC
{
AL_API DECL_FUNC2(void, alListeneri, ALenum,param, ALint,value)
FORCE_ALIGN void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint /*value*/) noexcept
try {
std::lock_guard<std::mutex> proplock{context->mPropLock};
throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
AL_API DECL_FUNC4(void, alListener3i, ALenum,param, ALint,value1, ALint,value2, ALint,value3)
FORCE_ALIGN void AL_APIENTRY alListener3iDirect(ALCcontext *context, ALenum param, ALint value1,
ALint value2, ALint value3) noexcept
try {
switch(param)
{
case AL_POSITION:
case AL_VELOCITY:
alListener3f(param, static_cast<ALfloat>(value1), static_cast<ALfloat>(value2),
static_cast<ALfloat>(value3));
alListener3fDirect(context, param, static_cast<ALfloat>(value1),
static_cast<ALfloat>(value2), static_cast<ALfloat>(value3));
return;
}
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
std::lock_guard<std::mutex> proplock{context->mPropLock};
throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
std::lock_guard<std::mutex> _{context->mPropLock};
AL_API DECL_FUNC2(void, alListeneriv, ALenum,param, const ALint*,values)
FORCE_ALIGN void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum param,
const ALint *values) noexcept
try {
if(!values)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
al::span<const ALint> vals;
switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
case AL_POSITION:
case AL_VELOCITY:
vals = {values, 3_uz};
alListener3fDirect(context, param, static_cast<ALfloat>(vals[0]),
static_cast<ALfloat>(vals[1]), static_cast<ALfloat>(vals[2]));
return;
case AL_ORIENTATION:
vals = {values, 6_uz};
const std::array fvals{static_cast<ALfloat>(vals[0]), static_cast<ALfloat>(vals[1]),
static_cast<ALfloat>(vals[2]), static_cast<ALfloat>(vals[3]),
static_cast<ALfloat>(vals[4]), static_cast<ALfloat>(vals[5]),
};
alListenerfvDirect(context, param, fvals.data());
return;
}
std::lock_guard<std::mutex> proplock{context->mPropLock};
throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x",
param};
}
END_API_FUNC
AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values)
START_API_FUNC
{
if(values)
{
ALfloat fvals[6];
switch(param)
{
case AL_POSITION:
case AL_VELOCITY:
alListener3f(param, static_cast<ALfloat>(values[0]), static_cast<ALfloat>(values[1]),
static_cast<ALfloat>(values[2]));
return;
case AL_ORIENTATION:
fvals[0] = static_cast<ALfloat>(values[0]);
fvals[1] = static_cast<ALfloat>(values[1]);
fvals[2] = static_cast<ALfloat>(values[2]);
fvals[3] = static_cast<ALfloat>(values[3]);
fvals[4] = static_cast<ALfloat>(values[4]);
fvals[5] = static_cast<ALfloat>(values[5]);
alListenerfv(param, fvals);
return;
}
}
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!values) UNLIKELY
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
AL_API DECL_FUNC2(void, alGetListenerf, ALenum,param, ALfloat*,value)
FORCE_ALIGN void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum param,
ALfloat *value) noexcept
try {
if(!value)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
{
case AL_GAIN:
*value = listener.Gain;
break;
case AL_METERS_PER_UNIT:
*value = listener.mMetersPerUnit;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener float property");
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> proplock{context->mPropLock};
switch(param)
{
case AL_GAIN: *value = listener.Gain; return;
case AL_METERS_PER_UNIT: *value = listener.mMetersPerUnit; return;
}
throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
AL_API DECL_FUNC4(void, alGetListener3f, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3)
FORCE_ALIGN void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum param,
ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept
try {
if(!value1 || !value2 || !value3)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> proplock{context->mPropLock};
switch(param)
{
case AL_POSITION:
*value1 = listener.Position[0];
*value2 = listener.Position[1];
*value3 = listener.Position[2];
break;
return;
case AL_VELOCITY:
*value1 = listener.Velocity[0];
*value2 = listener.Velocity[1];
*value3 = listener.Velocity[2];
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
return;
}
throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values)
START_API_FUNC
{
AL_API DECL_FUNC2(void, alGetListenerfv, ALenum,param, ALfloat*,values)
FORCE_ALIGN void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum param,
ALfloat *values) noexcept
try {
if(!values)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
switch(param)
{
case AL_GAIN:
case AL_METERS_PER_UNIT:
alGetListenerf(param, values);
alGetListenerfDirect(context, param, values);
return;
case AL_POSITION:
case AL_VELOCITY:
alGetListener3f(param, values+0, values+1, values+2);
auto vals = al::span<ALfloat,3>{values, 3_uz};
alGetListener3fDirect(context, param, &vals[0], &vals[1], &vals[2]);
return;
}
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
std::lock_guard<std::mutex> proplock{context->mPropLock};
switch(param)
{
case AL_ORIENTATION:
al::span<ALfloat,6> vals{values, 6_uz};
// AT then UP
values[0] = listener.OrientAt[0];
values[1] = listener.OrientAt[1];
values[2] = listener.OrientAt[2];
values[3] = listener.OrientUp[0];
values[4] = listener.OrientUp[1];
values[5] = listener.OrientUp[2];
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
std::copy_n(listener.OrientAt.cbegin(), 3, vals.begin());
std::copy_n(listener.OrientUp.cbegin(), 3, vals.begin()+3);
return;
}
throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param};
}
END_API_FUNC
AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!value)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
AL_API DECL_FUNC2(void, alGetListeneri, ALenum,param, ALint*,value)
FORCE_ALIGN void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) noexcept
try {
if(!value) throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
std::lock_guard<std::mutex> proplock{context->mPropLock};
throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
AL_API DECL_FUNC4(void, alGetListener3i, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3)
FORCE_ALIGN void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum param,
ALint *value1, ALint *value2, ALint *value3) noexcept
try {
if(!value1 || !value2 || !value3)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!value1 || !value2 || !value3)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
std::lock_guard<std::mutex> proplock{context->mPropLock};
switch(param)
{
case AL_POSITION:
*value1 = static_cast<ALint>(listener.Position[0]);
*value2 = static_cast<ALint>(listener.Position[1]);
*value3 = static_cast<ALint>(listener.Position[2]);
break;
return;
case AL_VELOCITY:
*value1 = static_cast<ALint>(listener.Velocity[0]);
*value2 = static_cast<ALint>(listener.Velocity[1]);
*value3 = static_cast<ALint>(listener.Velocity[2]);
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
return;
}
throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC
AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint* values)
START_API_FUNC
{
AL_API DECL_FUNC2(void, alGetListeneriv, ALenum,param, ALint*,values)
FORCE_ALIGN void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum param,
ALint *values) noexcept
try {
if(!values)
throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
switch(param)
{
case AL_POSITION:
case AL_VELOCITY:
alGetListener3i(param, values+0, values+1, values+2);
auto vals = al::span<ALint,3>{values, 3_uz};
alGetListener3iDirect(context, param, &vals[0], &vals[1], &vals[2]);
return;
}
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
std::lock_guard<std::mutex> proplock{context->mPropLock};
static constexpr auto f2i = [](const float val) noexcept { return static_cast<ALint>(val); };
switch(param)
{
case AL_ORIENTATION:
auto vals = al::span<ALint,6>{values, 6_uz};
// AT then UP
values[0] = static_cast<ALint>(listener.OrientAt[0]);
values[1] = static_cast<ALint>(listener.OrientAt[1]);
values[2] = static_cast<ALint>(listener.OrientAt[2]);
values[3] = static_cast<ALint>(listener.OrientUp[0]);
values[4] = static_cast<ALint>(listener.OrientUp[1]);
values[5] = static_cast<ALint>(listener.OrientUp[2]);
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
std::transform(listener.OrientAt.cbegin(), listener.OrientAt.cend(), vals.begin(), f2i);
std::transform(listener.OrientUp.cbegin(), listener.OrientUp.cend(), vals.begin()+3, f2i);
return;
}
throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x",
param};
}
catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
}
END_API_FUNC

View file

@ -3,8 +3,6 @@
#include <array>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "almalloc.h"
@ -18,7 +16,7 @@ struct ALlistener {
float Gain{1.0f};
float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT};
DISABLE_ALLOC()
DISABLE_ALLOC
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -2,26 +2,26 @@
#define AL_SOURCE_H
#include <array>
#include <atomic>
#include <cstddef>
#include <iterator>
#include <limits>
#include <cstdint>
#include <deque>
#include <limits>
#include <string_view>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "alc/alu.h"
#include "alc/context.h"
#include "alc/inprogext.h"
#include "aldeque.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "atomic.h"
#include "alspan.h"
#include "core/context.h"
#include "core/voice.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include "eax/api.h"
#include "eax/call.h"
#include "eax/exception.h"
#include "eax/fx_slot_index.h"
@ -30,23 +30,23 @@
struct ALbuffer;
struct ALeffectslot;
enum class Resampler : uint8_t;
enum class SourceStereo : bool {
Normal = AL_NORMAL_SOFT,
Enhanced = AL_SUPER_STEREO_SOFT
};
#define DEFAULT_SENDS 2
inline constexpr size_t DefaultSendCount{2};
#define INVALID_VOICE_IDX static_cast<ALuint>(-1)
inline constexpr ALuint InvalidVoiceIndex{std::numeric_limits<ALuint>::max()};
extern bool sBufferSubDataCompat;
inline bool sBufferSubDataCompat{false};
struct ALbufferQueueItem : public VoiceBufferItem {
ALbuffer *mBuffer{nullptr};
DISABLE_ALLOC()
DISABLE_ALLOC
};
@ -88,6 +88,7 @@ struct ALsource {
DirectMode DirectChannels{DirectMode::Off};
SpatializeMode mSpatialize{SpatializeMode::Auto};
SourceStereo mStereoMode{SourceStereo::Normal};
bool mPanningEnabled{false};
bool DryGainHFAuto{true};
bool WetGainAuto{true};
@ -105,24 +106,27 @@ struct ALsource {
float Radius{0.0f};
float EnhWidth{0.593f};
float mPan{0.0f};
/** Direct filter and auxiliary send info. */
struct {
float Gain;
float GainHF;
float HFReference;
float GainLF;
float LFReference;
} Direct;
struct SendData {
ALeffectslot *Slot;
float Gain;
float GainHF;
float HFReference;
float GainLF;
float LFReference;
struct DirectData {
float Gain{};
float GainHF{};
float HFReference{};
float GainLF{};
float LFReference{};
};
std::array<SendData,MAX_SENDS> Send;
DirectData Direct;
struct SendData {
ALeffectslot *Slot{};
float Gain{};
float GainHF{};
float HFReference{};
float GainLF{};
float LFReference{};
};
std::array<SendData,MaxSendCount> Send;
/**
* Last user-specified offset, and the offset type (bytes, samples, or
@ -138,26 +142,28 @@ struct ALsource {
ALenum state{AL_INITIAL};
/** Source Buffer Queue head. */
al::deque<ALbufferQueueItem> mQueue;
std::deque<ALbufferQueueItem> mQueue;
bool mPropsDirty{true};
/* Index into the context's Voices array. Lazily updated, only checked and
* reset when looking up the voice.
*/
ALuint VoiceIdx{INVALID_VOICE_IDX};
ALuint VoiceIdx{InvalidVoiceIndex};
/** Self ID */
ALuint id{0};
ALsource();
ALsource() noexcept;
~ALsource();
ALsource(const ALsource&) = delete;
ALsource& operator=(const ALsource&) = delete;
DISABLE_ALLOC()
static void SetName(ALCcontext *context, ALuint id, std::string_view name);
DISABLE_ALLOC
#ifdef ALSOFT_EAX
public:
@ -171,18 +177,18 @@ public:
private:
using Exception = EaxSourceException;
static constexpr auto eax_max_speakers = 9;
static constexpr auto eax_max_speakers{9u};
using EaxFxSlotIds = const GUID* [EAX_MAX_FXSLOTS];
using EaxFxSlotIds = std::array<const GUID*,EAX_MAX_FXSLOTS>;
static constexpr const EaxFxSlotIds eax4_fx_slot_ids = {
static constexpr const EaxFxSlotIds eax4_fx_slot_ids{
&EAXPROPERTYID_EAX40_FXSlot0,
&EAXPROPERTYID_EAX40_FXSlot1,
&EAXPROPERTYID_EAX40_FXSlot2,
&EAXPROPERTYID_EAX40_FXSlot3,
};
static constexpr const EaxFxSlotIds eax5_fx_slot_ids = {
static constexpr const EaxFxSlotIds eax5_fx_slot_ids{
&EAXPROPERTYID_EAX50_FXSlot0,
&EAXPROPERTYID_EAX50_FXSlot1,
&EAXPROPERTYID_EAX50_FXSlot2,
@ -215,11 +221,6 @@ private:
Eax3Props source;
EaxSends sends;
EAX40ACTIVEFXSLOTS active_fx_slots;
bool operator==(const Eax4Props& rhs) noexcept
{
return std::memcmp(this, &rhs, sizeof(Eax4Props)) == 0;
}
};
struct Eax4State {
@ -232,11 +233,6 @@ private:
EaxSends sends;
EAX50ACTIVEFXSLOTS active_fx_slots;
EaxSpeakerLevels speaker_levels;
bool operator==(const Eax5Props& rhs) noexcept
{
return std::memcmp(this, &rhs, sizeof(Eax5Props)) == 0;
}
};
struct Eax5State {
@ -546,7 +542,24 @@ private:
struct Eax5SourceAllValidator {
void operator()(const EAX50SOURCEPROPERTIES& props) const
{
Eax3SourceAllValidator{}(static_cast<const Eax3Props&>(props));
Eax2SourceDirectValidator{}(props.lDirect);
Eax2SourceDirectHfValidator{}(props.lDirectHF);
Eax2SourceRoomValidator{}(props.lRoom);
Eax2SourceRoomHfValidator{}(props.lRoomHF);
Eax2SourceObstructionValidator{}(props.lObstruction);
Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio);
Eax2SourceOcclusionValidator{}(props.lOcclusion);
Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio);
Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio);
Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio);
Eax3SourceExclusionValidator{}(props.lExclusion);
Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio);
Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF);
Eax3SourceDopplerFactorValidator{}(props.flDopplerFactor);
Eax3SourceRolloffFactorValidator{}(props.flRolloffFactor);
Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor);
Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor);
Eax5SourceFlagsValidator{}(props.ulFlags);
Eax5SourceMacroFXFactorValidator{}(props.flMacroFXFactor);
}
};
@ -809,39 +822,38 @@ private:
[[noreturn]] static void eax_fail_unknown_active_fx_slot_id();
[[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id();
void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept;
void eax1_set_defaults(Eax1Props& props) noexcept;
static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept;
static void eax1_set_defaults(Eax1Props& props) noexcept;
void eax1_set_defaults() noexcept;
void eax2_set_defaults(Eax2Props& props) noexcept;
static void eax2_set_defaults(Eax2Props& props) noexcept;
void eax2_set_defaults() noexcept;
void eax3_set_defaults(Eax3Props& props) noexcept;
static void eax3_set_defaults(Eax3Props& props) noexcept;
void eax3_set_defaults() noexcept;
void eax4_set_sends_defaults(EaxSends& sends) noexcept;
void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept;
static void eax4_set_sends_defaults(EaxSends& sends) noexcept;
static void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept;
void eax4_set_defaults() noexcept;
void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept;
void eax5_set_sends_defaults(EaxSends& sends) noexcept;
void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept;
void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept;
void eax5_set_defaults(Eax5Props& props) noexcept;
static void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept;
static void eax5_set_sends_defaults(EaxSends& sends) noexcept;
static void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept;
static void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept;
static void eax5_set_defaults(Eax5Props& props) noexcept;
void eax5_set_defaults() noexcept;
void eax_set_defaults() noexcept;
void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept;
void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept;
void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept;
void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept;
static void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept;
static void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept;
static void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept;
static void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept;
static float eax_calculate_dst_occlusion_mb(
long src_occlusion_mb,
float path_ratio,
float lf_ratio) noexcept;
EaxAlLowPassParam eax_create_direct_filter_param() const noexcept;
[[nodiscard]] auto eax_create_direct_filter_param() const noexcept -> EaxAlLowPassParam;
EaxAlLowPassParam eax_create_room_filter_param(
const ALeffectslot& fx_slot,
const EAXSOURCEALLSENDPROPERTIES& send) const noexcept;
[[nodiscard]] auto eax_create_room_filter_param(const ALeffectslot& fx_slot,
const EAXSOURCEALLSENDPROPERTIES& send) const noexcept -> EaxAlLowPassParam;
void eax_update_direct_filter();
void eax_update_room_filters();
@ -894,16 +906,16 @@ private:
}
}
void eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count);
void eax1_get(const EaxCall& call, const Eax1Props& props);
void eax2_get(const EaxCall& call, const Eax2Props& props);
void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props);
void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props);
void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props);
void eax3_get(const EaxCall& call, const Eax3Props& props);
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 eax2_get(const EaxCall& call, const Eax2Props& props);
static void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props);
static void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props);
static void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props);
static void eax3_get(const EaxCall& call, const Eax3Props& props);
void eax4_get(const EaxCall& call, const Eax4Props& props);
void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props);
void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& 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);
void eax5_get(const EaxCall& call, const Eax5Props& props);
void eax_get(const EaxCall& call);
@ -976,21 +988,21 @@ private:
}
template<typename TValidator, size_t TIdCount>
void eax_defer_active_fx_slot_id(const EaxCall& call, GUID (&dst_ids)[TIdCount])
void eax_defer_active_fx_slot_id(const EaxCall& call, const al::span<GUID,TIdCount> dst_ids)
{
const auto src_ids = call.get_values<const GUID>(TIdCount);
std::for_each(src_ids.cbegin(), src_ids.cend(), TValidator{});
std::uninitialized_copy(src_ids.cbegin(), src_ids.cend(), dst_ids);
std::uninitialized_copy(src_ids.cbegin(), src_ids.cend(), dst_ids.begin());
}
template<size_t TIdCount>
void eax4_defer_active_fx_slot_id(const EaxCall& call, GUID (&dst_ids)[TIdCount])
void eax4_defer_active_fx_slot_id(const EaxCall& call, const al::span<GUID,TIdCount> dst_ids)
{
eax_defer_active_fx_slot_id<Eax4ActiveFxSlotIdValidator>(call, dst_ids);
}
template<size_t TIdCount>
void eax5_defer_active_fx_slot_id(const EaxCall& call, GUID (&dst_ids)[TIdCount])
void eax5_defer_active_fx_slot_id(const EaxCall& call, const al::span<GUID,TIdCount> dst_ids)
{
eax_defer_active_fx_slot_id<Eax5ActiveFxSlotIdValidator>(call, dst_ids);
}
@ -1022,12 +1034,12 @@ private:
void eax_set_efx_wet_gain_auto();
void eax_set_efx_wet_gain_hf_auto();
void eax1_set(const EaxCall& call, Eax1Props& props);
void eax2_set(const EaxCall& call, Eax2Props& props);
static void eax1_set(const EaxCall& call, Eax1Props& props);
static void eax2_set(const EaxCall& call, Eax2Props& props);
void eax3_set(const EaxCall& call, Eax3Props& props);
void eax4_set(const EaxCall& call, Eax4Props& props);
void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props);
void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props);
static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props);
static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props);
void eax5_set(const EaxCall& call, Eax5Props& props);
void eax_set(const EaxCall& call);
@ -1041,4 +1053,19 @@ private:
void UpdateAllSourceProps(ALCcontext *context);
struct SourceSubList {
uint64_t FreeMask{~0_u64};
gsl::owner<std::array<ALsource,64>*> Sources{nullptr};
SourceSubList() noexcept = default;
SourceSubList(const SourceSubList&) = delete;
SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources}
{ rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; }
~SourceSubList();
SourceSubList& operator=(const SourceSubList&) = delete;
SourceSubList& operator=(SourceSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; }
};
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -22,9 +22,6 @@
#include "alconfig.h"
#include <cstdlib>
#include <cctype>
#include <cstring>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
@ -34,25 +31,47 @@
#endif
#include <algorithm>
#include <cstdio>
#include <array>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <istream>
#include <limits>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "alfstream.h"
#include "almalloc.h"
#include "alstring.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "strutils.h"
#include "vector.h"
#if defined(ALSOFT_UWP)
#include <winrt/Windows.Media.Core.h> // !!This is important!!
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
using namespace winrt;
#endif
namespace {
using namespace std::string_view_literals;
#if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP)
struct CoTaskMemDeleter {
void operator()(void *mem) const { CoTaskMemFree(mem); }
};
#endif
struct ConfigEntry {
std::string key;
std::string value;
};
al::vector<ConfigEntry> ConfOpts;
std::vector<ConfigEntry> ConfOpts;
std::string &lstrip(std::string &line)
@ -72,57 +91,48 @@ bool readline(std::istream &f, std::string &output)
return std::getline(f, output) && !output.empty();
}
std::string expdup(const char *str)
std::string expdup(std::string_view str)
{
std::string output;
std::string envval;
while(*str != '\0')
while(!str.empty())
{
const char *addstr;
size_t addstrlen;
if(str[0] != '$')
if(auto nextpos = str.find('$'))
{
const char *next = std::strchr(str, '$');
addstr = str;
addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
output += str.substr(0, nextpos);
if(nextpos == std::string_view::npos)
break;
str += addstrlen;
str.remove_prefix(nextpos);
}
else
str.remove_prefix(1);
if(str.empty())
{
str++;
if(*str == '$')
{
const char *next = std::strchr(str+1, '$');
addstr = str;
addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
str += addstrlen;
}
else
{
const bool hasbraces{(*str == '{')};
if(hasbraces) str++;
const char *envstart = str;
while(std::isalnum(*str) || *str == '_')
++str;
if(hasbraces && *str != '}')
continue;
const std::string envname{envstart, str};
if(hasbraces) str++;
envval = al::getenv(envname.c_str()).value_or(std::string{});
addstr = envval.data();
addstrlen = envval.length();
}
output += '$';
break;
}
if(addstrlen == 0)
if(str.front() == '$')
{
output += '$';
str.remove_prefix(1);
continue;
}
output.append(addstr, addstrlen);
const bool hasbraces{str.front() == '{'};
if(hasbraces) str.remove_prefix(1);
size_t envend{0};
while(envend < str.size() && (std::isalnum(str[envend]) || str[envend] == '_'))
++envend;
if(hasbraces && (envend == str.size() || str[envend] != '}'))
continue;
const std::string envname{str.substr(0, envend)};
if(hasbraces) ++envend;
str.remove_prefix(envend);
if(auto envval = al::getenv(envname.c_str()))
output += *envval;
}
return output;
@ -140,44 +150,43 @@ void LoadConfigFromFile(std::istream &f)
if(buffer[0] == '[')
{
auto line = const_cast<char*>(buffer.data());
char *section = line+1;
char *endsection;
endsection = std::strchr(section, ']');
if(!endsection || section == endsection)
auto endpos = buffer.find(']', 1);
if(endpos == 1 || endpos == std::string::npos)
{
ERR(" config parse error: bad line \"%s\"\n", line);
ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
continue;
}
if(endsection[1] != 0)
if(buffer[endpos+1] != '\0')
{
char *end = endsection+1;
while(std::isspace(*end))
++end;
if(*end != 0 && *end != '#')
size_t last{endpos+1};
while(last < buffer.size() && std::isspace(buffer[last]))
++last;
if(last < buffer.size() && buffer[last] != '#')
{
ERR(" config parse error: bad line \"%s\"\n", line);
ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
continue;
}
}
*endsection = 0;
auto section = std::string_view{buffer}.substr(1, endpos-1);
curSection.clear();
if(al::strcasecmp(section, "general") != 0)
if(al::case_compare(section, "general"sv) != 0)
{
do {
char *nextp = std::strchr(section, '%');
if(!nextp)
auto nextp = section.find('%');
if(nextp == std::string_view::npos)
{
curSection += section;
break;
}
curSection.append(section, nextp);
section = nextp;
curSection += section.substr(0, nextp);
section.remove_prefix(nextp);
if(((section[1] >= '0' && section[1] <= '9') ||
if(section.size() > 2 &&
((section[1] >= '0' && section[1] <= '9') ||
(section[1] >= 'a' && section[1] <= 'f') ||
(section[1] >= 'A' && section[1] <= 'F')) &&
((section[2] >= '0' && section[2] <= '9') ||
@ -198,19 +207,19 @@ void LoadConfigFromFile(std::istream &f)
else if(section[2] >= 'A' && section[2] <= 'F')
b |= (section[2]-'A'+0x0a);
curSection += static_cast<char>(b);
section += 3;
section.remove_prefix(3);
}
else if(section[1] == '%')
else if(section.size() > 1 && section[1] == '%')
{
curSection += '%';
section += 2;
section.remove_prefix(2);
}
else
{
curSection += '%';
section += 1;
section.remove_prefix(1);
}
} while(*section != 0);
} while(!section.empty());
}
continue;
@ -228,16 +237,17 @@ void LoadConfigFromFile(std::istream &f)
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
continue;
}
auto keyend = sep++;
while(keyend > 0 && std::isspace(buffer[keyend-1]))
--keyend;
if(!keyend)
auto keypart = std::string_view{buffer}.substr(0, sep++);
while(!keypart.empty() && std::isspace(keypart.back()))
keypart.remove_suffix(1);
if(keypart.empty())
{
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
continue;
}
while(sep < buffer.size() && std::isspace(buffer[sep]))
sep++;
auto valpart = std::string_view{buffer}.substr(sep);
while(!valpart.empty() && std::isspace(valpart.front()))
valpart.remove_prefix(1);
std::string fullKey;
if(!curSection.empty())
@ -245,20 +255,24 @@ void LoadConfigFromFile(std::istream &f)
fullKey += curSection;
fullKey += '/';
}
fullKey += buffer.substr(0u, keyend);
fullKey += keypart;
std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}};
if(value.size() > 1)
if(valpart.size() > size_t{std::numeric_limits<int>::max()})
{
if((value.front() == '"' && value.back() == '"')
|| (value.front() == '\'' && value.back() == '\''))
ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str());
continue;
}
if(valpart.size() > 1)
{
if((valpart.front() == '"' && valpart.back() == '"')
|| (valpart.front() == '\'' && valpart.back() == '\''))
{
value.pop_back();
value.erase(value.begin());
valpart.remove_prefix(1);
valpart.remove_suffix(1);
}
}
TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str());
TRACE(" setting '%s' = '%.*s'\n", fullKey.c_str(), al::sizei(valpart), valpart.data());
/* Check if we already have this option set */
auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
@ -266,61 +280,49 @@ void LoadConfigFromFile(std::istream &f)
auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
if(ent != ConfOpts.end())
{
if(!value.empty())
ent->value = expdup(value.c_str());
if(!valpart.empty())
ent->value = expdup(valpart);
else
ConfOpts.erase(ent);
}
else if(!value.empty())
ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())});
else if(!valpart.empty())
ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(valpart)});
}
ConfOpts.shrink_to_fit();
}
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName)
const char *GetConfigValue(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName)
{
if(!keyName)
if(keyName.empty())
return nullptr;
std::string key;
if(blockName && al::strcasecmp(blockName, "general") != 0)
if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0)
{
key = blockName;
if(devName)
{
key += '/';
key += devName;
}
key += '/';
key += keyName;
}
else
if(!devName.empty())
{
if(devName)
{
key = devName;
key += '/';
}
key += keyName;
key += devName;
key += '/';
}
key += keyName;
auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
[&key](const ConfigEntry &entry) -> bool
{ return entry.key == key; });
[&key](const ConfigEntry &entry) -> bool { return entry.key == key; });
if(iter != ConfOpts.cend())
{
TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
TRACE("Found option %s = \"%s\"\n", key.c_str(), iter->value.c_str());
if(!iter->value.empty())
return iter->value.c_str();
return nullptr;
}
if(!devName)
{
TRACE("Key %s not found\n", key.c_str());
if(devName.empty())
return nullptr;
}
return GetConfigValue(nullptr, blockName, keyName);
return GetConfigValue({}, blockName, keyName);
}
} // namespace
@ -329,33 +331,48 @@ const char *GetConfigValue(const char *devName, const char *blockName, const cha
#ifdef _WIN32
void ReadALConfig()
{
WCHAR buffer[MAX_PATH];
if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
{
std::string filepath{wstr_to_utf8(buffer)};
filepath += "\\alsoft.ini";
namespace fs = std::filesystem;
fs::path path;
TRACE("Loading config %s...\n", filepath.c_str());
al::ifstream f{filepath};
if(f.is_open())
LoadConfigFromFile(f);
#if !defined(_GAMING_XBOX)
{
#if !defined(ALSOFT_UWP)
std::unique_ptr<WCHAR,CoTaskMemDeleter> bufstore;
const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND,
nullptr, al::out_ptr(bufstore))};
if(SUCCEEDED(hr))
{
const std::wstring_view buffer{bufstore.get()};
#else
winrt::Windows::Storage::ApplicationDataContainer localSettings = winrt::Windows::Storage::ApplicationData::Current().LocalSettings();
auto bufstore = Windows::Storage::ApplicationData::Current().RoamingFolder().Path();
std::wstring_view buffer{bufstore};
{
#endif
path = fs::path{buffer};
path /= L"alsoft.ini";
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
}
#endif
std::string ppath{GetProcBinary().path};
if(!ppath.empty())
path = fs::u8path(GetProcBinary().path);
if(!path.empty())
{
ppath += "\\alsoft.ini";
TRACE("Loading config %s...\n", ppath.c_str());
al::ifstream f{ppath};
if(f.is_open())
path /= "alsoft.ini";
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
if(auto confpath = al::getenv(L"ALSOFT_CONF"))
{
TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
al::ifstream f{*confpath};
if(f.is_open())
path = *confpath;
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
}
@ -364,13 +381,12 @@ void ReadALConfig()
void ReadALConfig()
{
const char *str{"/etc/openal/alsoft.conf"};
namespace fs = std::filesystem;
fs::path path{"/etc/openal/alsoft.conf"};
TRACE("Loading config %s...\n", str);
al::ifstream f{str};
if(f.is_open())
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
f.close();
std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
/* Go through the list in reverse, since "the order of base directories
@ -378,48 +394,43 @@ void ReadALConfig()
* important". Ergo, we need to load the settings from the later dirs
* first so that the settings in the earlier dirs override them.
*/
std::string fname;
while(!confpaths.empty())
{
auto next = confpaths.find_last_of(':');
auto next = confpaths.rfind(':');
if(next < confpaths.length())
{
fname = confpaths.substr(next+1);
path = fs::path{std::string_view{confpaths}.substr(next+1)}.lexically_normal();
confpaths.erase(next);
}
else
{
fname = confpaths;
path = fs::path{confpaths}.lexically_normal();
confpaths.clear();
}
if(fname.empty() || fname.front() != '/')
WARN("Ignoring XDG config dir: %s\n", fname.c_str());
if(!path.is_absolute())
WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str());
else
{
if(fname.back() != '/') fname += "/alsoft.conf";
else fname += "alsoft.conf";
path /= "alsoft.conf";
TRACE("Loading config %s...\n", fname.c_str());
f = al::ifstream{fname};
if(f.is_open())
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
fname.clear();
}
#ifdef __APPLE__
CFBundleRef mainBundle = CFBundleGetMainBundle();
if(mainBundle)
{
unsigned char fileName[PATH_MAX];
CFURLRef configURL;
CFURLRef configURL{CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""),
nullptr)};
if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
std::array<unsigned char,PATH_MAX> fileName{};
if(configURL && CFURLGetFileSystemRepresentation(configURL, true, fileName.data(), fileName.size()))
{
f = al::ifstream{reinterpret_cast<char*>(fileName)};
if(f.is_open())
if(std::ifstream f{reinterpret_cast<char*>(fileName.data())}; f.is_open())
LoadConfigFromFile(f);
}
}
@ -427,102 +438,100 @@ void ReadALConfig()
if(auto homedir = al::getenv("HOME"))
{
fname = *homedir;
if(fname.back() != '/') fname += "/.alsoftrc";
else fname += ".alsoftrc";
path = *homedir;
path /= ".alsoftrc";
TRACE("Loading config %s...\n", fname.c_str());
f = al::ifstream{fname};
if(f.is_open())
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
{
fname = *configdir;
if(fname.back() != '/') fname += "/alsoft.conf";
else fname += "alsoft.conf";
path = *configdir;
path /= "alsoft.conf";
}
else
{
fname.clear();
path.clear();
if(auto homedir = al::getenv("HOME"))
{
fname = *homedir;
if(fname.back() != '/') fname += "/.config/alsoft.conf";
else fname += ".config/alsoft.conf";
path = *homedir;
path /= ".config/alsoft.conf";
}
}
if(!fname.empty())
if(!path.empty())
{
TRACE("Loading config %s...\n", fname.c_str());
f = al::ifstream{fname};
if(f.is_open())
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
std::string ppath{GetProcBinary().path};
if(!ppath.empty())
path = GetProcBinary().path;
if(!path.empty())
{
if(ppath.back() != '/') ppath += "/alsoft.conf";
else ppath += "alsoft.conf";
path /= "alsoft.conf";
TRACE("Loading config %s...\n", ppath.c_str());
f = al::ifstream{ppath};
if(f.is_open())
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
if(auto confname = al::getenv("ALSOFT_CONF"))
{
TRACE("Loading config %s...\n", confname->c_str());
f = al::ifstream{*confname};
if(f.is_open())
if(std::ifstream f{*confname}; f.is_open())
LoadConfigFromFile(f);
}
}
#endif
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
std::optional<std::string> ConfigValueStr(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return val;
return al::nullopt;
return std::nullopt;
}
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
std::optional<int> ConfigValueInt(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return static_cast<int>(std::strtol(val, nullptr, 0));
return al::nullopt;
return std::nullopt;
}
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
std::optional<unsigned int> ConfigValueUInt(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return static_cast<unsigned int>(std::strtoul(val, nullptr, 0));
return al::nullopt;
return std::nullopt;
}
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
std::optional<float> ConfigValueFloat(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return std::strtof(val, nullptr);
return al::nullopt;
return std::nullopt;
}
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
std::optional<bool> ConfigValueBool(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true")==0 || atoi(val) != 0;
return al::nullopt;
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
return std::nullopt;
}
bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def)
bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName, bool def)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0);
return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
return def;
}

View file

@ -1,18 +1,25 @@
#ifndef ALCONFIG_H
#define ALCONFIG_H
#include <optional>
#include <string>
#include <string_view>
#include "aloptional.h"
void ReadALConfig();
bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def);
bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName, bool def);
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName);
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName);
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName);
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName);
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName);
std::optional<std::string> ConfigValueStr(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
std::optional<int> ConfigValueInt(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName);
std::optional<unsigned int> ConfigValueUInt(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
std::optional<float> ConfigValueFloat(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
std::optional<bool> ConfigValueBool(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
#endif /* ALCONFIG_H */

File diff suppressed because it is too large Load diff

View file

@ -2,20 +2,20 @@
#define ALU_H
#include <bitset>
#include "aloptional.h"
#include <cstdint>
#include <optional>
struct ALCcontext;
struct ALCdevice;
struct EffectSlot;
enum class StereoEncoding : unsigned char;
enum class StereoEncoding : std::uint8_t;
constexpr float GainMixMax{1000.0f}; /* +60dB */
enum CompatFlags : uint8_t {
enum CompatFlags : std::uint8_t {
ReverseX,
ReverseY,
ReverseZ,
@ -31,7 +31,7 @@ void aluInit(CompatFlagBitset flags, const float nfcscale);
* Set up the appropriate panning method and mixing method given the device
* properties.
*/
void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding> stereomode);
void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncoding> stereomode);
void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context);

View file

@ -31,29 +31,33 @@
#include <exception>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#include "albyte.h"
#include "albit.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <alsa/asoundlib.h>
namespace {
constexpr char alsaDevice[] = "ALSA Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; }
#ifdef HAVE_DYNLOAD
@ -248,59 +252,97 @@ struct DevMap {
{ }
};
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices;
const char *prefix_name(snd_pcm_stream_t stream)
std::string_view prefix_name(snd_pcm_stream_t stream) noexcept
{
assert(stream == SND_PCM_STREAM_PLAYBACK || stream == SND_PCM_STREAM_CAPTURE);
return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix";
if(stream == SND_PCM_STREAM_PLAYBACK)
return "device-prefix"sv;
return "capture-prefix"sv;
}
al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
struct SndCtlCardInfo {
snd_ctl_card_info_t *mInfo{};
SndCtlCardInfo() { snd_ctl_card_info_malloc(&mInfo); }
~SndCtlCardInfo() { if(mInfo) snd_ctl_card_info_free(mInfo); }
SndCtlCardInfo(const SndCtlCardInfo&) = delete;
SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete;
[[nodiscard]]
operator snd_ctl_card_info_t*() const noexcept { return mInfo; }
};
struct SndPcmInfo {
snd_pcm_info_t *mInfo{};
SndPcmInfo() { snd_pcm_info_malloc(&mInfo); }
~SndPcmInfo() { if(mInfo) snd_pcm_info_free(mInfo); }
SndPcmInfo(const SndPcmInfo&) = delete;
SndPcmInfo& operator=(const SndPcmInfo&) = delete;
[[nodiscard]]
operator snd_pcm_info_t*() const noexcept { return mInfo; }
};
struct SndCtl {
snd_ctl_t *mHandle{};
SndCtl() = default;
~SndCtl() { if(mHandle) snd_ctl_close(mHandle); }
SndCtl(const SndCtl&) = delete;
SndCtl& operator=(const SndCtl&) = delete;
[[nodiscard]]
auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); }
[[nodiscard]]
operator snd_ctl_t*() const noexcept { return mHandle; }
};
std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
{
al::vector<DevMap> devlist;
std::vector<DevMap> devlist;
snd_ctl_card_info_t *info;
snd_ctl_card_info_malloc(&info);
snd_pcm_info_t *pcminfo;
snd_pcm_info_malloc(&pcminfo);
SndCtlCardInfo info;
SndPcmInfo pcminfo;
auto defname = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture");
devlist.emplace_back(alsaDevice, defname ? defname->c_str() : "default");
auto defname = ConfigValueStr({}, "alsa"sv,
(stream == SND_PCM_STREAM_PLAYBACK) ? "device"sv : "capture"sv);
devlist.emplace_back(GetDefaultName(), defname ? std::string_view{*defname} : "default"sv);
if(auto customdevs = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures"))
if(auto customdevs = ConfigValueStr({}, "alsa"sv,
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices"sv : "custom-captures"sv))
{
size_t nextpos{customdevs->find_first_not_of(';')};
size_t curpos;
while((curpos=nextpos) < customdevs->length())
size_t curpos{customdevs->find_first_not_of(';')};
while(curpos < customdevs->length())
{
nextpos = customdevs->find_first_of(';', curpos+1);
size_t seppos{customdevs->find_first_of('=', curpos)};
size_t nextpos{customdevs->find(';', curpos+1)};
const size_t seppos{customdevs->find('=', curpos)};
if(seppos == curpos || seppos >= nextpos)
{
std::string spec{customdevs->substr(curpos, nextpos-curpos)};
const std::string spec{customdevs->substr(curpos, nextpos-curpos)};
ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
}
else
{
devlist.emplace_back(customdevs->substr(curpos, seppos-curpos),
customdevs->substr(seppos+1, nextpos-seppos-1));
const auto &entry = devlist.back();
const std::string_view strview{*customdevs};
const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos),
strview.substr(seppos+1, nextpos-seppos-1));
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
if(nextpos < customdevs->length())
nextpos = customdevs->find_first_not_of(';', nextpos+1);
curpos = nextpos;
}
}
const std::string main_prefix{
ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")};
const std::string main_prefix{ConfigValueStr({}, "alsa"sv, prefix_name(stream))
.value_or("plughw:")};
int card{-1};
int err{snd_card_next(&card)};
@ -308,16 +350,17 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
{
std::string name{"hw:" + std::to_string(card)};
snd_ctl_t *handle;
if((err=snd_ctl_open(&handle, name.c_str(), 0)) < 0)
SndCtl handle;
err = handle.open(name.c_str(), 0);
if(err < 0)
{
ERR("control open (hw:%d): %s\n", card, snd_strerror(err));
continue;
}
if((err=snd_ctl_card_info(handle, info)) < 0)
err = snd_ctl_card_info(handle, info);
if(err < 0)
{
ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err));
snd_ctl_close(handle);
continue;
}
@ -326,8 +369,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
name = prefix_name(stream);
name += '-';
name += cardid;
const std::string card_prefix{
ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)};
const std::string card_prefix{ConfigValueStr({}, "alsa"sv, name).value_or(main_prefix)};
int dev{-1};
while(true)
@ -339,7 +381,8 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
snd_pcm_info_set_device(pcminfo, static_cast<uint>(dev));
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, stream);
if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0)
err = snd_ctl_pcm_info(handle, pcminfo);
if(err < 0)
{
if(err != -ENOENT)
ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err));
@ -352,8 +395,8 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
name += cardid;
name += '-';
name += std::to_string(dev);
const std::string device_prefix{
ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(card_prefix)};
const std::string device_prefix{ConfigValueStr({}, "alsa"sv, name)
.value_or(card_prefix)};
/* "CardName, PcmName (CARD=cardid,DEV=dev)" */
name = cardname;
@ -372,18 +415,13 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
device += ",DEV=";
device += std::to_string(dev);
devlist.emplace_back(std::move(name), std::move(device));
const auto &entry = devlist.back();
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());
}
snd_ctl_close(handle);
}
if(err < 0)
ERR("snd_card_next failed: %s\n", snd_strerror(err));
snd_pcm_info_free(pcminfo);
snd_ctl_card_info_free(info);
return devlist;
}
@ -392,7 +430,6 @@ int verify_state(snd_pcm_t *handle)
{
snd_pcm_state_t state{snd_pcm_state(handle)};
int err;
switch(state)
{
case SND_PCM_STATE_OPEN:
@ -405,15 +442,27 @@ int verify_state(snd_pcm_t *handle)
break;
case SND_PCM_STATE_XRUN:
if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0)
if(int err{snd_pcm_recover(handle, -EPIPE, 1)}; err < 0)
return err;
break;
case SND_PCM_STATE_SUSPENDED:
if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0)
if(int err{snd_pcm_recover(handle, -ESTRPIPE, 1)}; err < 0)
return err;
break;
case SND_PCM_STATE_DISCONNECTED:
return -ENODEV;
/* ALSA headers have made this enum public, leaving us in a bind: use
* the enum despite being private and internal to the libasound, or
* ignore when an enum value isn't handled. We can't rely on it being
* declared either, since older headers don't have it and it could be
* removed in the future. We can't even really rely on its value, since
* being private/internal means it's subject to change, but this is the
* best we can do.
*/
case 1024 /*SND_PCM_STATE_PRIVATE1*/:
assert(state != 1024);
}
return state;
@ -427,7 +476,7 @@ struct AlsaPlayback final : public BackendBase {
int mixerProc();
int mixerNoMMapProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -439,12 +488,10 @@ struct AlsaPlayback final : public BackendBase {
std::mutex mMutex;
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(AlsaPlayback)
};
AlsaPlayback::~AlsaPlayback()
@ -458,7 +505,7 @@ AlsaPlayback::~AlsaPlayback()
int AlsaPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
@ -506,7 +553,7 @@ int AlsaPlayback::mixerProc()
avail -= avail%update_size;
// it is possible that contiguous areas are smaller, thus we use a loop
std::lock_guard<std::mutex> _{mMutex};
std::lock_guard<std::mutex> dlock{mMutex};
while(avail > 0)
{
snd_pcm_uframes_t frames{avail};
@ -520,6 +567,7 @@ int AlsaPlayback::mixerProc()
break;
}
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)};
mDevice->renderSamples(WritePtr, static_cast<uint>(frames), mFrameStep);
@ -541,7 +589,7 @@ int AlsaPlayback::mixerProc()
int AlsaPlayback::mixerNoMMapProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
@ -585,13 +633,13 @@ int AlsaPlayback::mixerNoMMapProc()
continue;
}
al::byte *WritePtr{mBuffer.data()};
auto WritePtr = mBuffer.begin();
avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(WritePtr, static_cast<uint>(avail), mFrameStep);
std::lock_guard<std::mutex> dlock{mMutex};
mDevice->renderSamples(al::to_address(WritePtr), static_cast<uint>(avail), mFrameStep);
while(avail > 0)
{
snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr,
snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, al::to_address(WritePtr),
static_cast<snd_pcm_uframes_t>(avail))};
switch(ret)
{
@ -626,10 +674,10 @@ int AlsaPlayback::mixerNoMMapProc()
}
void AlsaPlayback::open(const char *name)
void AlsaPlayback::open(std::string_view name)
{
std::string driver{"default"};
if(name)
if(!name.empty())
{
if(PlaybackDevices.empty())
PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
@ -638,13 +686,13 @@ void AlsaPlayback::open(const char *name)
[name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
driver = iter->device_name;
}
else
{
name = alsaDevice;
if(auto driveropt = ConfigValueStr(nullptr, "alsa", "device"))
name = GetDefaultName();
if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv))
driver = std::move(driveropt).value();
}
TRACE("Opening device \"%s\"\n", driver.c_str());
@ -692,15 +740,14 @@ bool AlsaPlayback::reset()
break;
}
bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", true)};
bool allowmmap{GetConfigValueBool(mDevice->DeviceName, "alsa"sv, "mmap"sv, true)};
uint periodLen{static_cast<uint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)};
uint bufferLen{static_cast<uint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)};
uint rate{mDevice->Frequency};
int err{};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
snd_strerror(err)}; \
} while(0)
@ -715,17 +762,18 @@ bool AlsaPlayback::reset()
/* test and set format (implicitly sets sample bits) */
if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) < 0)
{
static const struct {
struct FormatMap {
snd_pcm_format_t format;
DevFmtType fmttype;
} formatlist[] = {
{ SND_PCM_FORMAT_FLOAT, DevFmtFloat },
{ SND_PCM_FORMAT_S32, DevFmtInt },
{ SND_PCM_FORMAT_U32, DevFmtUInt },
{ SND_PCM_FORMAT_S16, DevFmtShort },
{ SND_PCM_FORMAT_U16, DevFmtUShort },
{ SND_PCM_FORMAT_S8, DevFmtByte },
{ SND_PCM_FORMAT_U8, DevFmtUByte },
};
static constexpr std::array formatlist{
FormatMap{SND_PCM_FORMAT_FLOAT, DevFmtFloat },
FormatMap{SND_PCM_FORMAT_S32, DevFmtInt },
FormatMap{SND_PCM_FORMAT_U32, DevFmtUInt },
FormatMap{SND_PCM_FORMAT_S16, DevFmtShort },
FormatMap{SND_PCM_FORMAT_U16, DevFmtUShort},
FormatMap{SND_PCM_FORMAT_S8, DevFmtByte },
FormatMap{SND_PCM_FORMAT_U8, DevFmtUByte },
};
for(const auto &fmt : formatlist)
@ -750,7 +798,7 @@ bool AlsaPlayback::reset()
else mDevice->FmtChans = DevFmtStereo;
}
/* set rate (implicitly constrains period/buffer parameters) */
if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", false)
if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false)
|| !mDevice->Flags.test(FrequencyRequest))
{
if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
@ -760,10 +808,10 @@ bool AlsaPlayback::reset()
WARN("Failed to enable ALSA resampler\n");
CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr));
/* set period time (implicitly constrains period/buffer parameters) */
if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 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));
/* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */
if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)) < 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));
/* install and prepare hardware configuration */
CHECK(snd_pcm_hw_params(mPcmHandle, hp.get()));
@ -798,11 +846,10 @@ bool AlsaPlayback::reset()
void AlsaPlayback::start()
{
int err{};
snd_pcm_access_t access{};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
snd_strerror(err)}; \
} while(0)
@ -849,10 +896,9 @@ void AlsaPlayback::stop()
ClockLatency AlsaPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
std::lock_guard<std::mutex> dlock{mMutex};
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
snd_pcm_sframes_t delay{};
int err{snd_pcm_delay(mPcmHandle, &delay)};
if(err < 0)
@ -871,23 +917,21 @@ struct AlsaCapture final : public BackendBase {
AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaCapture() override;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
ClockLatency getClockLatency() override;
snd_pcm_t *mPcmHandle{nullptr};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
bool mDoCapture{false};
RingBufferPtr mRing{nullptr};
snd_pcm_sframes_t mLastAvail{0};
DEF_NEWDEL(AlsaCapture)
};
AlsaCapture::~AlsaCapture()
@ -898,10 +942,10 @@ AlsaCapture::~AlsaCapture()
}
void AlsaCapture::open(const char *name)
void AlsaCapture::open(std::string_view name)
{
std::string driver{"default"};
if(name)
if(!name.empty())
{
if(CaptureDevices.empty())
CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
@ -910,19 +954,18 @@ void AlsaCapture::open(const char *name)
[name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
driver = iter->device_name;
}
else
{
name = alsaDevice;
if(auto driveropt = ConfigValueStr(nullptr, "alsa", "capture"))
name = GetDefaultName();
if(auto driveropt = ConfigValueStr({}, "alsa"sv, "capture"sv))
driver = std::move(driveropt).value();
}
TRACE("Opening device \"%s\"\n", driver.c_str());
int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)};
if(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,
"Could not open ALSA device \"%s\"", driver.c_str()};
@ -955,13 +998,15 @@ void AlsaCapture::open(const char *name)
break;
}
snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)};
snd_pcm_uframes_t periodSizeInFrames{minu(mDevice->BufferSize, 25*mDevice->Frequency/1000)};
snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->BufferSize,
100u*mDevice->Frequency/1000u)};
snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->BufferSize,
25u*mDevice->Frequency/1000u)};
bool needring{false};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
snd_strerror(err)}; \
} while(0)
@ -999,13 +1044,11 @@ void AlsaCapture::open(const char *name)
void AlsaCapture::start()
{
int err{snd_pcm_prepare(mPcmHandle)};
if(err < 0)
if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0)
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s",
snd_strerror(err)};
err = snd_pcm_start(mPcmHandle);
if(err < 0)
if(int err{snd_pcm_start(mPcmHandle)}; err < 0)
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s",
snd_strerror(err)};
@ -1024,25 +1067,27 @@ void AlsaCapture::stop()
/* The ring buffer implicitly captures when checking availability.
* Direct access needs to explicitly capture it into temp storage.
*/
auto temp = al::vector<al::byte>(
auto temp = std::vector<std::byte>(
static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, avail)));
captureSamples(temp.data(), avail);
mBuffer = std::move(temp);
}
int err{snd_pcm_drop(mPcmHandle)};
if(err < 0)
if(int err{snd_pcm_drop(mPcmHandle)}; err < 0)
ERR("drop failed: %s\n", snd_strerror(err));
mDoCapture = false;
}
void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
void AlsaCapture::captureSamples(std::byte *buffer, uint samples)
{
if(mRing)
{
mRing->read(buffer, samples);
std::ignore = mRing->read(buffer, samples);
return;
}
const auto outspan = al::span{buffer,
static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, samples))};
auto outiter = outspan.begin();
mLastAvail -= samples;
while(mDevice->Connected.load(std::memory_order_acquire) && samples > 0)
{
@ -1055,20 +1100,21 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
if(static_cast<snd_pcm_uframes_t>(amt) > samples) amt = samples;
amt = snd_pcm_frames_to_bytes(mPcmHandle, amt);
std::copy_n(mBuffer.begin(), amt, buffer);
std::copy_n(mBuffer.begin(), amt, outiter);
mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt);
amt = snd_pcm_bytes_to_frames(mPcmHandle, amt);
}
else if(mDoCapture)
amt = snd_pcm_readi(mPcmHandle, buffer, samples);
amt = snd_pcm_readi(mPcmHandle, al::to_address(outiter), samples);
if(amt < 0)
{
ERR("read error: %s\n", snd_strerror(static_cast<int>(amt)));
if(amt == -EAGAIN)
continue;
if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
amt = snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1);
if(amt >= 0)
{
amt = snd_pcm_start(mPcmHandle);
if(amt >= 0)
@ -1088,12 +1134,12 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
continue;
}
buffer = buffer + amt;
outiter += amt;
samples -= static_cast<uint>(amt);
}
if(samples > 0)
std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples),
al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
std::fill_n(outiter, snd_pcm_frames_to_bytes(mPcmHandle, samples),
std::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
}
uint AlsaCapture::availableSamples()
@ -1105,7 +1151,8 @@ uint AlsaCapture::availableSamples()
{
ERR("avail update failed: %s\n", snd_strerror(static_cast<int>(avail)));
if((avail=snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1)) >= 0)
avail = snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1);
if(avail >= 0)
{
if(mDoCapture)
avail = snd_pcm_start(mPcmHandle);
@ -1141,7 +1188,8 @@ uint AlsaCapture::availableSamples()
if(amt == -EAGAIN)
continue;
if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
amt = snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1);
if(amt >= 0)
{
if(mDoCapture)
amt = snd_pcm_start(mPcmHandle);
@ -1168,9 +1216,8 @@ uint AlsaCapture::availableSamples()
ClockLatency AlsaCapture::getClockLatency()
{
ClockLatency ret;
ret.ClockTime = GetDeviceClockTime(mDevice);
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
snd_pcm_sframes_t delay{};
int err{snd_pcm_delay(mPcmHandle, &delay)};
if(err < 0)
@ -1189,13 +1236,9 @@ ClockLatency AlsaCapture::getClockLatency()
bool AlsaBackendFactory::init()
{
bool error{false};
#ifdef HAVE_DYNLOAD
if(!alsa_handle)
{
std::string missing_funcs;
alsa_handle = LoadLib("libasound.so.2");
if(!alsa_handle)
{
@ -1203,52 +1246,47 @@ bool AlsaBackendFactory::init()
return false;
}
error = false;
std::string missing_funcs;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f)); \
if(p##f == nullptr) { \
error = true; \
missing_funcs += "\n" #f; \
} \
if(p##f == nullptr) missing_funcs += "\n" #f; \
} while(0)
ALSA_FUNCS(LOAD_FUNC);
#undef LOAD_FUNC
if(error)
if(!missing_funcs.empty())
{
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
CloseLib(alsa_handle);
alsa_handle = nullptr;
return false;
}
}
#endif
return !error;
return true;
}
bool AlsaBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string AlsaBackendFactory::probe(BackendType type)
auto AlsaBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames.append(entry.name.c_str(), entry.name.length()+1);
};
{ outnames.emplace_back(entry.name); };
switch(type)
{
case BackendType::Playback:
PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}

View file

@ -5,15 +5,15 @@
struct AlsaBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_ALSA_H */

View file

@ -7,17 +7,6 @@
#include <array>
#include <atomic>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmreg.h>
#include "albit.h"
#include "core/logging.h"
#include "aloptional.h"
#endif
#include "atomic.h"
#include "core/devformat.h"
@ -25,10 +14,12 @@ namespace al {
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;
@ -38,7 +29,7 @@ backend_exception::~backend_exception() = default;
bool BackendBase::reset()
{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; }
void BackendBase::captureSamples(al::byte*, uint)
void BackendBase::captureSamples(std::byte*, uint)
{ }
uint BackendBase::availableSamples()
@ -46,27 +37,26 @@ uint BackendBase::availableSamples()
ClockLatency BackendBase::getClockLatency()
{
ClockLatency ret;
ClockLatency ret{};
uint refcount;
do {
refcount = mDevice->waitForMix();
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.ClockTime = mDevice->getClockTime();
std::atomic_thread_fence(std::memory_order_acquire);
} while(refcount != ReadRef(mDevice->MixCount));
} while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed));
/* NOTE: The device will generally have about all but one periods filled at
* any given time during playback. Without a more accurate measurement from
* the output, this is an okay approximation.
*/
ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize},
std::chrono::seconds::zero());
ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
return ret;
}
void BackendBase::setDefaultWFXChannelOrder()
void BackendBase::setDefaultWFXChannelOrder() const
{
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
@ -126,6 +116,24 @@ void BackendBase::setDefaultWFXChannelOrder()
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX7144:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[BackLeft] = 4;
mDevice->RealOut.ChannelIndex[BackRight] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12;
mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13;
mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14;
mDevice->RealOut.ChannelIndex[BottomBackRight] = 15;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
@ -141,7 +149,7 @@ void BackendBase::setDefaultWFXChannelOrder()
}
}
void BackendBase::setDefaultChannelOrder()
void BackendBase::setDefaultChannelOrder() const
{
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
@ -179,6 +187,24 @@ void BackendBase::setDefaultChannelOrder()
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX7144:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12;
mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13;
mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14;
mDevice->RealOut.ChannelIndex[BottomBackRight] = 15;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;

View file

@ -3,13 +3,16 @@
#include <chrono>
#include <cstdarg>
#include <cstddef>
#include <memory>
#include <ratio>
#include <string>
#include <string_view>
#include <vector>
#include "albyte.h"
#include "core/device.h"
#include "core/except.h"
#include "alc/events.h"
using uint = unsigned int;
@ -20,27 +23,33 @@ struct ClockLatency {
};
struct BackendBase {
virtual void open(const char *name) = 0;
virtual void open(std::string_view name) = 0;
virtual bool reset();
virtual void start() = 0;
virtual void stop() = 0;
virtual void captureSamples(al::byte *buffer, uint samples);
virtual void captureSamples(std::byte *buffer, uint samples);
virtual uint availableSamples();
virtual ClockLatency getClockLatency();
DeviceBase *const mDevice;
BackendBase() = delete;
BackendBase(const BackendBase&) = delete;
BackendBase(BackendBase&&) = delete;
BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
virtual ~BackendBase() = default;
void operator=(const BackendBase&) = delete;
void operator=(BackendBase&&) = delete;
protected:
/** Sets the default channel order used by most non-WaveFormatEx-based APIs. */
void setDefaultChannelOrder();
void setDefaultChannelOrder() const;
/** Sets the default channel order used by WaveFormatEx. */
void setDefaultWFXChannelOrder();
void setDefaultWFXChannelOrder() const;
};
using BackendPtr = std::unique_ptr<BackendBase>;
@ -50,18 +59,6 @@ enum class BackendType {
};
/* Helper to get the current clock time from the device's ClockBase, and
* SamplesDone converted from the sample rate.
*/
inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device)
{
using std::chrono::seconds;
using std::chrono::nanoseconds;
auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
return device->ClockBase + ns;
}
/* Helper to get the device latency from the backend, including any fixed
* latency from post-processing.
*/
@ -74,16 +71,24 @@ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
struct BackendFactory {
virtual bool init() = 0;
virtual bool querySupport(BackendType type) = 0;
virtual std::string probe(BackendType type) = 0;
virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
protected:
BackendFactory() = default;
BackendFactory(const BackendFactory&) = delete;
BackendFactory(BackendFactory&&) = delete;
virtual ~BackendFactory() = default;
void operator=(const BackendFactory&) = delete;
void operator=(BackendFactory&&) = delete;
virtual auto init() -> bool = 0;
virtual auto querySupport(BackendType type) -> bool = 0;
virtual auto queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport
{ return alc::EventSupport::NoSupport; }
virtual auto enumerate(BackendType type) -> std::vector<std::string> = 0;
virtual auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr = 0;
};
namespace al {
@ -98,15 +103,15 @@ class backend_exception final : public base_exception {
backend_error mErrorCode;
public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#ifdef __MINGW32__
[[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
backend_exception(backend_error code, const char *msg, ...);
~backend_exception() override;
backend_error errorCode() const noexcept { return mErrorCode; }
[[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; }
};
} // namespace al

View file

@ -22,18 +22,20 @@
#include "coreaudio.h"
#include <inttypes.h>
#include <cinttypes>
#include <cmath>
#include <memory>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <unistd.h>
#include <cmath>
#include <memory>
#include <string>
#include <vector>
#include <optional>
#include "alnumeric.h"
#include "alstring.h"
#include "core/converter.h"
#include "core/device.h"
#include "core/logging.h"
@ -42,18 +44,40 @@
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
namespace {
#if TARGET_OS_IOS || TARGET_OS_TV
#define CAN_ENUMERATE 0
#else
#include <IOKit/audio/IOAudioTypes.h>
#define CAN_ENUMERATE 1
#endif
namespace {
constexpr auto OutputElement = 0;
constexpr auto InputElement = 1;
struct FourCCPrinter {
char mString[sizeof(UInt32) + 1]{};
constexpr FourCCPrinter(UInt32 code) noexcept
{
for(size_t i{0};i < sizeof(UInt32);++i)
{
const auto ch = static_cast<char>(code & 0xff);
/* If this breaks early it'll leave the first byte null, to get
* read as a 0-length string.
*/
if(ch <= 0x1f || ch >= 0x7f)
break;
mString[sizeof(UInt32)-1-i] = ch;
code >>= 8;
}
}
constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { }
constexpr const char *c_str() const noexcept { return mString; }
};
#if CAN_ENUMERATE
struct DeviceEntry {
AudioDeviceID mId;
@ -147,7 +171,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
&propSize);
if(err)
{
ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n",
FourCCPrinter{err}.c_str(), err);
return 0;
}
@ -158,7 +183,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
buflist);
if(err)
{
ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n",
FourCCPrinter{err}.c_str(), err);
return 0;
}
@ -182,7 +208,7 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
{
ERR("Failed to get device list: %u\n", err);
ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
return;
}
@ -247,6 +273,48 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
newdevs.swap(list);
}
struct DeviceHelper {
DeviceHelper()
{
AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
if (status != noErr)
ERR("AudioObjectAddPropertyListener fail: %d", status);
}
~DeviceHelper()
{
AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
if (status != noErr)
ERR("AudioObjectRemovePropertyListener fail: %d", status);
}
static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses,
const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/)
{
for(UInt32 i = 0; i < inNumberAddresses; ++i)
{
switch(inAddresses[i].mSelector)
{
case kAudioHardwarePropertyDefaultOutputDevice:
case kAudioHardwarePropertyDefaultSystemOutputDevice:
alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
"Default playback device changed: "+std::to_string(inAddresses[i].mSelector));
break;
case kAudioHardwarePropertyDefaultInputDevice:
alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
"Default capture device changed: "+std::to_string(inAddresses[i].mSelector));
break;
}
}
return noErr;
}
};
static std::optional<DeviceHelper> sDeviceHelper;
#else
static constexpr char ca_device[] = "CoreAudio Default";
@ -260,15 +328,8 @@ struct CoreAudioPlayback final : public BackendBase {
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept;
static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept
{
return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp,
inBusNumber, inNumberFrames, ioData);
}
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -277,8 +338,6 @@ struct CoreAudioPlayback final : public BackendBase {
uint mFrameSize{0u};
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
DEF_NEWDEL(CoreAudioPlayback)
};
CoreAudioPlayback::~CoreAudioPlayback()
@ -301,11 +360,11 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi
}
void CoreAudioPlayback::open(const char *name)
void CoreAudioPlayback::open(std::string_view name)
{
#if CAN_ENUMERATE
AudioDeviceID audioDevice{kAudioDeviceUnknown};
if(!name)
if(name.empty())
GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
&audioDevice);
else
@ -318,16 +377,16 @@ void CoreAudioPlayback::open(const char *name)
auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
if(devmatch == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
audioDevice = devmatch->mId;
}
#else
if(!name)
if(name.empty())
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
else if(name != ca_device)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
#endif
/* open the default output unit */
@ -351,7 +410,7 @@ void CoreAudioPlayback::open(const char *name)
OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
"Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
@ -362,7 +421,7 @@ void CoreAudioPlayback::open(const char *name)
err = AudioUnitInitialize(audioUnit);
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: %u", err};
"Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
/* WARNING: I don't know if "valid" audio unit values are guaranteed to be
* non-0. If not, this logic is broken.
@ -375,7 +434,7 @@ void CoreAudioPlayback::open(const char *name)
mAudioUnit = audioUnit;
#if CAN_ENUMERATE
if(name)
if(!name.empty())
mDevice->DeviceName = name;
else
{
@ -388,6 +447,21 @@ void CoreAudioPlayback::open(const char *name)
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
else mDevice->DeviceName = "Unknown Device Name";
}
if(audioDevice != kAudioDeviceUnknown)
{
UInt32 type{};
err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false,
kAudioObjectPropertyElementMaster, sizeof(type), &type);
if(err != noErr)
ERR("Failed to get audio device type: %u\n", err);
else
{
TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str());
mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones));
}
}
#else
mDevice->DeviceName = name;
#endif
@ -397,7 +471,7 @@ bool CoreAudioPlayback::reset()
{
OSStatus err{AudioUnitUninitialize(mAudioUnit)};
if(err != noErr)
ERR("-- AudioUnitUninitialize failed.\n");
ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
/* retrieve default output unit's properties (output side) */
AudioStreamBasicDescription streamFormat{};
@ -406,7 +480,8 @@ bool CoreAudioPlayback::reset()
OutputElement, &streamFormat, &size);
if(err != noErr || size != sizeof(streamFormat))
{
ERR("AudioUnitGetProperty failed\n");
ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
err);
return false;
}
@ -473,7 +548,8 @@ bool CoreAudioPlayback::reset()
OutputElement, &streamFormat, sizeof(streamFormat));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
err);
return false;
}
@ -482,14 +558,16 @@ bool CoreAudioPlayback::reset()
/* setup callback */
mFrameSize = mDevice->frameSizeFromFmt();
AURenderCallbackStruct input{};
input.inputProc = CoreAudioPlayback::MixerProcC;
input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
{ return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n",
FourCCPrinter{err}.c_str(), err);
return false;
}
@ -497,7 +575,7 @@ bool CoreAudioPlayback::reset()
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
{
ERR("AudioUnitInitialize failed\n");
ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
return false;
}
@ -509,14 +587,14 @@ void CoreAudioPlayback::start()
const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"AudioOutputUnitStart failed: %d", err};
"AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
}
void CoreAudioPlayback::stop()
{
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr)
ERR("AudioOutputUnitStop failed\n");
ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
}
@ -527,18 +605,11 @@ struct CoreAudioCapture final : public BackendBase {
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept
{
return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp,
inBusNumber, inNumberFrames, ioData);
}
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
AudioUnit mAudioUnit{0};
@ -548,11 +619,9 @@ struct CoreAudioCapture final : public BackendBase {
SampleConverterPtr mConverter;
al::vector<char> mCaptureData;
std::vector<char> mCaptureData;
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(CoreAudioCapture)
};
CoreAudioCapture::~CoreAudioCapture()
@ -568,7 +637,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
AudioBufferList*) noexcept
{
union {
al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
AudioBufferList list;
} audiobuf{};
@ -581,20 +650,20 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
inNumberFrames, &audiobuf.list)};
if(err != noErr)
{
ERR("AudioUnitRender capture error: %d\n", err);
ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
return err;
}
mRing->write(mCaptureData.data(), inNumberFrames);
std::ignore = mRing->write(mCaptureData.data(), inNumberFrames);
return noErr;
}
void CoreAudioCapture::open(const char *name)
void CoreAudioCapture::open(std::string_view name)
{
#if CAN_ENUMERATE
AudioDeviceID audioDevice{kAudioDeviceUnknown};
if(!name)
if(name.empty())
GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
&audioDevice);
else
@ -607,16 +676,16 @@ void CoreAudioCapture::open(const char *name)
auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
if(devmatch == CaptureList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
audioDevice = devmatch->mId;
}
#else
if(!name)
if(name.empty())
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
else if(name != ca_device)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
#endif
AudioComponentDescription desc{};
@ -640,7 +709,7 @@ void CoreAudioCapture::open(const char *name)
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
"Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Turn off AudioUnit output
UInt32 enableIO{0};
@ -648,7 +717,8 @@ void CoreAudioCapture::open(const char *name)
kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable audio unit output property: %u", err};
"Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(),
err};
// Turn on AudioUnit input
enableIO = 1;
@ -656,7 +726,8 @@ void CoreAudioCapture::open(const char *name)
kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not enable audio unit input property: %u", err};
"Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(),
err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
@ -666,14 +737,15 @@ void CoreAudioCapture::open(const char *name)
// set capture callback
AURenderCallbackStruct input{};
input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
{ return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set capture callback: %u", err};
"Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Disable buffer allocation for capture
UInt32 flag{0};
@ -681,13 +753,14 @@ void CoreAudioCapture::open(const char *name)
kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable buffer allocation property: %u", err};
"Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(),
err};
// Initialize the device
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: %u", err};
"Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Get the hardware format
AudioStreamBasicDescription hardwareFormat{};
@ -696,7 +769,7 @@ void CoreAudioCapture::open(const char *name)
InputElement, &hardwareFormat, &propertySize);
if(err != noErr || propertySize != sizeof(hardwareFormat))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input format: %u", err};
"Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Set up the requested format description
AudioStreamBasicDescription requestedFormat{};
@ -749,6 +822,7 @@ void CoreAudioCapture::open(const char *name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
@ -777,14 +851,14 @@ void CoreAudioCapture::open(const char *name)
InputElement, &outputFormat, sizeof(outputFormat));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set input format: %u", err};
"Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
/* Calculate the minimum AudioUnit output format frame count for the pre-
* conversion ring buffer. Ensure at least 100ms for the total buffer.
*/
double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10);
auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10_u64);
FrameCount64 += MaxResamplerPadding;
if(FrameCount64 > std::numeric_limits<int32_t>::max())
throw al::backend_exception{al::backend_error::DeviceError,
@ -796,11 +870,11 @@ void CoreAudioCapture::open(const char *name)
kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
if(err != noErr || propertySize != sizeof(outputFrameCount))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input frame count: %u", err};
"Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
mCaptureData.resize(outputFrameCount * mFrameSize);
outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
outputFrameCount = static_cast<UInt32>(std::max(uint64_t{outputFrameCount}, FrameCount64));
mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
/* Set up sample converter if needed */
@ -810,7 +884,7 @@ void CoreAudioCapture::open(const char *name)
mDevice->Frequency, Resampler::FastBSinc24);
#if CAN_ENUMERATE
if(name)
if(!name.empty())
mDevice->DeviceName = name;
else
{
@ -834,21 +908,21 @@ void CoreAudioCapture::start()
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"AudioOutputUnitStart failed: %d", err};
"AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
}
void CoreAudioCapture::stop()
{
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr)
ERR("AudioOutputUnitStop failed\n");
ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
}
void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples)
void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
{
if(!mConverter)
{
mRing->read(buffer, samples);
std::ignore = mRing->read(buffer, samples);
return;
}
@ -882,28 +956,34 @@ BackendFactory &CoreAudioBackendFactory::getFactory()
return factory;
}
bool CoreAudioBackendFactory::init() { return true; }
bool CoreAudioBackendFactory::init()
{
#if CAN_ENUMERATE
sDeviceHelper.emplace();
#endif
return true;
}
bool CoreAudioBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string CoreAudioBackendFactory::probe(BackendType type)
auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
#if CAN_ENUMERATE
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
{ outnames.emplace_back(entry.mName); };
switch(type)
{
case BackendType::Playback:
EnumerateDevices(PlaybackList, false);
outnames.reserve(PlaybackList.size());
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:
EnumerateDevices(CaptureList, true);
outnames.reserve(CaptureList.size());
std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
break;
}
@ -914,8 +994,7 @@ std::string CoreAudioBackendFactory::probe(BackendType type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(ca_device, sizeof(ca_device));
outnames.emplace_back(ca_device);
break;
}
#endif
@ -930,3 +1009,18 @@ BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendTyp
return BackendPtr{new CoreAudioCapture{device}};
return nullptr;
}
alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
{
switch(eventType)
{
case alc::EventType::DefaultDeviceChanged:
return alc::EventSupport::FullSupport;
case alc::EventType::DeviceAdded:
case alc::EventType::DeviceRemoved:
case alc::EventType::Count:
break;
}
return alc::EventSupport::NoSupport;
}

View file

@ -5,15 +5,17 @@
struct CoreAudioBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_COREAUDIO_H */

View file

@ -25,10 +25,6 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <cguid.h>
#include <mmreg.h>
#ifndef _WAVEFORMATEXTENSIBLE_
@ -36,15 +32,21 @@
#include <ksmedia.h>
#endif
#include <algorithm>
#include <atomic>
#include <cassert>
#include <thread>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <memory.h>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include "alnumeric.h"
#include "alspan.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "comptr.h"
#include "core/device.h"
#include "core/helpers.h"
@ -52,7 +54,6 @@
#include "dynload.h"
#include "ringbuffer.h"
#include "strutils.h"
#include "threads.h"
/* MinGW-w64 needs this for some unknown reason now. */
using LPCWAVEFORMATEX = const WAVEFORMATEX*;
@ -129,10 +130,10 @@ struct DevMap {
{ }
};
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices;
bool checkName(const al::vector<DevMap> &list, const std::string &name)
bool checkName(const al::span<DevMap> list, const std::string &name)
{
auto match_name = [&name](const DevMap &entry) -> bool
{ return entry.name == name; };
@ -144,7 +145,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
if(!guid)
return TRUE;
auto& devices = *static_cast<al::vector<DevMap>*>(data);
auto& devices = *static_cast<std::vector<DevMap>*>(data);
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
int count{1};
@ -176,7 +177,7 @@ struct DSoundPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -189,8 +190,6 @@ struct DSoundPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(DSoundPlayback)
};
DSoundPlayback::~DSoundPlayback()
@ -209,7 +208,7 @@ DSoundPlayback::~DSoundPlayback()
FORCE_ALIGN int DSoundPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
DSBCAPS DSBCaps{};
DSBCaps.dwSize = sizeof(DSBCaps);
@ -299,24 +298,22 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
return 0;
}
void DSoundPlayback::open(const char *name)
void DSoundPlayback::open(std::string_view name)
{
HRESULT hr;
if(PlaybackDevices.empty())
{
/* Initialize COM to prevent name truncation */
HRESULT hrcom{CoInitialize(nullptr)};
ComWrapper com{};
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
if(SUCCEEDED(hrcom))
CoUninitialize();
}
const GUID *guid{nullptr};
if(!name && !PlaybackDevices.empty())
if(name.empty() && !PlaybackDevices.empty())
{
name = PlaybackDevices[0].name.c_str();
name = PlaybackDevices[0].name;
guid = &PlaybackDevices[0].guid;
}
else
@ -332,7 +329,7 @@ void DSoundPlayback::open(const char *name)
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
}
guid = &iter->guid;
}
@ -347,7 +344,7 @@ void DSoundPlayback::open(const char *name)
//DirectSound Init code
ComPtr<IDirectSound> ds;
if(SUCCEEDED(hr))
hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
hr = DirectSoundCreate(guid, al::out_ptr(ds), nullptr);
if(SUCCEEDED(hr))
hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
if(FAILED(hr))
@ -425,49 +422,53 @@ bool DSoundPlayback::reset()
case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
case DevFmtX7144: mDevice->FmtChans = DevFmtX714;
/* fall-through */
case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break;
}
retry_open:
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
do {
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
if(mDevice->FmtType == DevFmtFloat)
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
mPrimaryBuffer = nullptr;
}
else
{
if(SUCCEEDED(hr) && !mPrimaryBuffer)
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
}
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
if(mDevice->FmtType == DevFmtFloat)
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
mPrimaryBuffer = nullptr;
}
else
{
if(SUCCEEDED(hr) && !mPrimaryBuffer)
{
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
}
if(FAILED(hr))
break;
if(SUCCEEDED(hr))
{
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
if(num_updates > MAX_UPDATES)
num_updates = MAX_UPDATES;
@ -480,26 +481,21 @@ retry_open:
DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
DSBDescription.lpwfxFormat = &OutputType.Format;
hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
{
mDevice->FmtType = DevFmtShort;
goto retry_open;
}
}
hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr);
if(SUCCEEDED(hr) || mDevice->FmtType != DevFmtFloat)
break;
mDevice->FmtType = DevFmtShort;
} while(FAILED(hr));
if(SUCCEEDED(hr))
{
void *ptr;
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies));
if(SUCCEEDED(hr))
{
mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
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)
{
nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
@ -550,10 +546,10 @@ struct DSoundCapture final : public BackendBase {
DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundCapture() override;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
ComPtr<IDirectSoundCapture> mDSC;
@ -562,8 +558,6 @@ struct DSoundCapture final : public BackendBase {
DWORD mCursor{0u};
RingBufferPtr mRing;
DEF_NEWDEL(DSoundCapture)
};
DSoundCapture::~DSoundCapture()
@ -577,24 +571,22 @@ DSoundCapture::~DSoundCapture()
}
void DSoundCapture::open(const char *name)
void DSoundCapture::open(std::string_view name)
{
HRESULT hr;
if(CaptureDevices.empty())
{
/* Initialize COM to prevent name truncation */
HRESULT hrcom{CoInitialize(nullptr)};
ComWrapper com{};
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
if(SUCCEEDED(hrcom))
CoUninitialize();
}
const GUID *guid{nullptr};
if(!name && !CaptureDevices.empty())
if(name.empty() && !CaptureDevices.empty())
{
name = CaptureDevices[0].name.c_str();
name = CaptureDevices[0].name;
guid = &CaptureDevices[0].guid;
}
else
@ -610,7 +602,7 @@ void DSoundCapture::open(const char *name)
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
}
guid = &iter->guid;
}
@ -641,6 +633,7 @@ void DSoundCapture::open(const char *name)
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
@ -657,6 +650,7 @@ void DSoundCapture::open(const char *name)
InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
InputType.Format.nBlockAlign;
InputType.Format.cbSize = 0;
/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
if(mDevice->FmtType == DevFmtFloat)
InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
@ -669,8 +663,7 @@ void DSoundCapture::open(const char *name)
InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
}
uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
DSCBUFFERDESC DSCBDescription{};
DSCBDescription.dwSize = sizeof(DSCBDescription);
@ -679,9 +672,9 @@ void DSoundCapture::open(const char *name)
DSCBDescription.lpwfxFormat = &InputType.Format;
//DirectSoundCapture Init code
hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
hr = DirectSoundCaptureCreate(guid, al::out_ptr(mDSC), nullptr);
if(SUCCEEDED(hr))
mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr);
if(SUCCEEDED(hr))
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
@ -719,8 +712,8 @@ void DSoundCapture::stop()
}
}
void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void DSoundCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint DSoundCapture::availableSamples()
{
@ -743,9 +736,9 @@ uint DSoundCapture::availableSamples()
}
if(SUCCEEDED(hr))
{
mRing->write(ReadPtr1, ReadCnt1/FrameSize);
std::ignore = mRing->write(ReadPtr1, ReadCnt1/FrameSize);
if(ReadPtr2 != nullptr && ReadCnt2 > 0)
mRing->write(ReadPtr2, ReadCnt2/FrameSize);
std::ignore = mRing->write(ReadPtr2, ReadCnt2/FrameSize);
hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
mCursor = ReadCursor;
}
@ -802,40 +795,32 @@ bool DSoundBackendFactory::init()
bool DSoundBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string DSoundBackendFactory::probe(BackendType type)
auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames.append(entry.name.c_str(), entry.name.length()+1);
};
{ outnames.emplace_back(entry.name); };
/* Initialize COM to prevent name truncation */
HRESULT hr;
HRESULT hrcom{CoInitialize(nullptr)};
ComWrapper com{};
switch(type)
{
case BackendType::Playback:
PlaybackDevices.clear();
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr))
ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices.clear();
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr))
ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
if(SUCCEEDED(hrcom))
CoUninitialize();
return outnames;
}

View file

@ -5,15 +5,15 @@
struct DSoundBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_DSOUND_H */

View file

@ -22,23 +22,26 @@
#include "jack.h"
#include <array>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <memory.h>
#include <array>
#include <mutex>
#include <thread>
#include <functional>
#include <vector>
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "threads.h"
#include <jack/jack.h>
#include <jack/ringbuffer.h>
@ -46,6 +49,8 @@
namespace {
using namespace std::string_view_literals;
#ifdef HAVE_DYNLOAD
#define JACK_FUNCS(MAGIC) \
MAGIC(jack_client_open); \
@ -99,19 +104,13 @@ decltype(jack_error_callback) * pjack_error_callback;
#endif
constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
jack_options_t ClientOptions = JackNullOption;
bool jack_load()
{
bool error{false};
#ifdef HAVE_DYNLOAD
if(!jack_handle)
{
std::string missing_funcs;
#ifdef _WIN32
#define JACKLIB "libjack.dll"
#else
@ -124,13 +123,10 @@ bool jack_load()
return false;
}
error = false;
std::string missing_funcs;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
if(p##f == nullptr) { \
error = true; \
missing_funcs += "\n" #f; \
} \
if(p##f == nullptr) missing_funcs += "\n" #f; \
} while(0)
JACK_FUNCS(LOAD_FUNC);
#undef LOAD_FUNC
@ -139,61 +135,66 @@ bool jack_load()
LOAD_SYM(jack_error_callback);
#undef LOAD_SYM
if(error)
if(!missing_funcs.empty())
{
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
CloseLib(jack_handle);
jack_handle = nullptr;
return false;
}
}
#endif
return !error;
return true;
}
struct JackDeleter {
void operator()(void *ptr) { jack_free(ptr); }
};
using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>; /* NOLINT(*-avoid-c-arrays) */
struct DeviceEntry {
std::string mName;
std::string mPattern;
DeviceEntry() = default;
DeviceEntry(const DeviceEntry&) = default;
DeviceEntry(DeviceEntry&&) = default;
template<typename T, typename U>
DeviceEntry(T&& name, U&& pattern)
: mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
{ }
~DeviceEntry();
DeviceEntry& operator=(const DeviceEntry&) = default;
DeviceEntry& operator=(DeviceEntry&&) = default;
};
DeviceEntry::~DeviceEntry() = default;
al::vector<DeviceEntry> PlaybackList;
std::vector<DeviceEntry> PlaybackList;
void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
{
std::remove_reference_t<decltype(list)>{}.swap(list);
if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
if(JackPortsPtr ports{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)})
{
for(size_t i{0};ports[i];++i)
{
const char *sep{std::strchr(ports[i], ':')};
if(!sep || ports[i] == sep) continue;
const std::string_view portname{ports[i]};
const size_t seppos{portname.find(':')};
if(seppos == 0 || seppos >= portname.size())
continue;
const al::span<const char> portdev{ports[i], sep};
const auto portdev = portname.substr(0, seppos);
auto check_name = [portdev](const DeviceEntry &entry) -> bool
{
const size_t len{portdev.size()};
return entry.mName.length() == len
&& entry.mName.compare(0, len, portdev.data(), len) == 0;
};
{ return entry.mName == portdev; };
if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
continue;
std::string name{portdev.data(), portdev.size()};
list.emplace_back(name, name+":");
const auto &entry = list.back();
const auto &entry = list.emplace_back(portdev, std::string{portdev}+":");
TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
/* There are ports but couldn't get device names from them. Add a
@ -202,11 +203,11 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
if(ports[0] && list.empty())
{
WARN("No device names found in available ports, adding a generic name.\n");
list.emplace_back("JACK", "");
list.emplace_back("JACK"sv, ""sv);
}
}
if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"))
if(auto listopt = ConfigValueStr({}, "jack", "custom-devices"))
{
for(size_t strpos{0};strpos < listopt->size();)
{
@ -214,38 +215,32 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
size_t seppos{listopt->find('=', strpos)};
if(seppos >= nextpos || seppos == strpos)
{
const std::string entry{listopt->substr(strpos, nextpos-strpos)};
ERR("Invalid device entry: \"%s\"\n", entry.c_str());
const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos);
ERR("Invalid device entry: \"%.*s\"\n", al::sizei(entry), entry.data());
if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos;
continue;
}
const al::span<const char> name{listopt->data()+strpos, seppos-strpos};
const al::span<const char> pattern{listopt->data()+(seppos+1),
std::min(nextpos, listopt->size())-(seppos+1)};
const auto name = std::string_view{*listopt}.substr(strpos, seppos-strpos);
const auto pattern = std::string_view{*listopt}.substr(seppos+1,
std::min(nextpos, listopt->size())-(seppos+1));
/* Check if this custom pattern already exists in the list. */
auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
{
const size_t len{pattern.size()};
return entry.mPattern.length() == len
&& entry.mPattern.compare(0, len, pattern.data(), len) == 0;
};
{ return entry.mPattern == pattern; };
auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
if(itemmatch != list.end())
{
/* If so, replace the name with this custom one. */
itemmatch->mName.assign(name.data(), name.size());
itemmatch->mName = name;
TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
itemmatch->mPattern.c_str());
}
else
{
/* Otherwise, add a new device entry. */
list.emplace_back(std::string{name.data(), name.size()},
std::string{pattern.data(), pattern.size()});
const auto &entry = list.back();
const auto &entry = list.emplace_back(name, pattern);
TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
@ -295,7 +290,7 @@ struct JackPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -304,7 +299,7 @@ struct JackPlayback final : public BackendBase {
std::string mPortPattern;
jack_client_t *mClient{nullptr};
std::array<jack_port_t*,MAX_OUTPUT_CHANNELS> mPort{};
std::array<jack_port_t*,MaxOutputChannels> mPort{};
std::mutex mMutex;
@ -315,8 +310,6 @@ struct JackPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(JackPlayback)
};
JackPlayback::~JackPlayback()
@ -336,22 +329,22 @@ JackPlayback::~JackPlayback()
int JackPlayback::processRt(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
size_t numchans{0};
auto outptrs = std::array<jack_default_audio_sample_t*,MaxOutputChannels>{};
auto numchans = size_t{0};
for(auto port : mPort)
{
if(!port || numchans == mDevice->RealOut.Buffer.size())
break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
outptrs[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
const auto dst = al::span{outptrs}.first(numchans);
if(mPlaying.load(std::memory_order_acquire)) LIKELY
mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
mDevice->renderSamples(dst, static_cast<uint>(numframes));
else
{
auto clear_buf = [numframes](float *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
std::for_each(dst.begin(), dst.end(), [numframes](float *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); });
}
return 0;
@ -360,53 +353,46 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept
int JackPlayback::process(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
std::array<al::span<float>,MaxOutputChannels> out;
size_t numchans{0};
for(auto port : mPort)
{
if(!port) break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
out[numchans++] = {static_cast<float*>(jack_port_get_buffer(port, numframes)), numframes};
}
jack_nframes_t total{0};
size_t total{0};
if(mPlaying.load(std::memory_order_acquire)) LIKELY
{
auto data = mRing->getReadVector();
jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))};
auto write_first = [&data,numchans,todo](float *outbuf) -> float*
{
const float *RESTRICT in = reinterpret_cast<float*>(data.first.buf);
auto deinterlace_input = [&in,numchans]() noexcept -> float
{
float ret{*in};
in += numchans;
return ret;
};
std::generate_n(outbuf, todo, deinterlace_input);
data.first.buf += sizeof(float);
return outbuf + todo;
};
std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first);
total += todo;
const auto update_size = size_t{mDevice->UpdateSize};
todo = minu(numframes-total, static_cast<uint>(data.second.len));
if(todo > 0)
const auto outlen = size_t{numframes / update_size};
const auto len1 = size_t{std::min(data.first.len/update_size, outlen)};
const auto len2 = size_t{std::min(data.second.len/update_size, outlen-len1)};
auto src = al::span{reinterpret_cast<float*>(data.first.buf), update_size*len1*numchans};
for(size_t i{0};i < len1;++i)
{
auto write_second = [&data,numchans,todo](float *outbuf) -> float*
for(size_t c{0};c < numchans;++c)
{
const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf);
auto deinterlace_input = [&in,numchans]() noexcept -> float
{
float ret{*in};
in += numchans;
return ret;
};
std::generate_n(outbuf, todo, deinterlace_input);
data.second.buf += sizeof(float);
return outbuf + todo;
};
std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second);
total += todo;
const auto iter = std::copy_n(src.begin(), update_size, out[c].begin());
out[c] = {iter, out[c].end()};
src = src.subspan(update_size);
}
total += update_size;
}
src = al::span{reinterpret_cast<float*>(data.second.buf), update_size*len2*numchans};
for(size_t i{0};i < len2;++i)
{
for(size_t c{0};c < numchans;++c)
{
const auto iter = std::copy_n(src.begin(), update_size, out[c].begin());
out[c] = {iter, out[c].end()};
src = src.subspan(update_size);
}
total += update_size;
}
mRing->readAdvance(total);
@ -415,8 +401,8 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
if(numframes > total)
{
const jack_nframes_t todo{numframes - total};
auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
auto clear_buf = [](const al::span<float> outbuf) -> void
{ std::fill(outbuf.begin(), outbuf.end(), 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
}
@ -426,45 +412,70 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
int JackPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frame_step{mDevice->channelsFromFmt()};
const auto update_size = uint{mDevice->UpdateSize};
const auto num_channels = size_t{mDevice->channelsFromFmt()};
auto outptrs = std::vector<float*>(num_channels);
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() < mDevice->UpdateSize)
if(mRing->writeSpace() < update_size)
{
mSem.wait();
continue;
}
auto data = mRing->getWriteVector();
size_t todo{data.first.len + data.second.len};
todo -= todo%mDevice->UpdateSize;
const auto len1 = size_t{data.first.len / update_size};
const auto len2 = size_t{data.second.len / update_size};
const auto len1 = static_cast<uint>(minz(data.first.len, todo));
const auto len2 = static_cast<uint>(minz(data.second.len, todo-len1));
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(data.first.buf, len1, frame_step);
std::lock_guard<std::mutex> dlock{mMutex};
auto buffer = al::span{reinterpret_cast<float*>(data.first.buf),
data.first.len*num_channels};
auto bufiter = buffer.begin();
for(size_t i{0};i < len1;++i)
{
std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size]
{
auto ret = al::to_address(bufiter);
bufiter += ptrdiff_t(update_size);
return ret;
});
mDevice->renderSamples(outptrs, update_size);
}
if(len2 > 0)
mDevice->renderSamples(data.second.buf, len2, frame_step);
mRing->writeAdvance(todo);
{
buffer = al::span{reinterpret_cast<float*>(data.second.buf),
data.second.len*num_channels};
bufiter = buffer.begin();
for(size_t i{0};i < len2;++i)
{
std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size]
{
auto ret = al::to_address(bufiter);
bufiter += ptrdiff_t(update_size);
return ret;
});
mDevice->renderSamples(outptrs, update_size);
}
}
mRing->writeAdvance((len1+len2) * update_size);
}
return 0;
}
void JackPlayback::open(const char *name)
void JackPlayback::open(std::string_view name)
{
if(!mClient)
{
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
jack_status_t status{};
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
if(mClient == nullptr)
throw al::backend_exception{al::backend_error::DeviceError,
@ -481,9 +492,9 @@ void JackPlayback::open(const char *name)
if(PlaybackList.empty())
EnumerateDevices(mClient, PlaybackList);
if(!name && !PlaybackList.empty())
if(name.empty() && !PlaybackList.empty())
{
name = PlaybackList[0].mName.c_str();
name = PlaybackList[0].mName;
mPortPattern = PlaybackList[0].mPattern;
}
else
@ -493,14 +504,10 @@ void JackPlayback::open(const char *name)
auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
if(iter == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name?name:""};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
mPortPattern = iter->mPattern;
}
mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
mDevice->DeviceName = name;
}
@ -511,6 +518,10 @@ bool JackPlayback::reset()
std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(nullptr);
mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
/* Ignore the requested buffer metrics and just keep one JACK-sized buffer
* ready for when requested.
*/
@ -525,9 +536,9 @@ bool JackPlayback::reset()
}
else
{
const char *devname{mDevice->DeviceName.c_str()};
const std::string_view devname{mDevice->DeviceName};
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
}
@ -535,27 +546,27 @@ bool JackPlayback::reset()
mDevice->FmtType = DevFmtFloat;
int port_num{0};
auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
auto bad_port = mPort.begin();
while(bad_port != ports_end)
auto ports = al::span{mPort}.first(mDevice->channelsFromFmt());
auto bad_port = ports.begin();
while(bad_port != ports.end())
{
std::string name{"channel_" + std::to_string(++port_num)};
*bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
*bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput | JackPortIsTerminal, 0);
if(!*bad_port) break;
++bad_port;
}
if(bad_port != ports_end)
if(bad_port != ports.end())
{
ERR("Failed to register enough JACK ports for %s output\n",
DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == mPort.begin()) return false;
if(bad_port == ports.begin()) return false;
if(bad_port == mPort.begin()+1)
if(bad_port == ports.begin()+1)
mDevice->FmtChans = DevFmtMono;
else
{
ports_end = mPort.begin()+2;
const auto ports_end = ports.begin()+2;
while(bad_port != ports_end)
{
jack_port_unregister(mClient, *(--bad_port));
@ -575,10 +586,10 @@ void JackPlayback::start()
if(jack_activate(mClient))
throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
const char *devname{mDevice->DeviceName.c_str()};
const std::string_view devname{mDevice->DeviceName};
if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
{
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput)};
if(!pnames)
{
@ -586,7 +597,7 @@ void JackPlayback::start()
throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
}
for(size_t i{0};i < al::size(mPort) && mPort[i];++i)
for(size_t i{0};i < std::size(mPort) && mPort[i];++i)
{
if(!pnames[i])
{
@ -613,7 +624,7 @@ void JackPlayback::start()
else
{
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
@ -651,10 +662,9 @@ void JackPlayback::stop()
ClockLatency JackPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
std::lock_guard<std::mutex> dlock{mMutex};
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
@ -674,7 +684,7 @@ bool JackBackendFactory::init()
if(!jack_load())
return false;
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false))
if(!GetConfigValueBool({}, "jack", "spawn-server", false))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
const PathNamePair &binname = GetProcBinary();
@ -682,7 +692,7 @@ bool JackBackendFactory::init()
void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
jack_set_error_function(jack_msg_handler);
jack_status_t status;
jack_status_t status{};
jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
jack_set_error_function(old_error_cb);
if(!client)
@ -700,18 +710,15 @@ bool JackBackendFactory::init()
bool JackBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
std::string JackBackendFactory::probe(BackendType type)
auto JackBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
{ outnames.emplace_back(entry.mName); };
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
jack_status_t status{};
switch(type)
{
case BackendType::Playback:
@ -722,6 +729,7 @@ std::string JackBackendFactory::probe(BackendType type)
}
else
WARN("jack_client_open() failed, 0x%02x\n", status);
outnames.reserve(PlaybackList.size());
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:

View file

@ -5,15 +5,15 @@
struct JackBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_JACK_H */

View file

@ -30,16 +30,14 @@ namespace {
struct LoopbackBackend final : public BackendBase {
LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
DEF_NEWDEL(LoopbackBackend)
};
void LoopbackBackend::open(const char *name)
void LoopbackBackend::open(std::string_view name)
{
mDevice->DeviceName = name;
}
@ -65,8 +63,8 @@ bool LoopbackBackendFactory::init()
bool LoopbackBackendFactory::querySupport(BackendType)
{ return true; }
std::string LoopbackBackendFactory::probe(BackendType)
{ return std::string{}; }
auto LoopbackBackendFactory::enumerate(BackendType) -> std::vector<std::string>
{ return {}; }
BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
{ return BackendPtr{new LoopbackBackend{device}}; }

View file

@ -5,15 +5,15 @@
struct LoopbackBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_LOOPBACK_H */

View file

@ -30,10 +30,11 @@
#include <functional>
#include <thread>
#include "core/device.h"
#include "almalloc.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "threads.h"
namespace {
@ -41,8 +42,9 @@ namespace {
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
using namespace std::string_view_literals;
constexpr char nullDevice[] = "No Output";
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "No Output"sv; }
struct NullBackend final : public BackendBase {
@ -50,15 +52,13 @@ struct NullBackend final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(NullBackend)
};
int NullBackend::mixerProc()
@ -66,7 +66,7 @@ int NullBackend::mixerProc()
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
int64_t done{0};
auto start = std::chrono::steady_clock::now();
@ -105,13 +105,13 @@ int NullBackend::mixerProc()
}
void NullBackend::open(const char *name)
void NullBackend::open(std::string_view name)
{
if(!name)
name = nullDevice;
else if(strcmp(name, nullDevice) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
mDevice->DeviceName = name;
}
@ -150,19 +150,17 @@ bool NullBackendFactory::init()
bool NullBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
std::string NullBackendFactory::probe(BackendType type)
auto NullBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
/* Includes null char. */
outnames.append(nullDevice, sizeof(nullDevice));
break;
/* Include null char. */
return std::vector{std::string{GetDeviceName()}};
case BackendType::Capture:
break;
}
return outnames;
return {};
}
BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct NullBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_NULL_H */

View file

@ -4,10 +4,11 @@
#include "oboe.h"
#include <cassert>
#include <cstdint>
#include <cstring>
#include <stdint.h>
#include "alnumeric.h"
#include "alstring.h"
#include "core/device.h"
#include "core/logging.h"
#include "ringbuffer.h"
@ -17,7 +18,9 @@
namespace {
constexpr char device_name[] = "Oboe Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Oboe Default"sv; }
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
@ -28,7 +31,9 @@ struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
void onErrorAfterClose(oboe::AudioStream* /* audioStream */, oboe::Result /* error */) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -46,14 +51,20 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea
return oboe::DataCallbackResult::Continue;
}
void OboePlayback::open(const char *name)
void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(error == oboe::Result::ErrorDisconnected)
mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error));
TRACE("Error was %s", oboe::convertToText(error));
}
void OboePlayback::open(std::string_view name)
{
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
/* Open a basic output stream, just to ensure it can work. */
oboe::ManagedStream stream;
@ -72,6 +83,7 @@ bool OboePlayback::reset()
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Output);
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
builder.setUsage(oboe::Usage::Game);
/* Don't let Oboe convert. We should be able to handle anything it gives
* back.
*/
@ -135,7 +147,7 @@ bool OboePlayback::reset()
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
mStream->setBufferSizeInFrames(std::min(static_cast<int32_t>(mDevice->BufferSize),
mStream->getBufferCapacityInFrames()));
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
@ -164,6 +176,9 @@ bool OboePlayback::reset()
mDevice->FmtType = DevFmtInt;
break;
case oboe::AudioFormat::I24:
#endif
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 8)
case oboe::AudioFormat::IEC61937:
#endif
case oboe::AudioFormat::Unspecified:
case oboe::AudioFormat::Invalid:
@ -177,9 +192,9 @@ bool OboePlayback::reset()
* FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
* update size.
*/
mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
mDevice->UpdateSize = std::max(mDevice->Frequency/100u,
static_cast<uint32_t>(mStream->getFramesPerBurst()));
mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
mDevice->BufferSize = std::max(mDevice->UpdateSize*2u,
static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
return true;
@ -197,8 +212,7 @@ void OboePlayback::stop()
{
oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
}
@ -212,28 +226,28 @@ struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
};
oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
int32_t numFrames)
{
mRing->write(audioData, static_cast<uint32_t>(numFrames));
std::ignore = mRing->write(audioData, static_cast<uint32_t>(numFrames));
return oboe::DataCallbackResult::Continue;
}
void OboeCapture::open(const char *name)
void OboeCapture::open(std::string_view name)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Input)
@ -259,6 +273,7 @@ void OboeCapture::open(const char *name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
@ -297,7 +312,7 @@ void OboeCapture::open(const char *name)
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
/* Ensure a minimum ringbuffer size of 100ms. */
mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10),
mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u),
static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
mDevice->DeviceName = name;
@ -315,15 +330,14 @@ void OboeCapture::stop()
{
const oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
}
uint OboeCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
void OboeCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void OboeCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
} // namespace
@ -332,16 +346,15 @@ bool OboeBackendFactory::init() { return true; }
bool OboeBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string OboeBackendFactory::probe(BackendType type)
auto OboeBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
return std::string{device_name, sizeof(device_name)};
return std::vector{std::string{GetDeviceName()}};
}
return std::string{};
return {};
}
BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct OboeBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OBOE_H */

View file

@ -23,23 +23,26 @@
#include "opensl.h"
#include <stdlib.h>
#include <jni.h>
#include <new>
#include <array>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <new>
#include <thread>
#include <functional>
#include "albit.h"
#include "alnumeric.h"
#include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "opthelpers.h"
#include "ringbuffer.h"
#include "threads.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
@ -48,15 +51,17 @@
namespace {
using namespace std::string_view_literals;
/* Helper macros */
#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
constexpr char opensl_device[] = "OpenSL";
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "OpenSL"sv; }
[[nodiscard]]
constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
{
switch(chans)
@ -80,6 +85,7 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT |
SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT |
SL_SPEAKER_TOP_BACK_RIGHT;
case DevFmtX7144:
case DevFmtAmbi3D:
break;
}
@ -159,12 +165,10 @@ struct OpenSLPlayback final : public BackendBase {
~OpenSLPlayback() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLPlayback*>(context)->process(bq); }
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -189,8 +193,6 @@ struct OpenSLPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OpenSLPlayback)
};
OpenSLPlayback::~OpenSLPlayback()
@ -229,7 +231,7 @@ void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept
int OpenSLPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
SLPlayItf player;
SLAndroidSimpleBufferQueueItf bufferQueue;
@ -312,13 +314,13 @@ int OpenSLPlayback::mixerProc()
}
void OpenSLPlayback::open(const char *name)
void OpenSLPlayback::open(std::string_view name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
/* There's only one device, so if it's already open, there's nothing to do. */
if(mEngineObj) return;
@ -375,74 +377,6 @@ bool OpenSLPlayback::reset()
mRing = nullptr;
#if 0
if(!mDevice->Flags.get<FrequencyRequest>())
{
/* FIXME: Disabled until I figure out how to get the Context needed for
* the getSystemService call.
*/
JNIEnv *env = Android_GetJNIEnv();
jobject jctx = Android_GetContext();
/* Get necessary stuff for using java.lang.Integer,
* android.content.Context, and android.media.AudioManager.
*/
jclass int_cls = JCALL(env,FindClass)("java/lang/Integer");
jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls,
"parseInt", "(Ljava/lang/String;)I"
);
TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint);
jclass ctx_cls = JCALL(env,FindClass)("android/content/Context");
jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls,
"AUDIO_SERVICE", "Ljava/lang/String;"
);
jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls,
"getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"
);
TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n",
ctx_cls, ctx_audsvc, ctx_getSysSvc);
jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager");
jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls,
"PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;"
);
jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls,
"getProperty", "(Ljava/lang/String;)Ljava/lang/String;"
);
TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n",
audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty);
const char *strchars;
jstring strobj;
/* Now make the calls. */
//AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc);
jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj);
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr);
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
//String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate);
jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj);
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr);
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
//int sampleRate = Integer.parseInt(srateStr);
sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr);
strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr);
TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars);
JCALL(env,ReleaseStringUTFChars)(srateStr, strchars);
if(!sampleRate) sampleRate = device->Frequency;
else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE);
}
#endif
mDevice->FmtChans = DevFmtStereo;
mDevice->FmtType = DevFmtShort;
@ -564,7 +498,9 @@ void OpenSLPlayback::start()
PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
result = VCALL(bufferQueue,RegisterCallback)(
[](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLPlayback*>(context)->process(bq); }, this);
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS != result)
@ -628,8 +564,8 @@ ClockLatency OpenSLPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
std::lock_guard<std::mutex> dlock{mMutex};
ret.ClockTime = mDevice->getClockTime();
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
@ -642,13 +578,11 @@ struct OpenSLCapture final : public BackendBase {
~OpenSLCapture() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLCapture*>(context)->process(bq); }
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
/* engine interfaces */
@ -662,8 +596,6 @@ struct OpenSLCapture final : public BackendBase {
uint mSplOffset{0u};
uint mFrameSize{0};
DEF_NEWDEL(OpenSLCapture)
};
OpenSLCapture::~OpenSLCapture()
@ -686,13 +618,13 @@ void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept
}
void OpenSLCapture::open(const char* name)
void OpenSLCapture::open(std::string_view name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PrintErr(result, "slCreateEngine");
@ -710,10 +642,10 @@ void OpenSLCapture::open(const char* name)
{
mFrameSize = mDevice->frameSizeFromFmt();
/* Ensure the total length is at least 100ms */
uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
/* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
mDevice->Frequency/100*5)};
uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u,
mDevice->Frequency/100u*5u)};
uint num_updates{(length+update_len-1) / update_len};
mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
@ -813,13 +745,15 @@ void OpenSLCapture::open(const char* name)
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
result = VCALL(bufferQueue,RegisterCallback)(
[](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLCapture*>(context)->process(bq); }, this);
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
const uint chunk_size{mDevice->UpdateSize * mFrameSize};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0};
auto data = mRing->getWriteVector();
std::fill_n(data.first.buf, data.first.len*chunk_size, silence);
@ -883,7 +817,7 @@ void OpenSLCapture::stop()
}
}
void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
{
const uint update_size{mDevice->UpdateSize};
const uint chunk_size{update_size * mFrameSize};
@ -895,7 +829,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
auto rdata = mRing->getReadVector();
for(uint i{0};i < samples;)
{
const uint rem{minu(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},
buffer + i*size_t{mFrameSize});
@ -932,7 +866,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
return;
/* For each buffer chunk that was fully read, queue another writable buffer
* chunk to keep the OpenSL queue full. This is rather convulated, as a
* chunk to keep the OpenSL queue full. This is rather convoluted, as a
* result of the ring buffer holding more elements than are writable at a
* given time. The end of the write vector increments when the read pointer
* advances, which will "expose" a previously unwritable element. So for
@ -975,18 +909,15 @@ bool OSLBackendFactory::init() { return true; }
bool OSLBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string OSLBackendFactory::probe(BackendType type)
auto OSLBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(opensl_device, sizeof(opensl_device));
break;
return std::vector{std::string{GetDeviceName()}};
}
return outnames;
return {};
}
BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct OSLBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OSL_H */

View file

@ -31,27 +31,26 @@
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <exception>
#include <functional>
#include <memory>
#include <new>
#include <string>
#include <string_view>
#include <system_error>
#include <thread>
#include <utility>
#include <vector>
#include "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <sys/soundcard.h>
@ -83,117 +82,148 @@
namespace {
constexpr char DefaultName[] = "OSS Default";
std::string DefaultPlayback{"/dev/dsp"};
std::string DefaultCapture{"/dev/dsp"};
using namespace std::string_literals;
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OSS Default"sv; }
std::string DefaultPlayback{"/dev/dsp"s};
std::string DefaultCapture{"/dev/dsp"s};
struct DevMap {
std::string name;
std::string device_name;
template<typename T, typename U>
DevMap(T&& name_, U&& devname_)
: name{std::forward<T>(name_)}, device_name{std::forward<U>(devname_)}
{ }
};
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices;
#ifdef ALC_OSS_COMPAT
#define DSP_CAP_OUTPUT 0x00020000
#define DSP_CAP_INPUT 0x00010000
void ALCossListPopulate(al::vector<DevMap> &devlist, int type)
void ALCossListPopulate(std::vector<DevMap> &devlist, int type)
{
devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
devlist.emplace_back(GetDefaultName(), (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback);
}
#else
void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path)
class FileHandle {
int mFd{-1};
public:
FileHandle() = default;
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
~FileHandle() { if(mFd != -1) ::close(mFd); }
template<typename ...Args>
[[nodiscard]] auto open(const char *fname, Args&& ...args) -> bool
{
close();
mFd = ::open(fname, std::forward<Args>(args)...);
return mFd != -1;
}
void close()
{
if(mFd != -1)
::close(mFd);
mFd = -1;
}
[[nodiscard]]
auto get() const noexcept -> int { return mFd; }
};
void ALCossListAppend(std::vector<DevMap> &list, std::string_view handle, std::string_view path)
{
#ifdef ALC_OSS_DEVNODE_TRUC
for(size_t i{0};i < path.size();++i)
{
if(path[i] == '.' && handle.size() + i >= path.size())
if(path[i] == '.' && handle.size() >= path.size() - i)
{
const size_t hoffset{handle.size() + i - path.size()};
if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0)
handle = handle.first(hoffset);
path = path.first(i);
handle = handle.substr(0, hoffset);
path = path.substr(0, i);
}
}
#endif
if(handle.empty())
handle = path;
std::string basename{handle.data(), handle.size()};
std::string devname{path.data(), path.size()};
auto match_devname = [&devname](const DevMap &entry) -> bool
{ return entry.device_name == devname; };
auto match_devname = [path](const DevMap &entry) -> bool
{ return entry.device_name == path; };
if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend())
return;
auto checkName = [&list](const std::string &name) -> bool
auto checkName = [&list](const std::string_view name) -> bool
{
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();
};
int count{1};
std::string newname{basename};
std::string newname{handle};
while(checkName(newname))
{
newname = basename;
newname = handle;
newname += " #";
newname += std::to_string(++count);
}
list.emplace_back(DevMap{std::move(newname), std::move(devname)});
const DevMap &entry = list.back();
const DevMap &entry = list.emplace_back(std::move(newname), path);
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
void ALCossListPopulate(al::vector<DevMap> &devlist, int type_flag)
void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
{
int fd{open("/dev/mixer", O_RDONLY)};
if(fd < 0)
oss_sysinfo si{};
FileHandle file;
if(!file.open("/dev/mixer", O_RDONLY))
{
TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str());
goto done;
}
oss_sysinfo si;
if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1)
{
TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno).c_str());
goto done;
}
for(int i{0};i < si.numaudios;i++)
{
oss_audioinfo ai;
oss_audioinfo ai{};
ai.dev = i;
if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1)
{
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i,
std::generic_category().message(errno).c_str());
continue;
}
if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
continue;
al::span<const char> handle;
std::string_view handle;
if(ai.handle[0] != '\0')
handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
else
handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
const std::string_view devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
ALCossListAppend(devlist, handle, devnode);
}
done:
if(fd >= 0)
close(fd);
fd = -1;
file.close();
const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
@ -201,7 +231,7 @@ done:
{ return entry.device_name == defdev; }
);
if(iter == devlist.cend())
devlist.insert(devlist.begin(), DevMap{DefaultName, defdev});
devlist.insert(devlist.begin(), DevMap{GetDefaultName(), defdev});
else
{
DevMap entry{std::move(*iter)};
@ -231,19 +261,17 @@ struct OSSPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
int mFd{-1};
al::vector<al::byte> mMixData;
std::vector<std::byte> mMixData;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OSSPlayback)
};
OSSPlayback::~OSSPlayback()
@ -257,7 +285,7 @@ OSSPlayback::~OSSPlayback()
int OSSPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frame_step{mDevice->channelsFromFmt()};
const size_t frame_size{mDevice->frameSizeFromFmt()};
@ -269,38 +297,38 @@ int OSSPlayback::mixerProc()
pollitem.fd = mFd;
pollitem.events = POLLOUT;
int pret{poll(&pollitem, 1, 1000)};
if(pret < 0)
if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("poll failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed waiting for playback buffer: %s", errstr.c_str());
break;
}
else if(pret == 0)
else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
{
WARN("poll timeout\n");
continue;
}
al::byte *write_ptr{mMixData.data()};
size_t to_write{mMixData.size()};
mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
al::span write_buf{mMixData};
mDevice->renderSamples(write_buf.data(), static_cast<uint>(write_buf.size()/frame_size),
frame_step);
while(!write_buf.empty() && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
ssize_t wrote{write(mFd, write_buf.data(), write_buf.size())};
if(wrote < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue;
ERR("write failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("write failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed writing playback samples: %s", errstr.c_str());
break;
}
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
write_buf = write_buf.subspan(static_cast<size_t>(wrote));
}
}
@ -308,11 +336,11 @@ int OSSPlayback::mixerProc()
}
void OSSPlayback::open(const char *name)
void OSSPlayback::open(std::string_view name)
{
const char *devname{DefaultPlayback.c_str()};
if(!name)
name = DefaultName;
if(name.empty())
name = GetDefaultName();
else
{
if(PlaybackDevices.empty())
@ -324,14 +352,14 @@ void OSSPlayback::open(const char *name)
);
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
devname = iter->device_name.c_str();
}
int fd{::open(devname, O_WRONLY)};
if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
std::generic_category().message(errno).c_str()};
if(mFd != -1)
::close(mFd);
@ -367,15 +395,14 @@ bool OSSPlayback::reset()
uint ossSpeed{mDevice->Frequency};
uint frameSize{numChannels * mDevice->bytesFromFmt()};
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
uint log2FragmentSize{std::max(log2i(mDevice->UpdateSize*frameSize), 4u)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
const char *err;
#define CHECKERR(func) if((func) < 0) { \
err = #func; \
goto err; \
}
#define CHECKERR(func) if((func) < 0) \
throw al::backend_exception{al::backend_error::DeviceError, "%s failed: %s\n", #func, \
std::generic_category().message(errno).c_str()};
/* Don't fail if SETFRAGMENT fails. We can handle just about anything
* that's reported back via GETOSPACE */
ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
@ -383,12 +410,6 @@ bool OSSPlayback::reset()
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
if(0)
{
err:
ERR("%s failed: %s\n", err, strerror(errno));
return false;
}
#undef CHECKERR
if(mDevice->channelsFromFmt() != numChannels)
@ -413,7 +434,7 @@ bool OSSPlayback::reset()
setDefaultChannelOrder();
mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
mMixData.resize(size_t{mDevice->UpdateSize} * mDevice->frameSizeFromFmt());
return true;
}
@ -437,7 +458,7 @@ void OSSPlayback::stop()
mThread.join();
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
ERR("Error resetting device: %s\n", strerror(errno));
ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str());
}
@ -447,10 +468,10 @@ struct OSScapture final : public BackendBase {
int recordProc();
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
int mFd{-1};
@ -459,8 +480,6 @@ struct OSScapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OSScapture)
};
OSScapture::~OSScapture()
@ -474,7 +493,7 @@ OSScapture::~OSScapture()
int OSScapture::recordProc()
{
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
althrd_setname(GetRecordThreadName());
const size_t frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire))
@ -483,16 +502,16 @@ int OSScapture::recordProc()
pollitem.fd = mFd;
pollitem.events = POLLIN;
int sret{poll(&pollitem, 1, 1000)};
if(sret < 0)
if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("poll failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed to check capture samples: %s", errstr.c_str());
break;
}
else if(sret == 0)
else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
{
WARN("poll timeout\n");
continue;
@ -504,8 +523,9 @@ int OSScapture::recordProc()
ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)};
if(amt < 0)
{
ERR("read failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("read failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed reading capture samples: %s", errstr.c_str());
break;
}
mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
@ -516,11 +536,11 @@ int OSScapture::recordProc()
}
void OSScapture::open(const char *name)
void OSScapture::open(std::string_view name)
{
const char *devname{DefaultCapture.c_str()};
if(!name)
name = DefaultName;
if(name.empty())
name = GetDefaultName();
else
{
if(CaptureDevices.empty())
@ -532,14 +552,14 @@ void OSScapture::open(const char *name)
);
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
devname = iter->device_name.c_str();
}
mFd = ::open(devname, O_RDONLY);
if(mFd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
std::generic_category().message(errno).c_str()};
int ossFormat{};
switch(mDevice->FmtType)
@ -566,13 +586,13 @@ void OSScapture::open(const char *name)
uint frameSize{numChannels * mDevice->bytesFromFmt()};
uint ossSpeed{mDevice->Frequency};
/* according to the OSS spec, 16 bytes are the minimum */
uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
uint log2FragmentSize{std::max(log2i(mDevice->BufferSize * frameSize / periods), 4u)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
#define CHECKERR(func) if((func) < 0) { \
throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \
strerror(errno)}; \
std::generic_category().message(errno).c_str()}; \
}
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
@ -617,11 +637,11 @@ void OSScapture::stop()
mThread.join();
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
ERR("Error resetting device: %s\n", strerror(errno));
ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str());
}
void OSScapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void OSScapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint OSScapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
@ -637,9 +657,9 @@ BackendFactory &OSSBackendFactory::getFactory()
bool OSSBackendFactory::init()
{
if(auto devopt = ConfigValueStr(nullptr, "oss", "device"))
if(auto devopt = ConfigValueStr({}, "oss", "device"))
DefaultPlayback = std::move(*devopt);
if(auto capopt = ConfigValueStr(nullptr, "oss", "capture"))
if(auto capopt = ConfigValueStr({}, "oss", "capture"))
DefaultCapture = std::move(*capopt);
return true;
@ -648,18 +668,13 @@ bool OSSBackendFactory::init()
bool OSSBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string OSSBackendFactory::probe(BackendType type)
auto OSSBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
struct stat buf;
if(stat(entry.device_name.c_str(), &buf) == 0)
{
/* Includes null char. */
outnames.append(entry.name.c_str(), entry.name.length()+1);
}
if(struct stat buf{}; stat(entry.device_name.c_str(), &buf) == 0)
outnames.emplace_back(entry.name);
};
switch(type)
@ -667,12 +682,14 @@ std::string OSSBackendFactory::probe(BackendType type)
case BackendType::Playback:
PlaybackDevices.clear();
ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices.clear();
ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}

View file

@ -5,15 +5,15 @@
struct OSSBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OSS_H */

File diff suppressed because it is too large Load diff

View file

@ -2,22 +2,26 @@
#define BACKENDS_PIPEWIRE_H
#include <string>
#include <vector>
#include "alc/events.h"
#include "base.h"
struct DeviceBase;
struct PipeWireBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_PIPEWIRE_H */

View file

@ -26,21 +26,20 @@
#include <cstdlib>
#include <cstring>
#include "albit.h"
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "alstring.h"
#include "core/device.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include <portaudio.h>
#include <portaudio.h> /* NOLINT(*-duplicate-include) Not the same header. */
namespace {
constexpr char pa_device[] = "PortAudio Default";
#ifdef HAVE_DYNLOAD
void *pa_handle;
#define MAKE_FUNC(x) decltype(x) * p##x
@ -51,6 +50,8 @@ MAKE_FUNC(Pa_StartStream);
MAKE_FUNC(Pa_StopStream);
MAKE_FUNC(Pa_OpenStream);
MAKE_FUNC(Pa_CloseStream);
MAKE_FUNC(Pa_GetDeviceCount);
MAKE_FUNC(Pa_GetDeviceInfo);
MAKE_FUNC(Pa_GetDefaultOutputDevice);
MAKE_FUNC(Pa_GetDefaultInputDevice);
MAKE_FUNC(Pa_GetStreamInfo);
@ -64,12 +65,49 @@ MAKE_FUNC(Pa_GetStreamInfo);
#define Pa_StopStream pPa_StopStream
#define Pa_OpenStream pPa_OpenStream
#define Pa_CloseStream pPa_CloseStream
#define Pa_GetDeviceCount pPa_GetDeviceCount
#define Pa_GetDeviceInfo pPa_GetDeviceInfo
#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
#define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
#define Pa_GetStreamInfo pPa_GetStreamInfo
#endif
#endif
struct DeviceEntry {
std::string mName;
bool mIsPlayback{};
bool mIsCapture{};
};
std::vector<DeviceEntry> DeviceNames;
void EnumerateDevices()
{
const auto devcount = Pa_GetDeviceCount();
if(devcount < 0)
{
ERR("Error getting device count: %s\n", Pa_GetErrorText(devcount));
return;
}
std::vector<DeviceEntry>(static_cast<uint>(devcount)).swap(DeviceNames);
PaDeviceIndex idx{0};
for(auto &entry : DeviceNames)
{
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;
#endif
entry.mIsPlayback = (info->maxOutputChannels > 0);
entry.mIsCapture = (info->maxInputChannels > 0);
TRACE("Device %d \"%s\": %d playback, %d capture channels\n", idx, entry.mName.c_str(),
info->maxOutputChannels, info->maxInputChannels);
}
++idx;
}
}
struct PortPlayback final : public BackendBase {
PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
@ -77,24 +115,17 @@ struct PortPlayback final : public BackendBase {
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
static int writeCallbackC(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);
}
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams{};
DevFmtChannels mChannelConfig{};
uint mAmbiOrder{};
uint mUpdateSize{0u};
DEF_NEWDEL(PortPlayback)
};
PortPlayback::~PortPlayback()
@ -115,22 +146,38 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f
}
void PortPlayback::open(const char *name)
void PortPlayback::open(std::string_view name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(DeviceNames.empty())
EnumerateDevices();
int deviceid{-1};
if(name.empty())
{
if(auto devidopt = ConfigValueInt({}, "port", "device"))
deviceid = *devidopt;
if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
deviceid = Pa_GetDefaultOutputDevice();
name = DeviceNames.at(static_cast<uint>(deviceid)).mName;
}
else
{
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
[name](const DeviceEntry &entry) { return entry.mIsPlayback && name == entry.mName; });
if(iter == DeviceNames.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
}
PaStreamParameters params{};
auto devidopt = ConfigValueInt(nullptr, "port", "device");
if(devidopt && *devidopt >= 0) params.device = *devidopt;
else params.device = Pa_GetDefaultOutputDevice();
params.device = deviceid;
params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
params.hostApiSpecificStreamInfo = nullptr;
params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
mChannelConfig = mDevice->FmtChans;
mAmbiOrder = mDevice->mAmbiOrder;
params.channelCount = static_cast<int>(mDevice->channelsFromFmt());
switch(mDevice->FmtType)
{
@ -155,19 +202,21 @@ void PortPlayback::open(const char *name)
break;
}
retry_open:
PaStream *stream{};
PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency, mDevice->UpdateSize,
paNoFlag, &PortPlayback::writeCallbackC, this)};
if(err != paNoError)
static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
if(params.sampleFormat == paFloat32)
{
params.sampleFormat = paInt16;
goto retry_open;
}
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
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);
@ -182,7 +231,18 @@ bool PortPlayback::reset()
{
const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
mDevice->Frequency = static_cast<uint>(streamInfo->sampleRate);
mDevice->FmtChans = mChannelConfig;
mDevice->mAmbiOrder = mAmbiOrder;
mDevice->UpdateSize = mUpdateSize;
mDevice->BufferSize = mUpdateSize * 2;
if(streamInfo->outputLatency > 0.0f)
{
const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate};
TRACE("Reported stream latency: %f sec (%f samples)\n", streamInfo->outputLatency,
sampleLatency);
mDevice->BufferSize = static_cast<uint>(std::clamp(sampleLatency,
double(mDevice->BufferSize), double{std::numeric_limits<int>::max()}));
}
if(mParams.sampleFormat == paInt8)
mDevice->FmtType = DevFmtByte;
@ -200,15 +260,6 @@ bool PortPlayback::reset()
return false;
}
if(mParams.channelCount >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(mParams.channelCount == 1)
mDevice->FmtChans = DevFmtMono;
else
{
ERR("Unexpected channel count: %u\n", mParams.channelCount);
return false;
}
setDefaultChannelOrder();
return true;
@ -216,16 +267,14 @@ bool PortPlayback::reset()
void PortPlayback::start()
{
const PaError err{Pa_StartStream(mStream)};
if(err == paNoError)
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
Pa_GetErrorText(err)};
}
void PortPlayback::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
}
@ -235,27 +284,18 @@ struct PortCapture final : public BackendBase {
~PortCapture() override;
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
static int readCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) const noexcept;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams;
PaStreamParameters mParams{};
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(PortCapture)
};
PortCapture::~PortCapture()
@ -268,30 +308,43 @@ PortCapture::~PortCapture()
int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) const noexcept
{
mRing->write(inputBuffer, framesPerBuffer);
std::ignore = mRing->write(inputBuffer, framesPerBuffer);
return 0;
}
void PortCapture::open(const char *name)
void PortCapture::open(std::string_view name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(DeviceNames.empty())
EnumerateDevices();
uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
uint frame_size{mDevice->frameSizeFromFmt()};
int deviceid{};
if(name.empty())
{
if(auto devidopt = ConfigValueInt({}, "port", "capture"))
deviceid = *devidopt;
if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
deviceid = Pa_GetDefaultInputDevice();
name = DeviceNames.at(static_cast<uint>(deviceid)).mName;
}
else
{
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
[name](const DeviceEntry &entry) { return entry.mIsCapture && name == entry.mName; });
if(iter == DeviceNames.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
}
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
const uint frame_size{mDevice->frameSizeFromFmt()};
mRing = RingBuffer::Create(samples, frame_size, false);
auto devidopt = ConfigValueInt(nullptr, "port", "capture");
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
else mParams.device = Pa_GetDefaultOutputDevice();
mParams.device = deviceid;
mParams.suggestedLatency = 0.0f;
mParams.hostApiSpecificStreamInfo = nullptr;
@ -319,8 +372,15 @@ void PortCapture::open(const char *name)
}
mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
static constexpr auto readCallback = [](const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
};
PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
paFramesPerBufferUnspecified, paNoFlag, readCallback, this)};
if(err != paNoError)
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
@ -331,16 +391,14 @@ void PortCapture::open(const char *name)
void PortCapture::start()
{
const PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording: %s", Pa_GetErrorText(err)};
}
void PortCapture::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
}
@ -348,16 +406,14 @@ void PortCapture::stop()
uint PortCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
void PortCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void PortCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
} // namespace
bool PortBackendFactory::init()
{
PaError err;
#ifdef HAVE_DYNLOAD
if(!pa_handle)
{
@ -391,12 +447,15 @@ bool PortBackendFactory::init()
LOAD_FUNC(Pa_StopStream);
LOAD_FUNC(Pa_OpenStream);
LOAD_FUNC(Pa_CloseStream);
LOAD_FUNC(Pa_GetDeviceCount);
LOAD_FUNC(Pa_GetDeviceInfo);
LOAD_FUNC(Pa_GetDefaultOutputDevice);
LOAD_FUNC(Pa_GetDefaultInputDevice);
LOAD_FUNC(Pa_GetStreamInfo);
#undef LOAD_FUNC
if((err=Pa_Initialize()) != paNoError)
const PaError err{Pa_Initialize()};
if(err != paNoError)
{
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
CloseLib(pa_handle);
@ -405,7 +464,8 @@ bool PortBackendFactory::init()
}
}
#else
if((err=Pa_Initialize()) != paNoError)
const PaError err{Pa_Initialize()};
if(err != paNoError)
{
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
return false;
@ -417,18 +477,52 @@ bool PortBackendFactory::init()
bool PortBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string PortBackendFactory::probe(BackendType type)
auto PortBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> devices;
EnumerateDevices();
int defaultid{-1};
switch(type)
{
case BackendType::Playback:
defaultid = Pa_GetDefaultOutputDevice();
if(auto devidopt = ConfigValueInt({}, "port", "device"); devidopt && *devidopt >= 0
&& static_cast<uint>(*devidopt) < DeviceNames.size())
defaultid = *devidopt;
for(size_t i{0};i < DeviceNames.size();++i)
{
if(DeviceNames[i].mIsPlayback)
{
if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
devices.emplace(devices.cbegin(), DeviceNames[i].mName);
else
devices.emplace_back(DeviceNames[i].mName);
}
}
break;
case BackendType::Capture:
/* Includes null char. */
outnames.append(pa_device, sizeof(pa_device));
defaultid = Pa_GetDefaultInputDevice();
if(auto devidopt = ConfigValueInt({}, "port", "capture"); devidopt && *devidopt >= 0
&& static_cast<uint>(*devidopt) < DeviceNames.size())
defaultid = *devidopt;
for(size_t i{0};i < DeviceNames.size();++i)
{
if(DeviceNames[i].mIsCapture)
{
if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
devices.emplace(devices.cbegin(), DeviceNames[i].mName);
else
devices.emplace_back(DeviceNames[i].mName);
}
}
break;
}
return outnames;
return devices;
}
BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct PortBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_PORTAUDIO_H */

File diff suppressed because it is too large Load diff

View file

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

View file

@ -26,6 +26,7 @@
#include <cstdlib>
#include <cstring>
#include <string>
#include <string_view>
#include "almalloc.h"
#include "alnumeric.h"
@ -46,17 +47,17 @@ namespace {
#define DEVNAME_PREFIX ""
#endif
constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; }
constexpr auto getDefaultDeviceName() noexcept -> std::string_view
{ return DEVNAME_PREFIX "Default Device"; }
struct Sdl2Backend final : public BackendBase {
Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
~Sdl2Backend() override;
void audioCallback(Uint8 *stream, int len) noexcept;
static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept
{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -68,8 +69,6 @@ struct Sdl2Backend final : public BackendBase {
DevFmtChannels mFmtChans{};
DevFmtType mFmtType{};
uint mUpdateSize{0u};
DEF_NEWDEL(Sdl2Backend)
};
Sdl2Backend::~Sdl2Backend()
@ -86,7 +85,7 @@ void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept
mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt());
}
void Sdl2Backend::open(const char *name)
void Sdl2Backend::open(std::string_view name)
{
SDL_AudioSpec want{}, have{};
@ -102,24 +101,39 @@ void Sdl2Backend::open(const char *name)
case DevFmtFloat: want.format = AUDIO_F32; break;
}
want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
want.samples = static_cast<Uint16>(minu(mDevice->UpdateSize, 8192));
want.callback = &Sdl2Backend::audioCallbackC;
want.samples = static_cast<Uint16>(std::min(mDevice->UpdateSize, 8192u));
want.callback = [](void *ptr, Uint8 *stream, int len) noexcept
{ return static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); };
want.userdata = this;
/* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
* necessarily the first in the list.
*/
const auto defaultDeviceName = getDefaultDeviceName();
SDL_AudioDeviceID devid;
if(!name || strcmp(name, defaultDeviceName) == 0)
if(name.empty() || name == defaultDeviceName)
{
name = defaultDeviceName;
devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
}
else
{
const size_t prefix_len = strlen(DEVNAME_PREFIX);
if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0)
devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
const auto namePrefix = getDevicePrefix();
if(name.size() >= namePrefix.size() && name.substr(0, namePrefix.size()) == namePrefix)
{
/* 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
devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
{
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()};
@ -161,7 +175,7 @@ void Sdl2Backend::open(const char *name)
mFmtType = devtype;
mUpdateSize = have.samples;
mDevice->DeviceName = name ? name : defaultDeviceName;
mDevice->DeviceName = name;
}
bool Sdl2Backend::reset()
@ -195,23 +209,27 @@ bool SDL2BackendFactory::init()
bool SDL2BackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string SDL2BackendFactory::probe(BackendType type)
auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
if(type != BackendType::Playback)
return outnames;
int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)};
if(num_devices <= 0)
return outnames;
/* Includes null char. */
outnames.append(defaultDeviceName, sizeof(defaultDeviceName));
outnames.reserve(static_cast<unsigned int>(num_devices));
outnames.emplace_back(getDefaultDeviceName());
for(int i{0};i < num_devices;++i)
{
std::string name{DEVNAME_PREFIX};
name += SDL_GetAudioDeviceName(i, SDL_FALSE);
if(!name.empty())
outnames.append(name.c_str(), name.length()+1);
std::string outname{getDevicePrefix()};
if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE))
outname += name;
else
outname += "Unknown Device Name #"+std::to_string(i);
outnames.emplace_back(std::move(outname));
}
return outnames;
}

View file

@ -5,15 +5,15 @@
struct SDL2BackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SDL2_H */

View file

@ -22,31 +22,35 @@
#include "sndio.h"
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <inttypes.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <system_error>
#include <thread>
#include <vector>
#include "alnumeric.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <sndio.h>
#include <sndio.h> /* NOLINT(*-duplicate-include) Not the same header. */
namespace {
static const char sndio_device[] = "SndIO Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "SndIO Default"sv; }
struct SioPar : public sio_par {
SioPar() { sio_initpar(this); }
SioPar() : sio_par{} { sio_initpar(this); }
void clear() { sio_initpar(this); }
};
@ -57,7 +61,7 @@ struct SndioPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -65,12 +69,10 @@ struct SndioPlayback final : public BackendBase {
sio_hdl *mSndHandle{nullptr};
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SndioPlayback)
};
SndioPlayback::~SndioPlayback()
@ -86,12 +88,12 @@ int SndioPlayback::mixerProc()
const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
al::span<al::byte> buffer{mBuffer};
al::span<std::byte> buffer{mBuffer};
mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize),
frameStep);
@ -112,13 +114,13 @@ int SndioPlayback::mixerProc()
}
void SndioPlayback::open(const char *name)
void SndioPlayback::open(std::string_view name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDefaultName();
else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
if(!sndHandle)
@ -136,72 +138,75 @@ bool SndioPlayback::reset()
SioPar par;
auto tryfmt = mDevice->FmtType;
retry_params:
switch(tryfmt)
while(true)
{
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
}
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = 1;
switch(tryfmt)
{
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
}
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = 1;
par.rate = mDevice->Frequency;
par.pchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.pchan = mDevice->channelsFromFmt();
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
try {
if(!sio_setpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device parameters"};
try {
if(!sio_setpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device parameters"};
par.clear();
if(!sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get device parameters"};
par.clear();
if(!sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get device parameters"};
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
if(par.pchan < 1)
throw al::backend_exception{al::backend_error::DeviceError,
"No playback channels on device"};
}
catch(al::backend_exception &e) {
if(tryfmt == DevFmtShort)
throw;
par.clear();
tryfmt = DevFmtShort;
goto retry_params;
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
if(par.pchan < 1)
throw al::backend_exception{al::backend_error::DeviceError,
"No playback channels on device"};
break;
}
catch(al::backend_exception &e) {
if(tryfmt == DevFmtShort)
throw;
par.clear();
tryfmt = DevFmtShort;
}
}
if(par.bps == 1)
@ -229,11 +234,11 @@ retry_params:
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.bufsz + par.round;
mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps);
mBuffer.resize(size_t{mDevice->UpdateSize} * par.pchan*par.bps);
if(par.sig == 1)
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
else if(par.bits == 8)
std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80));
std::fill_n(mBuffer.data(), mBuffer.size(), std::byte(0x80));
else if(par.bits == 16)
std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000);
else if(par.bits == 32)
@ -280,10 +285,10 @@ struct SndioCapture final : public BackendBase {
int recordProc();
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
sio_hdl *mSndHandle{nullptr};
@ -292,8 +297,6 @@ struct SndioCapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SndioCapture)
};
SndioCapture::~SndioCapture()
@ -306,7 +309,7 @@ SndioCapture::~SndioCapture()
int SndioCapture::recordProc()
{
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
althrd_setname(GetRecordThreadName());
const uint frameSize{mDevice->frameSizeFromFmt()};
@ -317,29 +320,30 @@ int SndioCapture::recordProc()
return 1;
}
auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre));
auto fds = std::vector<pollfd>(static_cast<uint>(nfds_pre));
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
/* Wait until there's some samples to read. */
const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)};
if(nfds <= 0)
{
mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
break;
}
int pollres{::poll(fds.get(), static_cast<uint>(nfds), 2000)};
int pollres{::poll(fds.data(), fds.size(), 2000)};
if(pollres < 0)
{
if(errno == EINTR) continue;
mDevice->handleDisconnect("Poll error: %s", strerror(errno));
mDevice->handleDisconnect("Poll error: %s",
std::generic_category().message(errno).c_str());
break;
}
if(pollres == 0)
continue;
const int revents{sio_revents(mSndHandle, fds.get())};
const int revents{sio_revents(mSndHandle, fds.data())};
if((revents&POLLHUP))
{
mDevice->handleDisconnect("Got POLLHUP from poll events");
@ -349,7 +353,7 @@ int SndioCapture::recordProc()
continue;
auto data = mRing->getWriteVector();
al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
al::span<std::byte> buffer{data.first.buf, data.first.len*frameSize};
while(!buffer.empty())
{
size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
@ -373,8 +377,8 @@ int SndioCapture::recordProc()
if(buffer.empty())
{
/* Got samples to read, but no place to store it. Drop it. */
static char junk[4096];
sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize));
static std::array<char,4096> junk;
sio_read(mSndHandle, junk.data(), junk.size() - (junk.size()%frameSize));
}
}
@ -382,13 +386,13 @@ int SndioCapture::recordProc()
}
void SndioCapture::open(const char *name)
void SndioCapture::open(std::string_view name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDefaultName();
else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
mSndHandle = sio_open(nullptr, SIO_REC, true);
if(mSndHandle == nullptr)
@ -431,12 +435,12 @@ void SndioCapture::open(const char *name)
par.rchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u);
par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u);
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device praameters"};
"Failed to set device parameters"};
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
@ -461,7 +465,7 @@ void SndioCapture::open(const char *name)
DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false);
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
mDevice->UpdateSize = par.round;
@ -496,8 +500,8 @@ void SndioCapture::stop()
ERR("Error stopping device\n");
}
void SndioCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void SndioCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint SndioCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
@ -516,18 +520,15 @@ bool SndIOBackendFactory::init()
bool SndIOBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string SndIOBackendFactory::probe(BackendType type)
auto SndIOBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(sndio_device, sizeof(sndio_device));
break;
return std::vector{std::string{GetDefaultName()}};
}
return outnames;
return {};
}
BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct SndIOBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SNDIO_H */

View file

@ -35,24 +35,26 @@
#include <poll.h>
#include <math.h>
#include <string.h>
#include <vector>
#include <thread>
#include <functional>
#include "albyte.h"
#include "alc/alconfig.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "threads.h"
#include "vector.h"
#include <sys/audioio.h>
namespace {
constexpr char solaris_device[] = "Solaris Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "Solaris Default"sv; }
std::string solaris_driver{"/dev/audio"};
@ -63,7 +65,7 @@ struct SolarisBackend final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -71,12 +73,10 @@ struct SolarisBackend final : public BackendBase {
int mFd{-1};
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SolarisBackend)
};
SolarisBackend::~SolarisBackend()
@ -89,10 +89,10 @@ SolarisBackend::~SolarisBackend()
int SolarisBackend::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frame_step{mDevice->channelsFromFmt()};
const uint frame_size{mDevice->frameSizeFromFmt()};
const size_t frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
@ -116,12 +116,12 @@ int SolarisBackend::mixerProc()
continue;
}
al::byte *write_ptr{mBuffer.data()};
size_t to_write{mBuffer.size()};
mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
al::span<std::byte> buffer{mBuffer};
mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size()/frame_size),
frame_step);
while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
ssize_t wrote{write(mFd, buffer.data(), buffer.size())};
if(wrote < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
@ -131,8 +131,7 @@ int SolarisBackend::mixerProc()
break;
}
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
buffer = buffer.subspan(static_cast<size_t>(wrote));
}
}
@ -140,13 +139,13 @@ int SolarisBackend::mixerProc()
}
void SolarisBackend::open(const char *name)
void SolarisBackend::open(std::string_view name)
{
if(!name)
name = solaris_device;
else if(strcmp(name, solaris_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDefaultName();
else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
int fd{::open(solaris_driver.c_str(), O_WRONLY)};
if(fd == -1)
@ -231,7 +230,7 @@ bool SolarisBackend::reset()
setDefaultChannelOrder();
mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
return true;
}
@ -268,7 +267,7 @@ BackendFactory &SolarisBackendFactory::getFactory()
bool SolarisBackendFactory::init()
{
if(auto devopt = ConfigValueStr(nullptr, "solaris", "device"))
if(auto devopt = ConfigValueStr({}, "solaris", "device"))
solaris_driver = std::move(*devopt);
return true;
}
@ -276,23 +275,19 @@ bool SolarisBackendFactory::init()
bool SolarisBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string SolarisBackendFactory::probe(BackendType type)
auto SolarisBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
{
struct stat buf;
if(stat(solaris_driver.c_str(), &buf) == 0)
outnames.append(solaris_device, sizeof(solaris_device));
}
break;
if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0)
return std::vector{std::string{GetDefaultName()}};
break;
case BackendType::Capture:
break;
}
return outnames;
return {};
}
BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct SolarisBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SOLARIS_H */

File diff suppressed because it is too large Load diff

View file

@ -5,15 +5,17 @@
struct WasapiBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WASAPI_H */

View file

@ -31,24 +31,26 @@
#include <cstring>
#include <exception>
#include <functional>
#include <system_error>
#include <thread>
#include <vector>
#include "albit.h"
#include "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "opthelpers.h"
#include "strutils.h"
#include "threads.h"
#include "vector.h"
namespace {
using namespace std::string_view_literals;
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
@ -56,38 +58,43 @@ using std::chrono::nanoseconds;
using ubyte = unsigned char;
using ushort = unsigned short;
constexpr char waveDevice[] = "Wave File Writer";
struct FileDeleter {
void operator()(gsl::owner<FILE*> f) { fclose(f); }
};
using FilePtr = std::unique_ptr<FILE,FileDeleter>;
constexpr ubyte SUBTYPE_PCM[]{
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Wave File Writer"sv; }
constexpr std::array<ubyte,16> SUBTYPE_PCM{{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
constexpr ubyte SUBTYPE_FLOAT[]{
}};
constexpr std::array<ubyte,16> SUBTYPE_FLOAT{{
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
}};
constexpr ubyte SUBTYPE_BFORMAT_PCM[]{
constexpr std::array<ubyte,16> SUBTYPE_BFORMAT_PCM{{
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
}};
constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{
constexpr std::array<ubyte,16> SUBTYPE_BFORMAT_FLOAT{{
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
}};
void fwrite16le(ushort val, FILE *f)
{
ubyte data[2]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff) };
fwrite(data, 1, 2, f);
std::array data{static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff)};
fwrite(data.data(), 1, data.size(), f);
}
void fwrite32le(uint val, FILE *f)
{
ubyte data[4]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff) };
fwrite(data, 1, 4, f);
std::array data{static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff)};
fwrite(data.data(), 1, data.size(), f);
}
@ -97,34 +104,27 @@ struct WaveBackend final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
FILE *mFile{nullptr};
FilePtr mFile{nullptr};
long mDataStart{-1};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WaveBackend)
};
WaveBackend::~WaveBackend()
{
if(mFile)
fclose(mFile);
mFile = nullptr;
}
WaveBackend::~WaveBackend() = default;
int WaveBackend::mixerProc()
{
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frameStep{mDevice->channelsFromFmt()};
const size_t frameSize{mDevice->frameSizeFromFmt()};
@ -155,13 +155,13 @@ int WaveBackend::mixerProc()
if(bytesize == 2)
{
const size_t len{mBuffer.size() & ~size_t{1}};
const size_t len{mBuffer.size() & ~1_uz};
for(size_t i{0};i < len;i+=2)
std::swap(mBuffer[i], mBuffer[i+1]);
}
else if(bytesize == 4)
{
const size_t len{mBuffer.size() & ~size_t{3}};
const size_t len{mBuffer.size() & ~3_uz};
for(size_t i{0};i < len;i+=4)
{
std::swap(mBuffer[i ], mBuffer[i+3]);
@ -170,8 +170,8 @@ int WaveBackend::mixerProc()
}
}
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
if(fs < mDevice->UpdateSize || ferror(mFile))
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile.get())};
if(fs < mDevice->UpdateSize || ferror(mFile.get()))
{
ERR("Error writing to file\n");
mDevice->handleDisconnect("Failed to write playback samples");
@ -195,32 +195,32 @@ int WaveBackend::mixerProc()
return 0;
}
void WaveBackend::open(const char *name)
void WaveBackend::open(std::string_view name)
{
auto fname = ConfigValueStr(nullptr, "wave", "file");
auto fname = ConfigValueStr({}, "wave", "file");
if(!fname) throw al::backend_exception{al::backend_error::NoDevice,
"No wave output filename"};
if(!name)
name = waveDevice;
else if(strcmp(name, waveDevice) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
/* There's only one "device", so if it's already open, we're done. */
if(mFile) return;
#ifdef _WIN32
{
std::wstring wname{utf8_to_wstr(fname->c_str())};
mFile = _wfopen(wname.c_str(), L"wb");
std::wstring wname{utf8_to_wstr(fname.value())};
mFile = FilePtr{_wfopen(wname.c_str(), L"wb")};
}
#else
mFile = fopen(fname->c_str(), "wb");
mFile = FilePtr{fopen(fname->c_str(), "wb")};
#endif
if(!mFile)
throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s",
fname->c_str(), strerror(errno)};
fname->c_str(), std::generic_category().message(errno).c_str()};
mDevice->DeviceName = name;
}
@ -229,12 +229,11 @@ bool WaveBackend::reset()
{
uint channels{0}, bytes{0}, chanmask{0};
bool isbformat{false};
size_t val;
fseek(mFile, 0, SEEK_SET);
clearerr(mFile);
fseek(mFile.get(), 0, SEEK_SET);
clearerr(mFile.get());
if(GetConfigValueBool(nullptr, "wave", "bformat", false))
if(GetConfigValueBool({}, "wave", "bformat", false))
{
mDevice->FmtChans = DevFmtAmbi3D;
mDevice->mAmbiOrder = 1;
@ -265,6 +264,9 @@ bool WaveBackend::reset()
case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break;
case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtX7144:
mDevice->FmtChans = DevFmtX714;
[[fallthrough]];
case DevFmtX714:
chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000
| 0x8000 | 0x20000;
@ -273,7 +275,7 @@ bool WaveBackend::reset()
case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtAmbi3D:
/* .amb output requires FuMa */
mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
mDevice->mAmbiOrder = std::min(mDevice->mAmbiOrder, 3u);
mDevice->mAmbiLayout = DevAmbiLayout::FuMa;
mDevice->mAmbiScale = DevAmbiScaling::FuMa;
isbformat = true;
@ -283,49 +285,48 @@ bool WaveBackend::reset()
bytes = mDevice->bytesFromFmt();
channels = mDevice->channelsFromFmt();
rewind(mFile);
rewind(mFile.get());
fputs("RIFF", mFile);
fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close
fputs("RIFF", mFile.get());
fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close
fputs("WAVE", mFile);
fputs("WAVE", mFile.get());
fputs("fmt ", mFile);
fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
fputs("fmt ", mFile.get());
fwrite32le(40, mFile.get()); // 'fmt ' header len; 40 bytes for EXTENSIBLE
// 16-bit val, format type id (extensible: 0xFFFE)
fwrite16le(0xFFFE, mFile);
fwrite16le(0xFFFE, mFile.get());
// 16-bit val, channel count
fwrite16le(static_cast<ushort>(channels), mFile);
fwrite16le(static_cast<ushort>(channels), mFile.get());
// 32-bit val, frequency
fwrite32le(mDevice->Frequency, mFile);
fwrite32le(mDevice->Frequency, mFile.get());
// 32-bit val, bytes per second
fwrite32le(mDevice->Frequency * channels * bytes, mFile);
fwrite32le(mDevice->Frequency * channels * bytes, mFile.get());
// 16-bit val, frame size
fwrite16le(static_cast<ushort>(channels * bytes), mFile);
fwrite16le(static_cast<ushort>(channels * bytes), mFile.get());
// 16-bit val, bits per sample
fwrite16le(static_cast<ushort>(bytes * 8), mFile);
fwrite16le(static_cast<ushort>(bytes * 8), mFile.get());
// 16-bit val, extra byte count
fwrite16le(22, mFile);
fwrite16le(22, mFile.get());
// 16-bit val, valid bits per sample
fwrite16le(static_cast<ushort>(bytes * 8), mFile);
fwrite16le(static_cast<ushort>(bytes * 8), mFile.get());
// 32-bit val, channel mask
fwrite32le(chanmask, mFile);
fwrite32le(chanmask, mFile.get());
// 16 byte GUID, sub-type format
val = fwrite((mDevice->FmtType == DevFmtFloat) ?
(isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
(isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
(void)val;
std::ignore = fwrite((mDevice->FmtType == DevFmtFloat) ?
(isbformat ? SUBTYPE_BFORMAT_FLOAT.data() : SUBTYPE_FLOAT.data()) :
(isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get());
fputs("data", mFile);
fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close
fputs("data", mFile.get());
fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at close
if(ferror(mFile))
if(ferror(mFile.get()))
{
ERR("Error writing header: %s\n", strerror(errno));
ERR("Error writing header: %s\n", std::generic_category().message(errno).c_str());
return false;
}
mDataStart = ftell(mFile);
mDataStart = ftell(mFile.get());
setDefaultWFXChannelOrder();
@ -337,7 +338,7 @@ bool WaveBackend::reset()
void WaveBackend::start()
{
if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0)
if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0)
WARN("Failed to seek on output file\n");
try {
mKillNow.store(false, std::memory_order_release);
@ -357,14 +358,14 @@ void WaveBackend::stop()
if(mDataStart > 0)
{
long size{ftell(mFile)};
long size{ftell(mFile.get())};
if(size > 0)
{
long dataLen{size - mDataStart};
if(fseek(mFile, 4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(size-8), mFile); // 'WAVE' header len
if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(dataLen), mFile); // 'data' header len
if(fseek(mFile.get(), 4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(size-8), mFile.get()); // 'WAVE' header len
if(fseek(mFile.get(), mDataStart-4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(dataLen), mFile.get()); // 'data' header len
}
}
}
@ -378,19 +379,16 @@ bool WaveBackendFactory::init()
bool WaveBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string WaveBackendFactory::probe(BackendType type)
auto WaveBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
/* Includes null char. */
outnames.append(waveDevice, sizeof(waveDevice));
break;
return std::vector{std::string{GetDeviceName()}};
case BackendType::Capture:
break;
}
return outnames;
return {};
}
BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct WaveBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WAVE_H */

View file

@ -22,8 +22,8 @@
#include "winmm.h"
#include <stdlib.h>
#include <stdio.h>
#include <cstdlib>
#include <cstdio>
#include <memory.h>
#include <windows.h>
@ -38,13 +38,15 @@
#include <algorithm>
#include <functional>
#include "alnumeric.h"
#include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "strutils.h"
#include "threads.h"
#include "vector.h"
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
@ -55,13 +57,13 @@ namespace {
#define DEVNAME_HEAD "OpenAL Soft on "
al::vector<std::string> PlaybackDevices;
al::vector<std::string> CaptureDevices;
std::vector<std::string> PlaybackDevices;
std::vector<std::string> CaptureDevices;
bool checkName(const al::vector<std::string> &list, const std::string &name)
bool checkName(const std::vector<std::string> &list, const std::string &name)
{ return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
void ProbePlaybackDevices(void)
void ProbePlaybackDevices()
{
PlaybackDevices.clear();
@ -74,7 +76,7 @@ void ProbePlaybackDevices(void)
WAVEOUTCAPSW WaveCaps{};
if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))};
int count{1};
std::string newname{basename};
@ -92,7 +94,7 @@ void ProbePlaybackDevices(void)
}
}
void ProbeCaptureDevices(void)
void ProbeCaptureDevices()
{
CaptureDevices.clear();
@ -105,7 +107,7 @@ void ProbeCaptureDevices(void)
WAVEINCAPSW WaveCaps{};
if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))};
int count{1};
std::string newname{basename};
@ -134,7 +136,7 @@ struct WinMMPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -143,6 +145,7 @@ struct WinMMPlayback final : public BackendBase {
al::semaphore mSem;
uint mIdx{0u};
std::array<WAVEHDR,4> mWaveBuffer{};
al::vector<char,16> mBuffer;
HWAVEOUT mOutHdl{nullptr};
@ -150,8 +153,6 @@ struct WinMMPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WinMMPlayback)
};
WinMMPlayback::~WinMMPlayback()
@ -159,14 +160,11 @@ WinMMPlayback::~WinMMPlayback()
if(mOutHdl)
waveOutClose(mOutHdl);
mOutHdl = nullptr;
al_free(mWaveBuffer[0].lpData);
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
}
/* WinMMPlayback::waveOutProc
*
* Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
* Posts a message to 'WinMMPlayback::mixerProc' every time a WaveOut Buffer is
* completed and returns to the application (for more data)
*/
void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
@ -179,7 +177,7 @@ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PT
FORCE_ALIGN int WinMMPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
@ -207,59 +205,55 @@ FORCE_ALIGN int WinMMPlayback::mixerProc()
}
void WinMMPlayback::open(const char *name)
void WinMMPlayback::open(std::string_view name)
{
if(PlaybackDevices.empty())
ProbePlaybackDevices();
// Find the Device ID matching the deviceName if valid
auto iter = name ?
auto iter = !name.empty() ?
std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
PlaybackDevices.cbegin();
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
DevFmtType fmttype{mDevice->FmtType};
retry_open:
WAVEFORMATEX format{};
if(fmttype == DevFmtFloat)
{
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = 32;
}
else
{
format.wFormatTag = WAVE_FORMAT_PCM;
if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
format.wBitsPerSample = 8;
else
format.wBitsPerSample = 16;
}
format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
format.nSamplesPerSec = mDevice->Frequency;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
HWAVEOUT outHandle{};
MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format,
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res != MMSYSERR_NOERROR)
{
do {
format = WAVEFORMATEX{};
if(fmttype == DevFmtFloat)
{
fmttype = DevFmtShort;
goto retry_open;
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = 32;
}
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
}
else
{
format.wFormatTag = WAVE_FORMAT_PCM;
if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
format.wBitsPerSample = 8;
else
format.wBitsPerSample = 16;
}
format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
format.nSamplesPerSec = mDevice->Frequency;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &format,
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res == MMSYSERR_NOERROR) break;
if(fmttype != DevFmtFloat)
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u",
res};
fmttype = DevFmtShort;
} while(true);
if(mOutHdl)
waveOutClose(mOutHdl);
mOutHdl = outHandle;
mFormat = format;
mDevice->DeviceName = PlaybackDevices[DeviceID];
@ -312,11 +306,11 @@ bool WinMMPlayback::reset()
}
setDefaultWFXChannelOrder();
uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
al_free(mWaveBuffer[0].lpData);
decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
mWaveBuffer[0] = WAVEHDR{};
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
mWaveBuffer[0].lpData = mBuffer.data();
mWaveBuffer[0].dwBufferLength = BufferSize;
for(size_t i{1};i < mWaveBuffer.size();i++)
{
@ -369,16 +363,17 @@ struct WinMMCapture final : public BackendBase {
int captureProc();
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
std::atomic<uint> mReadable{0u};
al::semaphore mSem;
uint mIdx{0};
std::array<WAVEHDR,4> mWaveBuffer{};
al::vector<char,16> mBuffer;
HWAVEIN mInHdl{nullptr};
@ -388,8 +383,6 @@ struct WinMMCapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WinMMCapture)
};
WinMMCapture::~WinMMCapture()
@ -398,14 +391,11 @@ WinMMCapture::~WinMMCapture()
if(mInHdl)
waveInClose(mInHdl);
mInHdl = nullptr;
al_free(mWaveBuffer[0].lpData);
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
}
/* WinMMCapture::waveInProc
*
* Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
* Posts a message to 'WinMMCapture::captureProc' every time a WaveIn Buffer is
* completed and returns to the application (with more data).
*/
void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
@ -417,7 +407,7 @@ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR)
int WinMMCapture::captureProc()
{
althrd_setname(RECORD_THREAD_NAME);
althrd_setname(GetRecordThreadName());
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
@ -434,7 +424,8 @@ int WinMMCapture::captureProc()
WAVEHDR &waveHdr = mWaveBuffer[widx];
widx = (widx+1) % mWaveBuffer.size();
mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
std::ignore = mRing->write(waveHdr.lpData,
waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
mReadable.fetch_sub(1, std::memory_order_acq_rel);
waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
} while(--todo);
@ -445,18 +436,18 @@ int WinMMCapture::captureProc()
}
void WinMMCapture::open(const char *name)
void WinMMCapture::open(std::string_view name)
{
if(CaptureDevices.empty())
ProbeCaptureDevices();
// Find the Device ID matching the deviceName if valid
auto iter = name ?
auto iter = !name.empty() ?
std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
CaptureDevices.cbegin();
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
switch(mDevice->FmtChans)
@ -470,6 +461,7 @@ void WinMMCapture::open(const char *name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
@ -513,14 +505,14 @@ void WinMMCapture::open(const char *name)
// Allocate circular memory buffer for the captured audio
// Make sure circular buffer is at least 100ms in size
uint CapturedDataSize{mDevice->BufferSize};
CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
const auto CapturedDataSize = std::max<size_t>(mDevice->BufferSize,
BufferSize*mWaveBuffer.size());
mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
al_free(mWaveBuffer[0].lpData);
decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
mWaveBuffer[0] = WAVEHDR{};
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
mWaveBuffer[0].lpData = mBuffer.data();
mWaveBuffer[0].dwBufferLength = BufferSize;
for(size_t i{1};i < mWaveBuffer.size();++i)
{
@ -571,8 +563,8 @@ void WinMMCapture::stop()
mIdx = 0;
}
void WinMMCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void WinMMCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint WinMMCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
@ -586,26 +578,23 @@ bool WinMMBackendFactory::init()
bool WinMMBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string WinMMBackendFactory::probe(BackendType type)
auto WinMMBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const std::string &dname) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
if(!dname.empty())
outnames.append(dname.c_str(), dname.length()+1);
};
{ if(!dname.empty()) outnames.emplace_back(dname); };
switch(type)
{
case BackendType::Playback:
ProbePlaybackDevices();
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
ProbeCaptureDevices();
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}

View file

@ -5,15 +5,15 @@
struct WinMMBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WINMM_H */

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