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_WERROR)
mark_as_advanced(SDL_X11) mark_as_advanced(SDL_X11)
mark_as_advanced(SDL_XINPUT) mark_as_advanced(SDL_XINPUT)
mark_as_advanced(SDL2_DIR)
add_subdirectory(sdl ${TORQUE_LIB_TARG_DIRECTORY}/sdl2 EXCLUDE_FROM_ALL) add_subdirectory(sdl ${TORQUE_LIB_TARG_DIRECTORY}/sdl2 EXCLUDE_FROM_ALL)
add_subdirectory(nativeFileDialogs ${TORQUE_LIB_TARG_DIRECTORY}/nfd 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) if(TORQUE_SFX_OPENAL)
advanced_option(TORQUE_OGGVORBIS "Enable OGG Vorbis" ON) advanced_option(TORQUE_OGGVORBIS "Enable OGG Vorbis" ON)
advanced_option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32}) advanced_option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32})
advanced_option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" ON) advanced_option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" OFF)
advanced_option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" ON) 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) advanced_option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON)
mark_as_advanced(ALSOFT_REQUIRE_OBOE) mark_as_advanced(ALSOFT_BACKEND_COREAUDIO)
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_DSOUND)
mark_as_advanced(ALSOFT_BACKEND_MMDEVAPI) 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_WAVE)
mark_as_advanced(ALSOFT_BACKEND_WINMM) 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_SSE)
mark_as_advanced(ALSOFT_CPUEXT_SSE2) mark_as_advanced(ALSOFT_CPUEXT_SSE2)
mark_as_advanced(ALSOFT_CPUEXT_SSE3) mark_as_advanced(ALSOFT_CPUEXT_SSE3)
mark_as_advanced(ALSOFT_CPUEXT_SSE4_1) mark_as_advanced(ALSOFT_CPUEXT_SSE4_1)
mark_as_advanced(ALSOFT_DLOPEN) mark_as_advanced(ALSOFT_DLOPEN)
mark_as_advanced(ALSOFT_EMBED_HRTF_DATA) mark_as_advanced(ALSOFT_EAX)
mark_as_advanced(ALSOFT_EXAMPLES) advanced_option(ALSOFT_EMBED_HRTF_DATA "Embed the HRTF data files (increases library footprint)" OFF)
mark_as_advanced(ALSOFT_INSTALL_HRTF_DATA) advanced_option(ALSOFT_EXAMPLES "Build example programs" OFF)
mark_as_advanced(ALSOFT_INSTALL) 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_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_COREAUDIO)
mark_as_advanced(ALSOFT_REQUIRE_DSOUND) mark_as_advanced(ALSOFT_REQUIRE_DSOUND)
mark_as_advanced(ALSOFT_REQUIRE_JACK) mark_as_advanced(ALSOFT_REQUIRE_JACK)
mark_as_advanced(ALSOFT_REQUIRE_MMDEVAPI)
mark_as_advanced(ALSOFT_REQUIRE_NEON) mark_as_advanced(ALSOFT_REQUIRE_NEON)
mark_as_advanced(ALSOFT_REQUIRE_OBOE)
mark_as_advanced(ALSOFT_REQUIRE_OPENSL) 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_PORTAUDIO)
mark_as_advanced(ALSOFT_REQUIRE_PULSEAUDIO) mark_as_advanced(ALSOFT_REQUIRE_PULSEAUDIO)
mark_as_advanced(ALSOFT_REQUIRE_QSA) mark_as_advanced(ALSOFT_REQUIRE_SDL2)
mark_as_advanced(ALSOFT_REQUIRE_SNDIO)
mark_as_advanced(ALSOFT_REQUIRE_SOLARIS)
mark_as_advanced(ALSOFT_REQUIRE_SSE) mark_as_advanced(ALSOFT_REQUIRE_SSE)
mark_as_advanced(ALSOFT_REQUIRE_SSE2) mark_as_advanced(ALSOFT_REQUIRE_SSE2)
mark_as_advanced(ALSOFT_REQUIRE_SSE3)
mark_as_advanced(ALSOFT_REQUIRE_SSE4_1) mark_as_advanced(ALSOFT_REQUIRE_SSE4_1)
mark_as_advanced(ALSOFT_REQUIRE_WASAPI)
mark_as_advanced(ALSOFT_REQUIRE_WINMM) mark_as_advanced(ALSOFT_REQUIRE_WINMM)
mark_as_advanced(ALSOFT_SEARCH_INSTALL_DATADIR)
mark_as_advanced(ALSOFT_TESTS) mark_as_advanced(ALSOFT_TESTS)
mark_as_advanced(ALSOFT_UTILS) mark_as_advanced(ALSOFT_UTILS)
mark_as_advanced(ALSOFT_WERROR) 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() endif()
advanced_option(INSTALL_DOCS "" OFF) 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(INSTALL_DOCS "" OFF)
advanced_option(OPUS_INSTALL_PKG_CONFIG_MODULE "" OFF) advanced_option(OPUS_INSTALL_PKG_CONFIG_MODULE "" OFF)
advanced_option(OPUS_INSTALL_CMAKE_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(opus ${TORQUE_LIB_TARG_DIRECTORY}/opus EXCLUDE_FROM_ALL)
add_subdirectory(libtheora ${TORQUE_LIB_TARG_DIRECTORY}/libtheora 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_PROGRAMS "" OFF)
advanced_option(BUILD_EXAMPLES "" OFF) advanced_option(BUILD_EXAMPLES "" OFF)
advanced_option(ENABLE_CPACK "" OFF) advanced_option(ENABLE_CPACK "" OFF)
@ -289,3 +308,28 @@ if(TORQUE_TESTING)
add_subdirectory(gtest ${TORQUE_LIB_TARG_DIRECTORY}/gtest EXCLUDE_FROM_ALL) add_subdirectory(gtest ${TORQUE_LIB_TARG_DIRECTORY}/gtest EXCLUDE_FROM_ALL)
endif() 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 name: CI
on: [push] on: [push, pull_request]
jobs: jobs:
build: build:
@ -14,6 +14,7 @@ jobs:
name: "Win32-Release", name: "Win32-Release",
os: windows-latest, os: windows-latest,
cmake_opts: "-A Win32 \ cmake_opts: "-A Win32 \
-DALSOFT_TESTS=ON \
-DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \ -DALSOFT_REQUIRE_DSOUND=ON \
@ -24,6 +25,7 @@ jobs:
name: "Win32-Debug", name: "Win32-Debug",
os: windows-latest, os: windows-latest,
cmake_opts: "-A Win32 \ cmake_opts: "-A Win32 \
-DALSOFT_TESTS=ON \
-DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \ -DALSOFT_REQUIRE_DSOUND=ON \
@ -34,6 +36,7 @@ jobs:
name: "Win64-Release", name: "Win64-Release",
os: windows-latest, os: windows-latest,
cmake_opts: "-A x64 \ cmake_opts: "-A x64 \
-DALSOFT_TESTS=ON \
-DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \ -DALSOFT_REQUIRE_DSOUND=ON \
@ -44,16 +47,42 @@ jobs:
name: "Win64-Debug", name: "Win64-Debug",
os: windows-latest, os: windows-latest,
cmake_opts: "-A x64 \ cmake_opts: "-A x64 \
-DALSOFT_TESTS=ON \
-DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \ -DALSOFT_REQUIRE_DSOUND=ON \
-DALSOFT_REQUIRE_WASAPI=ON", -DALSOFT_REQUIRE_WASAPI=ON",
build_type: "Debug" 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", name: "macOS-Release",
os: macos-latest, 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" build_type: "Release"
} }
- { - {
@ -65,7 +94,8 @@ jobs:
-DALSOFT_REQUIRE_PORTAUDIO=ON \ -DALSOFT_REQUIRE_PORTAUDIO=ON \
-DALSOFT_REQUIRE_PULSEAUDIO=ON \ -DALSOFT_REQUIRE_PULSEAUDIO=ON \
-DALSOFT_REQUIRE_JACK=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 \ deps_cmdline: "sudo apt update && sudo apt-get install -qq \
libpulse-dev \ libpulse-dev \
portaudio19-dev \ portaudio19-dev \
@ -78,7 +108,7 @@ jobs:
} }
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- name: Install Dependencies - name: Install Dependencies
shell: bash shell: bash
@ -97,6 +127,12 @@ jobs:
run: | run: |
cmake --build build --config ${{matrix.config.build_type}} cmake --build build --config ${{matrix.config.build_type}}
- name: Test
shell: bash
run: |
cd build
ctest
- name: Create Archive - name: Create Archive
if: ${{ matrix.config.os == 'windows-latest' }} if: ${{ matrix.config.os == 'windows-latest' }}
shell: bash shell: bash
@ -109,7 +145,7 @@ jobs:
- name: Upload Archive - name: Upload Archive
# Upload package as an artifact of this workflow. # 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' }} if: ${{ matrix.config.os == 'windows-latest' }}
with: with:
name: soft_oal-${{matrix.config.name}} 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" copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll"
- name: Upload makemhr artifact - name: Upload makemhr artifact
uses: actions/upload-artifact@v3.1.1 uses: actions/upload-artifact@v3.1.2
with: with:
name: makemhr name: makemhr
path: "Artifacts/" path: "Artifacts/"

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,23 @@
#ifndef AL_BUFFER_H #ifndef AL_BUFFER_H
#define AL_BUFFER_H #define AL_BUFFER_H
#include <array>
#include <atomic> #include <atomic>
#include <cstddef>
#include <cstdint>
#include <string_view>
#include <utility>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h"
#include "albyte.h"
#include "alc/inprogext.h" #include "alc/inprogext.h"
#include "almalloc.h" #include "almalloc.h"
#include "atomic.h" #include "alnumeric.h"
#include "core/buffer_storage.h" #include "core/buffer_storage.h"
#include "vector.h" #include "vector.h"
#ifdef ALSOFT_EAX #ifdef ALSOFT_EAX
#include "eax/x_ram.h"
enum class EaxStorage : uint8_t { enum class EaxStorage : uint8_t {
Automatic, Automatic,
Accessible, Accessible,
@ -26,7 +29,7 @@ enum class EaxStorage : uint8_t {
struct ALbuffer : public BufferStorage { struct ALbuffer : public BufferStorage {
ALbitfieldSOFT Access{0u}; ALbitfieldSOFT Access{0u};
al::vector<al::byte,16> mDataStorage; al::vector<std::byte,16> mDataStorage;
ALuint OriginalSize{0}; ALuint OriginalSize{0};
@ -42,12 +45,14 @@ struct ALbuffer : public BufferStorage {
ALuint mLoopEnd{0u}; ALuint mLoopEnd{0u};
/* Number of times buffer was attached to a source (deletion can only occur when 0) */ /* 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 */ /* Self ID */
ALuint id{0}; ALuint id{0};
DISABLE_ALLOC() static void SetName(ALCcontext *context, ALuint id, std::string_view name);
DISABLE_ALLOC
#ifdef ALSOFT_EAX #ifdef ALSOFT_EAX
EaxStorage eax_x_ram_mode{EaxStorage::Automatic}; EaxStorage eax_x_ram_mode{EaxStorage::Automatic};
@ -55,4 +60,19 @@ struct ALbuffer : public BufferStorage {
#endif // ALSOFT_EAX #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 #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 <cfloat>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <tuple>
#include <array> #ifdef _WIN32
#include <guiddef.h>
#endif
#include "AL/al.h" #include "AL/al.h"
#ifndef GUID_DEFINED #ifndef _WIN32
#define GUID_DEFINED using GUID = struct _GUID { /* NOLINT(*-reserved-identifier) */
typedef struct _GUID {
std::uint32_t Data1; std::uint32_t Data1;
std::uint16_t Data2; std::uint16_t Data2;
std::uint16_t Data3; std::uint16_t Data3;
std::uint8_t Data4[8]; std::array<std::uint8_t,8> Data4;
} GUID; };
#ifndef _SYS_GUID_OPERATOR_EQ_
#define _SYS_GUID_OPERATOR_EQ_
inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept
{ return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; } { return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; }
inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept
{ return !(lhs == rhs); } { return !(lhs == rhs); }
#endif // _SYS_GUID_OPERATOR_EQ_ #endif // _WIN32
#endif // GUID_DEFINED
#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; extern const GUID DSPROPSETID_EAX_ReverbProperties;
@ -276,11 +281,15 @@ struct EAXVECTOR {
float x; float x;
float y; float y;
float z; float z;
[[nodiscard]]
auto get_members() const noexcept { return std::forward_as_tuple(x, y, z); }
}; // EAXVECTOR }; // EAXVECTOR
[[nodiscard]]
inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept 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 inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept
{ return !(lhs == rhs); } { return !(lhs == rhs); }
@ -361,6 +370,7 @@ constexpr auto EAXCONTEXT_MINMACROFXFACTOR = 0.0F;
constexpr auto EAXCONTEXT_MAXMACROFXFACTOR = 1.0F; constexpr auto EAXCONTEXT_MAXMACROFXFACTOR = 1.0F;
constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F; constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F;
constexpr auto EAXCONTEXT_DEFAULTLASTERROR = EAX_OK;
extern const GUID EAXPROPERTYID_EAX40_FXSlot0; extern const GUID EAXPROPERTYID_EAX40_FXSlot0;
extern const GUID EAXPROPERTYID_EAX50_FXSlot0; extern const GUID EAXPROPERTYID_EAX50_FXSlot0;
@ -613,7 +623,7 @@ struct EAX30SOURCEPROPERTIES {
float flOcclusionLFRatio; // occlusion low-frequency level re. main control float flOcclusionLFRatio; // occlusion low-frequency level re. main control
float flOcclusionRoomRatio; // relative occlusion control for room effect float flOcclusionRoomRatio; // relative occlusion control for room effect
float flOcclusionDirectRatio; // relative occlusion control for direct path 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 float flExclusionLFRatio; // exclusion low-frequency level re. main control
long lOutsideVolumeHF; // outside sound cone level at high frequencies long lOutsideVolumeHF; // outside sound cone level at high frequencies
float flDopplerFactor; // like DS3D flDopplerFactor but per source float flDopplerFactor; // like DS3D flDopplerFactor but per source
@ -653,11 +663,11 @@ struct EAXSPEAKERLEVELPROPERTIES {
}; // EAXSPEAKERLEVELPROPERTIES }; // EAXSPEAKERLEVELPROPERTIES
struct EAX40ACTIVEFXSLOTS { struct EAX40ACTIVEFXSLOTS {
GUID guidActiveFXSlots[EAX40_MAX_ACTIVE_FXSLOTS]; std::array<GUID,EAX40_MAX_ACTIVE_FXSLOTS> guidActiveFXSlots;
}; // EAX40ACTIVEFXSLOTS }; // EAX40ACTIVEFXSLOTS
struct EAX50ACTIVEFXSLOTS { struct EAX50ACTIVEFXSLOTS {
GUID guidActiveFXSlots[EAX50_MAX_ACTIVE_FXSLOTS]; std::array<GUID,EAX50_MAX_ACTIVE_FXSLOTS> guidActiveFXSlots;
}; // EAX50ACTIVEFXSLOTS }; // EAX50ACTIVEFXSLOTS
// Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. // Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property.
@ -836,6 +846,11 @@ struct EAXREVERBPROPERTIES {
float flLFReference; // reference low frequency float flLFReference; // reference low frequency
float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect
unsigned long ulFlags; // modifies the behavior of properties 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 }; // EAXREVERBPROPERTIES
@ -965,6 +980,7 @@ enum EAXAGCCOMPRESSOR_PROPERTY : unsigned int {
struct EAXAGCCOMPRESSORPROPERTIES { struct EAXAGCCOMPRESSORPROPERTIES {
unsigned long ulOnOff; // Switch Compressor on or off unsigned long ulOnOff; // Switch Compressor on or off
DECL_EQOP(EAXAGCCOMPRESSORPROPERTIES, ulOnOff)
}; // EAXAGCCOMPRESSORPROPERTIES }; // EAXAGCCOMPRESSORPROPERTIES
@ -991,6 +1007,7 @@ struct EAXAUTOWAHPROPERTIES {
float flReleaseTime; // Release time (seconds) float flReleaseTime; // Release time (seconds)
long lResonance; // Resonance (mB) long lResonance; // Resonance (mB)
long lPeakLevel; // Peak level (mB) long lPeakLevel; // Peak level (mB)
DECL_EQOP(EAXAUTOWAHPROPERTIES, flAttackTime, flReleaseTime, lResonance, lPeakLevel)
}; // EAXAUTOWAHPROPERTIES }; // EAXAUTOWAHPROPERTIES
@ -1038,6 +1055,7 @@ struct EAXCHORUSPROPERTIES {
float flDepth; // Depth (0 to 1) float flDepth; // Depth (0 to 1)
float flFeedback; // Feedback (-1 to 1) float flFeedback; // Feedback (-1 to 1)
float flDelay; // Delay (seconds) float flDelay; // Delay (seconds)
DECL_EQOP(EAXCHORUSPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay)
}; // EAXCHORUSPROPERTIES }; // EAXCHORUSPROPERTIES
@ -1086,6 +1104,7 @@ struct EAXDISTORTIONPROPERTIES {
float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz) 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 flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz)
float flEQBandwidth; // Controls the bandwidth 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 }; // EAXDISTORTIONPROPERTIES
@ -1130,6 +1149,7 @@ struct EAXECHOPROPERTIES {
float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1) 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 flFeedback; // Controls the duration of echo repetition (0 to 1)
float flSpread; // Controls the left-right spread of the echoes float flSpread; // Controls the left-right spread of the echoes
DECL_EQOP(EAXECHOPROPERTIES, flDelay, flLRDelay, flDamping, flFeedback, flSpread)
}; // EAXECHOPROPERTIES }; // EAXECHOPROPERTIES
@ -1184,6 +1204,8 @@ struct EAXEQUALIZERPROPERTIES {
float flMid2Width; // (octaves) float flMid2Width; // (octaves)
long lHighGain; // (mB) long lHighGain; // (mB)
float flHighCutOff; // (Hz) float flHighCutOff; // (Hz)
DECL_EQOP(EAXEQUALIZERPROPERTIES, lLowGain, flLowCutOff, lMid1Gain, flMid1Center, flMid1Width,
lMid2Gain, flMid2Center, flMid2Width, lHighGain, flHighCutOff)
}; // EAXEQUALIZERPROPERTIES }; // EAXEQUALIZERPROPERTIES
@ -1255,6 +1277,7 @@ struct EAXFLANGERPROPERTIES {
float flDepth; // Depth (0 to 1) float flDepth; // Depth (0 to 1)
float flFeedback; // Feedback (0 to 1) float flFeedback; // Feedback (0 to 1)
float flDelay; // Delay (seconds) float flDelay; // Delay (seconds)
DECL_EQOP(EAXFLANGERPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay)
}; // EAXFLANGERPROPERTIES }; // EAXFLANGERPROPERTIES
@ -1305,6 +1328,7 @@ struct EAXFREQUENCYSHIFTERPROPERTIES {
float flFrequency; // (Hz) float flFrequency; // (Hz)
unsigned long ulLeftDirection; // see enum above unsigned long ulLeftDirection; // see enum above
unsigned long ulRightDirection; // see enum above unsigned long ulRightDirection; // see enum above
DECL_EQOP(EAXFREQUENCYSHIFTERPROPERTIES, flFrequency, ulLeftDirection, ulRightDirection)
}; // EAXFREQUENCYSHIFTERPROPERTIES }; // EAXFREQUENCYSHIFTERPROPERTIES
@ -1383,6 +1407,8 @@ struct EAXVOCALMORPHERPROPERTIES {
long lPhonemeBCoarseTuning; // (semitones) long lPhonemeBCoarseTuning; // (semitones)
unsigned long ulWaveform; // Waveform selector - see enum above unsigned long ulWaveform; // Waveform selector - see enum above
float flRate; // (Hz) float flRate; // (Hz)
DECL_EQOP(EAXVOCALMORPHERPROPERTIES, ulPhonemeA, lPhonemeACoarseTuning, ulPhonemeB,
lPhonemeBCoarseTuning, ulWaveform, flRate)
}; // EAXVOCALMORPHERPROPERTIES }; // EAXVOCALMORPHERPROPERTIES
@ -1425,6 +1451,7 @@ enum EAXPITCHSHIFTER_PROPERTY : unsigned int {
struct EAXPITCHSHIFTERPROPERTIES { struct EAXPITCHSHIFTERPROPERTIES {
long lCoarseTune; // Amount of pitch shift (semitones) long lCoarseTune; // Amount of pitch shift (semitones)
long lFineTune; // Amount of pitch shift (cents) long lFineTune; // Amount of pitch shift (cents)
DECL_EQOP(EAXPITCHSHIFTERPROPERTIES, lCoarseTune, lFineTune)
}; // EAXPITCHSHIFTERPROPERTIES }; // EAXPITCHSHIFTERPROPERTIES
@ -1460,6 +1487,7 @@ struct EAXRINGMODULATORPROPERTIES {
float flFrequency; // Frequency of modulation (Hz) float flFrequency; // Frequency of modulation (Hz)
float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz) float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz)
unsigned long ulWaveform; // Waveform selector - see enum above unsigned long ulWaveform; // Waveform selector - see enum above
DECL_EQOP(EAXRINGMODULATORPROPERTIES, flFrequency, flHighPassCutOff, ulWaveform)
}; // EAXRINGMODULATORPROPERTIES }; // EAXRINGMODULATORPROPERTIES
@ -1490,4 +1518,5 @@ using LPEAXGET = ALenum(AL_APIENTRY*)(
ALvoid* property_buffer, ALvoid* property_buffer,
ALuint property_size); ALuint property_size);
#undef DECL_EQOP
#endif // !EAX_API_INCLUDED #endif // !EAX_API_INCLUDED

View file

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

View file

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

View file

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

View file

@ -6,54 +6,27 @@
#include <string> #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)} : std::runtime_error{make_message(context, message)}
{ {
} }
EaxException::~EaxException() = default; 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{}; 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 += "[";
what.append(context, context_size); what += context;
what.append(right_prefix, right_prefix_size); what += "] ";
}
if (has_message)
{
what.append(message, message_size);
} }
what += message;
return what; return what;
} }

View file

@ -1,18 +1,23 @@
#ifndef EAX_EXCEPTION_INCLUDED #ifndef EAX_EXCEPTION_INCLUDED
#define EAX_EXCEPTION_INCLUDED #define EAX_EXCEPTION_INCLUDED
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <string_view>
class EaxException : public std::runtime_error { 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: 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() 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 <cstddef>
#include <optional>
#include "aloptional.h"
#include "api.h" #include "api.h"
using EaxFxSlotIndexValue = std::size_t; using EaxFxSlotIndexValue = std::size_t;
class EaxFxSlotIndex : public al::optional<EaxFxSlotIndexValue> class EaxFxSlotIndex : public std::optional<EaxFxSlotIndexValue> {
{
public: public:
using al::optional<EaxFxSlotIndexValue>::optional; using std::optional<EaxFxSlotIndexValue>::optional;
EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; } EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; }
EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; } EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; }

View file

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

View file

@ -5,10 +5,11 @@
#include <cassert> #include <cassert>
#include <exception> #include <exception>
#include "alstring.h"
#include "core/logging.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(); const auto exception_ptr = std::current_exception();
assert(exception_ptr); assert(exception_ptr);
@ -17,10 +18,9 @@ void eax_log_exception(const char *message) noexcept
std::rethrow_exception(exception_ptr); std::rethrow_exception(exception_ptr);
} }
catch(const std::exception& ex) { catch(const std::exception& ex) {
const auto ex_message = ex.what(); ERR("%.*s %s\n", al::sizei(message), message.data(), ex.what());
ERR("%s %s\n", message ? message : "", ex_message);
} }
catch(...) { 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 <algorithm>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <string_view>
#include <type_traits> #include <type_traits>
#include "opthelpers.h"
using EaxDirtyFlags = unsigned int; using EaxDirtyFlags = unsigned int;
struct EaxAlLowPassParam { struct EaxAlLowPassParam {
@ -13,16 +16,13 @@ struct EaxAlLowPassParam {
float gain_hf; 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> template<typename TException, typename TValue>
void eax_validate_range( void eax_validate_range(std::string_view value_name, const TValue& value, const TValue& min_value,
const char* value_name,
const TValue& value,
const TValue& min_value,
const TValue& max_value) const TValue& max_value)
{ {
if (value >= min_value && value <= max_value) if(value >= min_value && value <= max_value) LIKELY
return; return;
const auto message = 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_HARDWARE_NAME = "AL_STORAGE_HARDWARE";
constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE"; constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE";
ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint *buffers, ALint value) noexcept;
ALboolean AL_APIENTRY EAXSetBufferMode( ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint *pReserved) noexcept;
ALsizei n,
const ALuint* buffers,
ALint value);
ALenum AL_APIENTRY EAXGetBufferMode(
ALuint buffer,
ALint* pReserved);
#endif // !EAX_X_RAM_INCLUDED #endif // !EAX_X_RAM_INCLUDED

View file

@ -28,9 +28,13 @@
#include <iterator> #include <iterator>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <new>
#include <numeric> #include <numeric>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility> #include <utility>
#include <variant>
#include <vector>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
@ -38,26 +42,23 @@
#include "AL/efx-presets.h" #include "AL/efx-presets.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "al/effects/effects.h"
#include "albit.h" #include "albit.h"
#include "alc/context.h" #include "alc/context.h"
#include "alc/device.h" #include "alc/device.h"
#include "alc/effects/base.h"
#include "alc/inprogext.h" #include "alc/inprogext.h"
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alspan.h"
#include "alstring.h" #include "alstring.h"
#include "core/except.h"
#include "core/logging.h" #include "core/logging.h"
#include "direct_defs.h"
#include "error.h"
#include "intrusive_ptr.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "eax/exception.h" const std::array<EffectList,16> gEffectList{{
#endif // ALSOFT_EAX
const EffectList gEffectList[16]{
{ "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB }, { "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB },
{ "reverb", REVERB_EFFECT, AL_EFFECT_REVERB }, { "reverb", REVERB_EFFECT, AL_EFFECT_REVERB },
{ "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH }, { "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH },
@ -73,95 +74,74 @@ const EffectList gEffectList[16]{
{ "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER }, { "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER },
{ "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT }, { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT },
{ "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE }, { "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 { namespace {
struct EffectPropsItem { using SubListAllocator = al::allocator<std::array<ALeffect,64>>;
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 },
};
constexpr auto GetDefaultProps(ALenum type) noexcept -> const EffectProps&
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)
{ {
auto iter = std::find_if(std::begin(EffectPropsList), std::end(EffectPropsList), switch(type)
[type](const EffectPropsItem &item) noexcept -> bool {
{ return item.Type == type; }); case AL_EFFECT_NULL: return NullEffectProps;
return (iter != std::end(EffectPropsList)) ? al::to_address(iter) : nullptr; 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)}; switch(type)
if(item)
{ {
effect->Props = item->DefaultProps; case AL_EFFECT_NULL: effect->PropsVariant.emplace<NullEffectHandler>(); break;
effect->vtab = &item->Vtable; case AL_EFFECT_EAXREVERB: effect->PropsVariant.emplace<ReverbEffectHandler>(); break;
} case AL_EFFECT_REVERB: effect->PropsVariant.emplace<StdReverbEffectHandler>(); break;
else case AL_EFFECT_AUTOWAH: effect->PropsVariant.emplace<AutowahEffectHandler>(); break;
{ case AL_EFFECT_CHORUS: effect->PropsVariant.emplace<ChorusEffectHandler>(); break;
effect->Props = EffectProps{}; case AL_EFFECT_COMPRESSOR: effect->PropsVariant.emplace<CompressorEffectHandler>(); break;
effect->vtab = &NullEffectVtable; 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; effect->type = type;
} }
bool EnsureEffects(ALCdevice *device, size_t needed) auto EnsureEffects(ALCdevice *device, size_t needed) noexcept -> bool
{ try {
size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), size_t{0}, size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz,
[](size_t cur, const EffectSubList &sublist) noexcept -> size_t [](size_t cur, const EffectSubList &sublist) noexcept -> size_t
{ return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })}; { 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 if(device->EffectList.size() >= 1<<25) UNLIKELY
return false; return false;
device->EffectList.emplace_back(); EffectSubList sublist{};
auto sublist = device->EffectList.end() - 1; sublist.FreeMask = ~0_u64;
sublist->FreeMask = ~0_u64; sublist.Effects = SubListAllocator{}.allocate(1);
sublist->Effects = static_cast<ALeffect*>(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64)); device->EffectList.emplace_back(std::move(sublist));
if(!sublist->Effects) UNLIKELY count += std::tuple_size_v<SubListAllocator::value_type>;
{
device->EffectList.pop_back();
return false;
}
count += 64;
} }
return true; 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(), auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(),
[](const EffectSubList &entry) noexcept -> bool [](const EffectSubList &entry) noexcept -> bool
@ -193,7 +171,7 @@ ALeffect *AllocEffect(ALCdevice *device)
auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask)); auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
ASSUME(slidx < 64); 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); InitEffectParams(effect, AL_EFFECT_NULL);
/* Add 1 to avoid effect ID 0. */ /* Add 1 to avoid effect ID 0. */
@ -206,16 +184,18 @@ ALeffect *AllocEffect(ALCdevice *device)
void FreeEffect(ALCdevice *device, ALeffect *effect) void FreeEffect(ALCdevice *device, ALeffect *effect)
{ {
device->mEffectNames.erase(effect->id);
const ALuint id{effect->id - 1}; const ALuint id{effect->id - 1};
const size_t lidx{id >> 6}; const size_t lidx{id >> 6};
const ALuint slidx{id & 0x3f}; const ALuint slidx{id & 0x3f};
al::destroy_at(effect); std::destroy_at(effect);
device->EffectList[lidx].FreeMask |= 1_u64 << slidx; 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 size_t lidx{(id-1) >> 6};
const ALuint slidx{(id-1) & 0x3f}; const ALuint slidx{(id-1) & 0x3f};
@ -225,320 +205,294 @@ inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
EffectSubList &sublist = device->EffectList[lidx]; EffectSubList &sublist = device->EffectList[lidx];
if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
return nullptr; return nullptr;
return sublist.Effects + slidx; return al::to_address(sublist.Effects->begin() + slidx);
} }
} // namespace } // namespace
AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) AL_API DECL_FUNC2(void, alGenEffects, ALsizei,n, ALuint*,effects)
START_API_FUNC FORCE_ALIGN void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) noexcept
{ try {
ContextRef context{GetContextRef()}; if(n < 0)
if(!context) UNLIKELY return; throw al::context_error{AL_INVALID_VALUE, "Generating %d effects", n};
if(n < 0) UNLIKELY
context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
if(n <= 0) UNLIKELY return; if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> effectlock{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;
}
if(n == 1) LIKELY const al::span eids{effects, static_cast<ALuint>(n)};
{ if(!EnsureEffects(device, eids.size()))
/* Special handling for the easy and normal case. */ throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n,
ALeffect *effect{AllocEffect(device)}; (n == 1) ? "" : "s"};
effects[0] = effect->id;
} std::generate(eids.begin(), eids.end(), [device]{ return AllocEffect(device)->id; });
else }
{ catch(al::context_error& e) {
/* Store the allocated buffer IDs in a separate local list, to avoid context->setError(e.errorCode(), "%s", e.what());
* 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);
}
} }
END_API_FUNC
AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects) AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects)
START_API_FUNC FORCE_ALIGN void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n,
{ const ALuint *effects) noexcept
ContextRef context{GetContextRef()}; try {
if(!context) UNLIKELY return; if(n < 0)
throw al::context_error{AL_INVALID_VALUE, "Deleting %d effects", n};
if(n < 0) UNLIKELY
context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
if(n <= 0) UNLIKELY return; if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()}; 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. */ /* First try to find any effects that are invalid. */
auto validate_effect = [device](const ALuint eid) -> bool auto validate_effect = [device](const ALuint eid) -> bool
{ return !eid || LookupEffect(device, eid) != nullptr; }; { return !eid || LookupEffect(device, eid) != nullptr; };
const ALuint *effects_end = effects + n; const al::span eids{effects, static_cast<ALuint>(n)};
auto inveffect = std::find_if_not(effects, effects_end, validate_effect); auto inveffect = std::find_if_not(eids.begin(), eids.end(), validate_effect);
if(inveffect != effects_end) UNLIKELY if(inveffect != eids.end())
{ throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", *inveffect};
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect);
return;
}
/* All good. Delete non-0 effect IDs. */ /* All good. Delete non-0 effect IDs. */
auto delete_effect = [device](ALuint eid) -> void auto delete_effect = [device](ALuint eid) -> void
{ {
ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr}; if(ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr})
if(effect) FreeEffect(device, effect); 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) AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect)
START_API_FUNC FORCE_ALIGN ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) noexcept
{ {
ContextRef context{GetContextRef()}; ALCdevice *device{context->mALDevice.get()};
if(context) LIKELY std::lock_guard<std::mutex> effectlock{device->EffectLock};
{ if(!effect || LookupEffect(device, effect))
ALCdevice *device{context->mALDevice.get()}; return AL_TRUE;
std::lock_guard<std::mutex> _{device->EffectLock};
if(!effect || LookupEffect(device, effect))
return AL_TRUE;
}
return AL_FALSE; 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()}; 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)}; ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY if(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
else if(param == AL_EFFECT_TYPE)
switch(param)
{ {
bool isOk{value == AL_EFFECT_NULL}; case AL_EFFECT_TYPE:
if(!isOk) if(value != AL_EFFECT_NULL)
{ {
for(const EffectList &effectitem : gEffectList) auto check_effect = [value](const EffectList &item) -> bool
{ { return value == item.val && !DisabledEffects.test(item.type); };
if(value == effectitem.val && !DisabledEffects[effectitem.type]) if(!std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect))
{ throw al::context_error{AL_INVALID_VALUE, "Effect type 0x%04x not supported",
isOk = true; value};
break;
}
}
} }
if(isOk) InitEffectParams(aleffect, value);
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]);
return; return;
} }
ContextRef context{GetContextRef()}; /* Call the appropriate handler */
if(!context) UNLIKELY return; 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()}; 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)}; ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
else try
/* Call the appropriate handler */
std::visit([aleffect,param,value](auto &arg)
{ {
/* Call the appropriate handler */ using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
ALeffect_setParamiv(aleffect, param, values); using PropType = typename Type::prop_type;
} return arg.SetParamf(std::get<PropType>(aleffect->Props), param, value);
catch(effect_exception &e) { }, aleffect->PropsVariant);
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 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()}; 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)}; ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY if(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
else try
/* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg)
{ {
/* Call the appropriate handler */ using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
ALeffect_setParamf(aleffect, param, value); using PropType = typename Type::prop_type;
} return arg.SetParamfv(std::get<PropType>(aleffect->Props), param, values);
catch(effect_exception &e) { }, aleffect->PropsVariant);
context->setError(e.errorCode(), "%s", e.what());
}
} }
END_API_FUNC catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
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());
}
} }
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()}; 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)}; const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY if(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
else if(param == AL_EFFECT_TYPE)
switch(param)
{
case AL_EFFECT_TYPE:
*value = aleffect->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; return;
} }
ContextRef context{GetContextRef()}; /* Call the appropriate handler */
if(!context) UNLIKELY return; 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()}; 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)}; const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY if(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
else try
/* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg)
{ {
/* Call the appropriate handler */ using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
ALeffect_getParamiv(aleffect, param, values); using PropType = typename Type::prop_type;
} return arg.GetParamiv(std::get<PropType>(aleffect->Props), param, values);
catch(effect_exception &e) { }, aleffect->PropsVariant);
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 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()}; 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)}; const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY if(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
else try
/* Call the appropriate handler */
std::visit([aleffect,param,value](auto &arg)
{ {
/* Call the appropriate handler */ using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
ALeffect_getParamf(aleffect, param, value); using PropType = typename Type::prop_type;
} return arg.GetParamf(std::get<PropType>(aleffect->Props), param, value);
catch(effect_exception &e) { }, aleffect->PropsVariant);
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 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()}; 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)}; const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY if(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
else try
/* Call the appropriate handler */
std::visit([aleffect,param,values](auto &arg)
{ {
/* Call the appropriate handler */ using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
ALeffect_getParamfv(aleffect, param, values); using PropType = typename Type::prop_type;
} return arg.GetParamfv(std::get<PropType>(aleffect->Props), param, values);
catch(effect_exception &e) { }, aleffect->PropsVariant);
context->setError(e.errorCode(), "%s", e.what()); }
} catch(al::context_error& e) {
context->setError(e.errorCode(), "%s", e.what());
} }
END_API_FUNC
void InitEffect(ALeffect *effect) void InitEffect(ALeffect *effect)
@ -546,26 +500,43 @@ void InitEffect(ALeffect *effect)
InitEffectParams(effect, AL_EFFECT_NULL); 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() EffectSubList::~EffectSubList()
{ {
if(!Effects)
return;
uint64_t usemask{~FreeMask}; uint64_t usemask{~FreeMask};
while(usemask) while(usemask)
{ {
const int idx{al::countr_zero(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); usemask &= ~(1_u64 << idx);
} }
FreeMask = ~usemask; FreeMask = ~usemask;
al_free(Effects); SubListAllocator{}.deallocate(Effects, 1);
Effects = nullptr; Effects = nullptr;
} }
#define DECL(x) { #x, EFX_REVERB_PRESET_##x } struct EffectPreset {
static const struct { const char name[32]; /* NOLINT(*-avoid-c-arrays) */
const char name[32];
EFXEAXREVERBPROPERTIES props; EFXEAXREVERBPROPERTIES props;
} reverblist[] = { };
#define DECL(x) EffectPreset{#x, EFX_REVERB_PRESET_##x}
static constexpr std::array reverblist{
DECL(GENERIC), DECL(GENERIC),
DECL(PADDEDCELL), DECL(PADDEDCELL),
DECL(ROOM), DECL(ROOM),
@ -695,61 +666,62 @@ static const struct {
}; };
#undef DECL #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); InitEffectParams(effect, AL_EFFECT_NULL);
TRACE("Loading reverb '%s'\n", "NONE"); TRACE("Loading reverb '%s'\n", "NONE");
return; return;
} }
if(!DisabledEffects[EAXREVERB_EFFECT]) if(!DisabledEffects.test(EAXREVERB_EFFECT))
InitEffectParams(effect, AL_EFFECT_EAXREVERB); InitEffectParams(effect, AL_EFFECT_EAXREVERB);
else if(!DisabledEffects[REVERB_EFFECT]) else if(!DisabledEffects.test(REVERB_EFFECT))
InitEffectParams(effect, AL_EFFECT_REVERB); InitEffectParams(effect, AL_EFFECT_REVERB);
else else
InitEffectParams(effect, AL_EFFECT_NULL); InitEffectParams(effect, AL_EFFECT_NULL);
for(const auto &reverbitem : reverblist) for(const auto &reverbitem : reverblist)
{ {
const EFXEAXREVERBPROPERTIES *props; if(al::case_compare(name, std::data(reverbitem.name)) != 0)
if(al::strcasecmp(name, reverbitem.name) != 0)
continue; continue;
TRACE("Loading reverb '%s'\n", reverbitem.name); TRACE("Loading reverb '%s'\n", std::data(reverbitem.name));
props = &reverbitem.props; const auto &props = reverbitem.props;
effect->Props.Reverb.Density = props->flDensity; auto &dst = std::get<ReverbProps>(effect->Props);
effect->Props.Reverb.Diffusion = props->flDiffusion; dst.Density = props.flDensity;
effect->Props.Reverb.Gain = props->flGain; dst.Diffusion = props.flDiffusion;
effect->Props.Reverb.GainHF = props->flGainHF; dst.Gain = props.flGain;
effect->Props.Reverb.GainLF = props->flGainLF; dst.GainHF = props.flGainHF;
effect->Props.Reverb.DecayTime = props->flDecayTime; dst.GainLF = props.flGainLF;
effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio; dst.DecayTime = props.flDecayTime;
effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio; dst.DecayHFRatio = props.flDecayHFRatio;
effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain; dst.DecayLFRatio = props.flDecayLFRatio;
effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay; dst.ReflectionsGain = props.flReflectionsGain;
effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0]; dst.ReflectionsDelay = props.flReflectionsDelay;
effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1]; dst.ReflectionsPan[0] = props.flReflectionsPan[0];
effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2]; dst.ReflectionsPan[1] = props.flReflectionsPan[1];
effect->Props.Reverb.LateReverbGain = props->flLateReverbGain; dst.ReflectionsPan[2] = props.flReflectionsPan[2];
effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay; dst.LateReverbGain = props.flLateReverbGain;
effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0]; dst.LateReverbDelay = props.flLateReverbDelay;
effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1]; dst.LateReverbPan[0] = props.flLateReverbPan[0];
effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2]; dst.LateReverbPan[1] = props.flLateReverbPan[1];
effect->Props.Reverb.EchoTime = props->flEchoTime; dst.LateReverbPan[2] = props.flLateReverbPan[2];
effect->Props.Reverb.EchoDepth = props->flEchoDepth; dst.EchoTime = props.flEchoTime;
effect->Props.Reverb.ModulationTime = props->flModulationTime; dst.EchoDepth = props.flEchoDepth;
effect->Props.Reverb.ModulationDepth = props->flModulationDepth; dst.ModulationTime = props.flModulationTime;
effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF; dst.ModulationDepth = props.flModulationDepth;
effect->Props.Reverb.HFReference = props->flHFReference; dst.AirAbsorptionGainHF = props.flAirAbsorptionGainHF;
effect->Props.Reverb.LFReference = props->flLFReference; dst.HFReference = props.flHFReference;
effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor; dst.LFReference = props.flLFReference;
effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE; dst.RoomRolloffFactor = props.flRoomRolloffFactor;
dst.DecayHFLimit = props.iDecayHFLimit ? AL_TRUE : AL_FALSE;
return; 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 bool IsValidEffectType(ALenum type) noexcept
@ -757,10 +729,7 @@ bool IsValidEffectType(ALenum type) noexcept
if(type == AL_EFFECT_NULL) if(type == AL_EFFECT_NULL)
return true; return true;
for(const auto &effect_item : gEffectList) auto check_effect = [type](const EffectList &item) noexcept -> bool
{ { return type == item.val && !DisabledEffects.test(item.type); };
if(type == effect_item.val && !DisabledEffects[effect_item.type]) return std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect);
return true;
}
return false;
} }

View file

@ -1,11 +1,20 @@
#ifndef AL_EFFECT_H #ifndef AL_EFFECT_H
#define AL_EFFECT_H #define AL_EFFECT_H
#include <array>
#include <bitset>
#include <cstdint>
#include <string_view>
#include <utility>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "al/effects/effects.h" #include "almalloc.h"
#include "alc/effects/base.h" #include "alnumeric.h"
#include "core/effects/base.h"
#include "effects/effects.h"
enum { enum {
@ -27,36 +36,55 @@ enum {
MAX_EFFECTS MAX_EFFECTS
}; };
extern bool DisabledEffects[MAX_EFFECTS]; inline std::bitset<MAX_EFFECTS> DisabledEffects;
extern float ReverbBoost;
struct EffectList { struct EffectList {
const char name[16]; const char name[16]; /* NOLINT(*-avoid-c-arrays) */
int type; ALuint type;
ALenum val; 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 { struct ALeffect {
// Effect type (AL_EFFECT_NULL, ...) // Effect type (AL_EFFECT_NULL, ...)
ALenum type{AL_EFFECT_NULL}; ALenum type{AL_EFFECT_NULL};
EffectHandlerVariant PropsVariant;
EffectProps Props{}; EffectProps Props{};
const EffectVtable *vtab{nullptr};
/* Self ID */ /* Self ID */
ALuint id{0u}; ALuint id{0u};
DISABLE_ALLOC() static void SetName(ALCcontext *context, ALuint id, std::string_view name);
DISABLE_ALLOC
}; };
void InitEffect(ALeffect *effect); 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; 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 #endif

View file

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

View file

@ -1,19 +1,17 @@
#include "config.h" #include "config.h"
#include <optional>
#include <stdexcept> #include <stdexcept>
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "core/logging.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #ifdef ALSOFT_EAX
#include <cassert> #include <cassert>
#include "alnumeric.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
#endif // ALSOFT_EAX #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_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"); 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) switch(type)
{ {
case AL_CHORUS_WAVEFORM_SINUSOID: return ChorusWaveform::Sinusoid; case AL_CHORUS_WAVEFORM_SINUSOID: return ChorusWaveform::Sinusoid;
case AL_CHORUS_WAVEFORM_TRIANGLE: return ChorusWaveform::Triangle; 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) 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))}; 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) switch(param)
{ {
case AL_CHORUS_WAVEFORM: case AL_CHORUS_WAVEFORM:
if(auto formopt = WaveformFromEnum(val)) if(auto formopt = WaveformFromEnum(val))
props->Chorus.Waveform = *formopt; props.Waveform = *formopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val}; throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val};
break; break;
@ -60,115 +86,89 @@ void Chorus_setParami(EffectProps *props, ALenum param, int val)
case AL_CHORUS_PHASE: case AL_CHORUS_PHASE:
if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE))
throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val}; throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val};
props->Chorus.Phase = val; props.Phase = val;
break; break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param};
} }
} }
void Chorus_setParamiv(EffectProps *props, ALenum param, const int *vals) void ChorusEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals)
{ Chorus_setParami(props, param, vals[0]); } { SetParami(props, param, *vals); }
void Chorus_setParamf(EffectProps *props, ALenum param, float val) void ChorusEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_CHORUS_RATE: case AL_CHORUS_RATE:
if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE))
throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val}; throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val};
props->Chorus.Rate = val; props.Rate = val;
break; break;
case AL_CHORUS_DEPTH: case AL_CHORUS_DEPTH:
if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH))
throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val}; throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val};
props->Chorus.Depth = val; props.Depth = val;
break; break;
case AL_CHORUS_FEEDBACK: case AL_CHORUS_FEEDBACK:
if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK))
throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val}; throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val};
props->Chorus.Feedback = val; props.Feedback = val;
break; break;
case AL_CHORUS_DELAY: case AL_CHORUS_DELAY:
if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY))
throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val}; throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val};
props->Chorus.Delay = val; props.Delay = val;
break; break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param};
} }
} }
void Chorus_setParamfv(EffectProps *props, ALenum param, const float *vals) void ChorusEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals)
{ Chorus_setParamf(props, param, vals[0]); } { 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) switch(param)
{ {
case AL_CHORUS_WAVEFORM: case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break;
*val = EnumFromWaveform(props->Chorus.Waveform); case AL_CHORUS_PHASE: *val = props.Phase; break;
break;
case AL_CHORUS_PHASE:
*val = props->Chorus.Phase;
break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param};
} }
} }
void Chorus_getParamiv(const EffectProps *props, ALenum param, int *vals) void ChorusEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals)
{ Chorus_getParami(props, param, vals); } { GetParami(props, param, vals); }
void Chorus_getParamf(const EffectProps *props, ALenum param, float *val) void ChorusEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_CHORUS_RATE: case AL_CHORUS_RATE: *val = props.Rate; break;
*val = props->Chorus.Rate; case AL_CHORUS_DEPTH: *val = props.Depth; break;
break; case AL_CHORUS_FEEDBACK: *val = props.Feedback; break;
case AL_CHORUS_DELAY: *val = props.Delay; 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;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param};
} }
} }
void Chorus_getParamfv(const EffectProps *props, ALenum param, float *vals) void ChorusEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals)
{ Chorus_getParamf(props, param, vals); } { 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 Flanger_setParami(EffectProps *props, ALenum param, int val) const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
void FlangerEffectHandler::SetParami(ChorusProps &props, ALenum param, int val)
{ {
switch(param) switch(param)
{ {
case AL_FLANGER_WAVEFORM: case AL_FLANGER_WAVEFORM:
if(auto formopt = WaveformFromEnum(val)) if(auto formopt = WaveformFromEnum(val))
props->Chorus.Waveform = *formopt; props.Waveform = *formopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val}; throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val};
break; break;
@ -176,127 +176,87 @@ void Flanger_setParami(EffectProps *props, ALenum param, int val)
case AL_FLANGER_PHASE: case AL_FLANGER_PHASE:
if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE))
throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val}; throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val};
props->Chorus.Phase = val; props.Phase = val;
break; break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param};
} }
} }
void Flanger_setParamiv(EffectProps *props, ALenum param, const int *vals) void FlangerEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals)
{ Flanger_setParami(props, param, vals[0]); } { SetParami(props, param, *vals); }
void Flanger_setParamf(EffectProps *props, ALenum param, float val) void FlangerEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_FLANGER_RATE: case AL_FLANGER_RATE:
if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE))
throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val}; throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val};
props->Chorus.Rate = val; props.Rate = val;
break; break;
case AL_FLANGER_DEPTH: case AL_FLANGER_DEPTH:
if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH))
throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val}; throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val};
props->Chorus.Depth = val; props.Depth = val;
break; break;
case AL_FLANGER_FEEDBACK: case AL_FLANGER_FEEDBACK:
if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK))
throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val}; throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val};
props->Chorus.Feedback = val; props.Feedback = val;
break; break;
case AL_FLANGER_DELAY: case AL_FLANGER_DELAY:
if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY))
throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val}; throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val};
props->Chorus.Delay = val; props.Delay = val;
break; break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param};
} }
} }
void Flanger_setParamfv(EffectProps *props, ALenum param, const float *vals) void FlangerEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals)
{ Flanger_setParamf(props, param, vals[0]); } { 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) switch(param)
{ {
case AL_FLANGER_WAVEFORM: case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break;
*val = EnumFromWaveform(props->Chorus.Waveform); case AL_FLANGER_PHASE: *val = props.Phase; break;
break;
case AL_FLANGER_PHASE:
*val = props->Chorus.Phase;
break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param};
} }
} }
void Flanger_getParamiv(const EffectProps *props, ALenum param, int *vals) void FlangerEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals)
{ Flanger_getParami(props, param, vals); } { GetParami(props, param, vals); }
void Flanger_getParamf(const EffectProps *props, ALenum param, float *val) void FlangerEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_FLANGER_RATE: case AL_FLANGER_RATE: *val = props.Rate; break;
*val = props->Chorus.Rate; case AL_FLANGER_DEPTH: *val = props.Depth; break;
break; case AL_FLANGER_FEEDBACK: *val = props.Feedback; break;
case AL_FLANGER_DELAY: *val = props.Delay; 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;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param};
} }
} }
void Flanger_getParamfv(const EffectProps *props, ALenum param, float *vals) void FlangerEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals)
{ Flanger_getParamf(props, param, vals); } { 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()};
#ifdef ALSOFT_EAX #ifdef ALSOFT_EAX
namespace { namespace {
struct EaxChorusTraits { struct EaxChorusTraits {
using Props = EAXCHORUSPROPERTIES; using EaxProps = EAXCHORUSPROPERTIES;
using Committer = EaxChorusCommitter; 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 efx_effect() { return AL_EFFECT_CHORUS; }
static constexpr auto eax_none_param_id() { return EAXCHORUS_NONE; } static constexpr auto eax_none_param_id() { return EAXCHORUS_NONE; }
@ -359,11 +319,9 @@ struct EaxChorusTraits {
}; // EaxChorusTraits }; // EaxChorusTraits
struct EaxFlangerTraits { struct EaxFlangerTraits {
using Props = EAXFLANGERPROPERTIES; using EaxProps = EAXFLANGERPROPERTIES;
using Committer = EaxFlangerCommitter; 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 efx_effect() { return AL_EFFECT_FLANGER; }
static constexpr auto eax_none_param_id() { return EAXFLANGER_NONE; } static constexpr auto eax_none_param_id() { return EAXFLANGER_NONE; }
@ -428,11 +386,10 @@ struct EaxFlangerTraits {
template<typename TTraits> template<typename TTraits>
struct ChorusFlangerEffect { struct ChorusFlangerEffect {
using Traits = TTraits; using Traits = TTraits;
using EaxProps = typename Traits::EaxProps;
using Committer = typename Traits::Committer; using Committer = typename Traits::Committer;
using Exception = typename Committer::Exception; using Exception = typename Committer::Exception;
static constexpr auto Field = Traits::Field;
struct WaveformValidator { struct WaveformValidator {
void operator()(unsigned long ulWaveform) const void operator()(unsigned long ulWaveform) const
{ {
@ -500,7 +457,7 @@ struct ChorusFlangerEffect {
}; // DelayValidator }; // DelayValidator
struct AllValidator { struct AllValidator {
void operator()(const typename Traits::Props& all) const void operator()(const EaxProps& all) const
{ {
WaveformValidator{}(all.ulWaveform); WaveformValidator{}(all.ulWaveform);
PhaseValidator{}(all.lPhase); PhaseValidator{}(all.lPhase);
@ -514,8 +471,7 @@ struct ChorusFlangerEffect {
public: public:
static void SetDefaults(EaxEffectProps &props) static void SetDefaults(EaxEffectProps &props)
{ {
auto&& all = props.*Field; auto&& all = props.emplace<EaxProps>();
props.mType = Traits::eax_effect_type();
all.ulWaveform = Traits::eax_default_waveform(); all.ulWaveform = Traits::eax_default_waveform();
all.lPhase = Traits::eax_default_phase(); all.lPhase = Traits::eax_default_phase();
all.flRate = Traits::eax_default_rate(); 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()) switch(call.get_property_id())
{ {
case Traits::eax_none_param_id(): case Traits::eax_none_param_id():
break; break;
case Traits::eax_allparameters_param_id(): case Traits::eax_allparameters_param_id():
call.template set_value<Exception>(all); call.template set_value<Exception>(all);
break; break;
case Traits::eax_waveform_param_id(): case Traits::eax_waveform_param_id():
call.template set_value<Exception>(all.ulWaveform); call.template set_value<Exception>(all.ulWaveform);
break; break;
case Traits::eax_phase_param_id(): case Traits::eax_phase_param_id():
call.template set_value<Exception>(all.lPhase); call.template set_value<Exception>(all.lPhase);
break; break;
case Traits::eax_rate_param_id(): case Traits::eax_rate_param_id():
call.template set_value<Exception>(all.flRate); call.template set_value<Exception>(all.flRate);
break; break;
case Traits::eax_depth_param_id(): case Traits::eax_depth_param_id():
call.template set_value<Exception>(all.flDepth); call.template set_value<Exception>(all.flDepth);
break; break;
case Traits::eax_feedback_param_id(): case Traits::eax_feedback_param_id():
call.template set_value<Exception>(all.flFeedback); call.template set_value<Exception>(all.flFeedback);
break; break;
case Traits::eax_delay_param_id(): case Traits::eax_delay_param_id():
call.template set_value<Exception>(all.flDelay); call.template set_value<Exception>(all.flDelay);
break; break;
default: default:
Committer::fail_unknown_property_id(); 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()) switch(call.get_property_id())
{ {
case Traits::eax_none_param_id(): case Traits::eax_none_param_id():
break; break;
case Traits::eax_allparameters_param_id(): case Traits::eax_allparameters_param_id():
Committer::template defer<AllValidator>(call, all); Committer::template defer<AllValidator>(call, all);
break; break;
case Traits::eax_waveform_param_id(): case Traits::eax_waveform_param_id():
Committer::template defer<WaveformValidator>(call, all.ulWaveform); Committer::template defer<WaveformValidator>(call, all.ulWaveform);
break; break;
case Traits::eax_phase_param_id(): case Traits::eax_phase_param_id():
Committer::template defer<PhaseValidator>(call, all.lPhase); Committer::template defer<PhaseValidator>(call, all.lPhase);
break; break;
case Traits::eax_rate_param_id(): case Traits::eax_rate_param_id():
Committer::template defer<RateValidator>(call, all.flRate); Committer::template defer<RateValidator>(call, all.flRate);
break; break;
case Traits::eax_depth_param_id(): case Traits::eax_depth_param_id():
Committer::template defer<DepthValidator>(call, all.flDepth); Committer::template defer<DepthValidator>(call, all.flDepth);
break; break;
case Traits::eax_feedback_param_id(): case Traits::eax_feedback_param_id():
Committer::template defer<FeedbackValidator>(call, all.flFeedback); Committer::template defer<FeedbackValidator>(call, all.flFeedback);
break; break;
case Traits::eax_delay_param_id(): case Traits::eax_delay_param_id():
Committer::template defer<DelayValidator>(call, all.flDelay); Committer::template defer<DelayValidator>(call, all.flDelay);
break; break;
default: default:
Committer::fail_unknown_property_id(); 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) if(auto *cur = std::get_if<EaxProps>(&props_); cur && *cur == props)
{ return false;
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;
}
props_ = props; props_ = props;
auto&& dst = props.*Field;
al_props_.Chorus.Waveform = Traits::eax_waveform(dst.ulWaveform); al_props_.Waveform = Traits::eax_waveform(props.ulWaveform);
al_props_.Chorus.Phase = static_cast<int>(dst.lPhase); al_props_.Phase = static_cast<int>(props.lPhase);
al_props_.Chorus.Rate = dst.flRate; al_props_.Rate = props.flRate;
al_props_.Chorus.Depth = dst.flDepth; al_props_.Depth = props.flDepth;
al_props_.Chorus.Feedback = dst.flFeedback; al_props_.Feedback = props.flFeedback;
al_props_.Chorus.Delay = dst.flDelay; al_props_.Delay = props.flDelay;
return true; return true;
} }
@ -652,29 +582,25 @@ template<>
throw Exception{message}; throw Exception{message};
} }
template<> bool EaxChorusCommitter::commit(const EAXCHORUSPROPERTIES &props)
bool ChorusCommitter::commit(const EaxEffectProps &props)
{ {
using Committer = ChorusFlangerEffect<EaxChorusTraits>; using Committer = ChorusFlangerEffect<EaxChorusTraits>;
return Committer::Commit(props, mEaxProps, mAlProps); return Committer::Commit(props, mEaxProps, mAlProps.emplace<ChorusProps>());
} }
template<> void EaxChorusCommitter::SetDefaults(EaxEffectProps &props)
void ChorusCommitter::SetDefaults(EaxEffectProps &props)
{ {
using Committer = ChorusFlangerEffect<EaxChorusTraits>; using Committer = ChorusFlangerEffect<EaxChorusTraits>;
Committer::SetDefaults(props); Committer::SetDefaults(props);
} }
template<> void EaxChorusCommitter::Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props)
void ChorusCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{ {
using Committer = ChorusFlangerEffect<EaxChorusTraits>; using Committer = ChorusFlangerEffect<EaxChorusTraits>;
Committer::Get(call, props); Committer::Get(call, props);
} }
template<> void EaxChorusCommitter::Set(const EaxCall &call, EAXCHORUSPROPERTIES &props)
void ChorusCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{ {
using Committer = ChorusFlangerEffect<EaxChorusTraits>; using Committer = ChorusFlangerEffect<EaxChorusTraits>;
Committer::Set(call, props); Committer::Set(call, props);
@ -693,29 +619,25 @@ template<>
throw Exception{message}; throw Exception{message};
} }
template<> bool EaxFlangerCommitter::commit(const EAXFLANGERPROPERTIES &props)
bool FlangerCommitter::commit(const EaxEffectProps &props)
{ {
using Committer = ChorusFlangerEffect<EaxFlangerTraits>; using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
return Committer::Commit(props, mEaxProps, mAlProps); return Committer::Commit(props, mEaxProps, mAlProps.emplace<ChorusProps>());
} }
template<> void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props)
void FlangerCommitter::SetDefaults(EaxEffectProps &props)
{ {
using Committer = ChorusFlangerEffect<EaxFlangerTraits>; using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
Committer::SetDefaults(props); Committer::SetDefaults(props);
} }
template<> void EaxFlangerCommitter::Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props)
void FlangerCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{ {
using Committer = ChorusFlangerEffect<EaxFlangerTraits>; using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
Committer::Get(call, props); Committer::Get(call, props);
} }
template<> void EaxFlangerCommitter::Set(const EaxCall &call, EAXFLANGERPROPERTIES &props)
void FlangerCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{ {
using Committer = ChorusFlangerEffect<EaxFlangerTraits>; using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
Committer::Set(call, props); Committer::Set(call, props);

View file

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

View file

@ -1,93 +1,117 @@
#include "config.h" #include "config.h"
#include "AL/al.h" #include <algorithm>
#include "alc/inprogext.h" #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" #include "effects.h"
namespace { namespace {
void Convolution_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) constexpr EffectProps genDefaultProps() noexcept
{ {
switch(param) ConvolutionProps props{};
{ props.OrientAt = {0.0f, 0.0f, -1.0f};
default: props.OrientUp = {0.0f, 1.0f, 0.0f};
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{};
return props; return props;
} }
} // namespace } // namespace
DEFINE_ALEFFECT_VTABLE(Convolution);
const EffectProps ConvolutionEffectProps{genDefaultProps()}; 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 { 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}; } { 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", throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
param}; param};
} }
void Dedicated_setParamf(EffectProps *props, ALenum param, float val) void DedicatedDialogEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_DEDICATED_GAIN: case AL_DEDICATED_GAIN:
if(!(val >= 0.0f && std::isfinite(val))) if(!(val >= 0.0f && std::isfinite(val)))
throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"};
props->Dedicated.Gain = val; props.Gain = val;
break; break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
} }
} }
void Dedicated_setParamfv(EffectProps *props, ALenum param, const float *vals) void DedicatedDialogEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals)
{ Dedicated_setParamf(props, param, vals[0]); } { 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}; } { 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", throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
param}; 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) switch(param)
{ {
case AL_DEDICATED_GAIN: 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; break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
} }
} }
void Dedicated_getParamfv(const EffectProps *props, ALenum param, float *vals) void DedicatedLfeEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals)
{ Dedicated_getParamf(props, param, 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{}; throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
props.Dedicated.Gain = 1.0f; param};
return props;
} }
void DedicatedLfeEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val)
} // namespace {
switch(param)
DEFINE_ALEFFECT_VTABLE(Dedicated); {
case AL_DEDICATED_GAIN: *val = props.Gain; break;
const EffectProps DedicatedEffectProps{genDefaultProps()}; 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 #ifdef ALSOFT_EAX
#include "alnumeric.h" #include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -16,108 +17,93 @@
namespace { 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}; } { 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", throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
param}; param};
} }
void Distortion_setParamf(EffectProps *props, ALenum param, float val) void DistortionEffectHandler::SetParamf(DistortionProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_DISTORTION_EDGE: case AL_DISTORTION_EDGE:
if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE))
throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"}; throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"};
props->Distortion.Edge = val; props.Edge = val;
break; break;
case AL_DISTORTION_GAIN: case AL_DISTORTION_GAIN:
if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"}; throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"};
props->Distortion.Gain = val; props.Gain = val;
break; break;
case AL_DISTORTION_LOWPASS_CUTOFF: case AL_DISTORTION_LOWPASS_CUTOFF:
if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF))
throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"}; throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"};
props->Distortion.LowpassCutoff = val; props.LowpassCutoff = val;
break; break;
case AL_DISTORTION_EQCENTER: case AL_DISTORTION_EQCENTER:
if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER))
throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"}; throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"};
props->Distortion.EQCenter = val; props.EQCenter = val;
break; break;
case AL_DISTORTION_EQBANDWIDTH: case AL_DISTORTION_EQBANDWIDTH:
if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH))
throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"}; throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"};
props->Distortion.EQBandwidth = val; props.EQBandwidth = val;
break; break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
} }
} }
void Distortion_setParamfv(EffectProps *props, ALenum param, const float *vals) void DistortionEffectHandler::SetParamfv(DistortionProps &props, ALenum param, const float *vals)
{ Distortion_setParamf(props, param, vals[0]); } { 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}; } { 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", throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
param}; param};
} }
void Distortion_getParamf(const EffectProps *props, ALenum param, float *val) void DistortionEffectHandler::GetParamf(const DistortionProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_DISTORTION_EDGE: case AL_DISTORTION_EDGE: *val = props.Edge; break;
*val = props->Distortion.Edge; case AL_DISTORTION_GAIN: *val = props.Gain; break;
break; case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; break;
case AL_DISTORTION_EQCENTER: *val = props.EQCenter; break;
case AL_DISTORTION_GAIN: case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; break;
*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;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
} }
} }
void Distortion_getParamfv(const EffectProps *props, ALenum param, float *vals) void DistortionEffectHandler::GetParamfv(const DistortionProps &props, ALenum param, float *vals)
{ Distortion_getParamf(props, param, 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 #ifdef ALSOFT_EAX
namespace { namespace {
@ -204,66 +190,66 @@ template<>
throw Exception{message}; throw Exception{message};
} }
template<> bool EaxDistortionCommitter::commit(const EAXDISTORTIONPROPERTIES &props)
bool DistortionCommitter::commit(const EaxEffectProps &props)
{ {
if(props.mType == mEaxProps.mType && mEaxProps.mDistortion.flEdge == props.mDistortion.flEdge if(auto *cur = std::get_if<EAXDISTORTIONPROPERTIES>(&mEaxProps); cur && *cur == props)
&& mEaxProps.mDistortion.lGain == props.mDistortion.lGain
&& mEaxProps.mDistortion.flLowPassCutOff == props.mDistortion.flLowPassCutOff
&& mEaxProps.mDistortion.flEQCenter == props.mDistortion.flEQCenter
&& mEaxProps.mDistortion.flEQBandwidth == props.mDistortion.flEQBandwidth)
return false; return false;
mEaxProps = props; mEaxProps = props;
mAlProps = [&]{
mAlProps.Distortion.Edge = props.mDistortion.flEdge; DistortionProps ret{};
mAlProps.Distortion.Gain = level_mb_to_gain(static_cast<float>(props.mDistortion.lGain)); ret.Edge = props.flEdge;
mAlProps.Distortion.LowpassCutoff = props.mDistortion.flLowPassCutOff; ret.Gain = level_mb_to_gain(static_cast<float>(props.lGain));
mAlProps.Distortion.EQCenter = props.mDistortion.flEQCenter; ret.LowpassCutoff = props.flLowPassCutOff;
mAlProps.Distortion.EQBandwidth = props.mDistortion.flEdge; ret.EQCenter = props.flEQCenter;
ret.EQBandwidth = props.flEdge;
return ret;
}();
return true; return true;
} }
template<> void EaxDistortionCommitter::SetDefaults(EaxEffectProps &props)
void DistortionCommitter::SetDefaults(EaxEffectProps &props)
{ {
props.mType = EaxEffectType::Distortion; static constexpr EAXDISTORTIONPROPERTIES defprops{[]
props.mDistortion.flEdge = EAXDISTORTION_DEFAULTEDGE; {
props.mDistortion.lGain = EAXDISTORTION_DEFAULTGAIN; EAXDISTORTIONPROPERTIES ret{};
props.mDistortion.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; ret.flEdge = EAXDISTORTION_DEFAULTEDGE;
props.mDistortion.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; ret.lGain = EAXDISTORTION_DEFAULTGAIN;
props.mDistortion.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; ret.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF;
ret.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER;
ret.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH;
return ret;
}()};
props = defprops;
} }
template<> void EaxDistortionCommitter::Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props)
void DistortionCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{ {
switch(call.get_property_id()) switch(call.get_property_id())
{ {
case EAXDISTORTION_NONE: break; case EAXDISTORTION_NONE: break;
case EAXDISTORTION_ALLPARAMETERS: call.set_value<Exception>(props.mDistortion); break; case EAXDISTORTION_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXDISTORTION_EDGE: call.set_value<Exception>(props.mDistortion.flEdge); break; case EAXDISTORTION_EDGE: call.set_value<Exception>(props.flEdge); break;
case EAXDISTORTION_GAIN: call.set_value<Exception>(props.mDistortion.lGain); break; case EAXDISTORTION_GAIN: call.set_value<Exception>(props.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: call.set_value<Exception>(props.mDistortion.flLowPassCutOff); break; case EAXDISTORTION_LOWPASSCUTOFF: call.set_value<Exception>(props.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: call.set_value<Exception>(props.mDistortion.flEQCenter); break; case EAXDISTORTION_EQCENTER: call.set_value<Exception>(props.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: call.set_value<Exception>(props.mDistortion.flEQBandwidth); break; case EAXDISTORTION_EQBANDWIDTH: call.set_value<Exception>(props.flEQBandwidth); break;
default: fail_unknown_property_id(); default: fail_unknown_property_id();
} }
} }
template<> void EaxDistortionCommitter::Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props)
void DistortionCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{ {
switch(call.get_property_id()) switch(call.get_property_id())
{ {
case EAXDISTORTION_NONE: break; case EAXDISTORTION_NONE: break;
case EAXDISTORTION_ALLPARAMETERS: defer<AllValidator>(call, props.mDistortion); break; case EAXDISTORTION_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXDISTORTION_EDGE: defer<EdgeValidator>(call, props.mDistortion.flEdge); break; case EAXDISTORTION_EDGE: defer<EdgeValidator>(call, props.flEdge); break;
case EAXDISTORTION_GAIN: defer<GainValidator>(call, props.mDistortion.lGain); break; case EAXDISTORTION_GAIN: defer<GainValidator>(call, props.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: defer<LowPassCutOffValidator>(call, props.mDistortion.flLowPassCutOff); break; case EAXDISTORTION_LOWPASSCUTOFF: defer<LowPassCutOffValidator>(call, props.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: defer<EqCenterValidator>(call, props.mDistortion.flEQCenter); break; case EAXDISTORTION_EQCENTER: defer<EqCenterValidator>(call, props.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: defer<EqBandwidthValidator>(call, props.mDistortion.flEQBandwidth); break; case EAXDISTORTION_EQBANDWIDTH: defer<EqBandwidthValidator>(call, props.flEQBandwidth); break;
default: fail_unknown_property_id(); default: fail_unknown_property_id();
} }
} }

View file

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

View file

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

View file

@ -1,52 +1,47 @@
#ifndef AL_EFFECTS_EFFECTS_H #ifndef AL_EFFECTS_EFFECTS_H
#define AL_EFFECTS_EFFECTS_H #define AL_EFFECTS_EFFECTS_H
#include <variant>
#include "AL/al.h" #include "AL/al.h"
#include "core/except.h" #include "al/error.h"
#include "core/effects/base.h"
#ifdef ALSOFT_EAX #define DECL_HANDLER(N, T) \
#include "al/eax/effect.h" struct N { \
#endif // ALSOFT_EAX using prop_type = T; \
\
union EffectProps; 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); \
class effect_exception final : public al::base_exception { static void SetParamfv(prop_type &props, ALenum param, const float *vals);\
ALenum mErrorCode; static void GetParami(const prop_type &props, ALenum param, int *val); \
static void GetParamiv(const prop_type &props, ALenum param, int *vals); \
public: static void GetParamf(const prop_type &props, ALenum param, float *val); \
#ifdef __USE_MINGW_ANSI_STDIO static void GetParamfv(const prop_type &props, ALenum param, float *vals);\
[[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; }
}; };
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 { using effect_exception = al::context_error;
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, \
}
/* Default properties for the given effect types. */ /* Default properties for the given effect types. */
@ -64,25 +59,8 @@ extern const EffectProps FshifterEffectProps;
extern const EffectProps ModulatorEffectProps; extern const EffectProps ModulatorEffectProps;
extern const EffectProps PshifterEffectProps; extern const EffectProps PshifterEffectProps;
extern const EffectProps VmorpherEffectProps; extern const EffectProps VmorpherEffectProps;
extern const EffectProps DedicatedEffectProps; extern const EffectProps DedicatedDialogEffectProps;
extern const EffectProps DedicatedLfeEffectProps;
extern const EffectProps ConvolutionEffectProps; 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 */ #endif /* AL_EFFECTS_EFFECTS_H */

View file

@ -9,6 +9,7 @@
#ifdef ALSOFT_EAX #ifdef ALSOFT_EAX
#include "alnumeric.h" #include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -16,163 +17,133 @@
namespace { 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}; } { 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", throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x",
param}; param};
} }
void Equalizer_setParamf(EffectProps *props, ALenum param, float val) void EqualizerEffectHandler::SetParamf(EqualizerProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_EQUALIZER_LOW_GAIN: case AL_EQUALIZER_LOW_GAIN:
if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band gain out of range"}; throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band gain out of range"};
props->Equalizer.LowGain = val; props.LowGain = val;
break; break;
case AL_EQUALIZER_LOW_CUTOFF: case AL_EQUALIZER_LOW_CUTOFF:
if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF))
throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"}; throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"};
props->Equalizer.LowCutoff = val; props.LowCutoff = val;
break; break;
case AL_EQUALIZER_MID1_GAIN: case AL_EQUALIZER_MID1_GAIN:
if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"}; throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"};
props->Equalizer.Mid1Gain = val; props.Mid1Gain = val;
break; break;
case AL_EQUALIZER_MID1_CENTER: case AL_EQUALIZER_MID1_CENTER:
if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band center out of range"}; throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band center out of range"};
props->Equalizer.Mid1Center = val; props.Mid1Center = val;
break; break;
case AL_EQUALIZER_MID1_WIDTH: case AL_EQUALIZER_MID1_WIDTH:
if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band width out of range"}; throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band width out of range"};
props->Equalizer.Mid1Width = val; props.Mid1Width = val;
break; break;
case AL_EQUALIZER_MID2_GAIN: case AL_EQUALIZER_MID2_GAIN:
if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"}; throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"};
props->Equalizer.Mid2Gain = val; props.Mid2Gain = val;
break; break;
case AL_EQUALIZER_MID2_CENTER: case AL_EQUALIZER_MID2_CENTER:
if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band center out of range"}; throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band center out of range"};
props->Equalizer.Mid2Center = val; props.Mid2Center = val;
break; break;
case AL_EQUALIZER_MID2_WIDTH: case AL_EQUALIZER_MID2_WIDTH:
if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH))
throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band width out of range"}; throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band width out of range"};
props->Equalizer.Mid2Width = val; props.Mid2Width = val;
break; break;
case AL_EQUALIZER_HIGH_GAIN: case AL_EQUALIZER_HIGH_GAIN:
if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN))
throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band gain out of range"}; throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band gain out of range"};
props->Equalizer.HighGain = val; props.HighGain = val;
break; break;
case AL_EQUALIZER_HIGH_CUTOFF: case AL_EQUALIZER_HIGH_CUTOFF:
if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF))
throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"}; throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"};
props->Equalizer.HighCutoff = val; props.HighCutoff = val;
break; break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param};
} }
} }
void Equalizer_setParamfv(EffectProps *props, ALenum param, const float *vals) void EqualizerEffectHandler::SetParamfv(EqualizerProps &props, ALenum param, const float *vals)
{ Equalizer_setParamf(props, param, vals[0]); } { 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}; } { 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", throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x",
param}; param};
} }
void Equalizer_getParamf(const EffectProps *props, ALenum param, float *val) void EqualizerEffectHandler::GetParamf(const EqualizerProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_EQUALIZER_LOW_GAIN: case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; break;
*val = props->Equalizer.LowGain; case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; break;
break; case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; break;
case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; break;
case AL_EQUALIZER_LOW_CUTOFF: case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; break;
*val = props->Equalizer.LowCutoff; case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; break;
break; case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; break;
case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; break;
case AL_EQUALIZER_MID1_GAIN: case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; break;
*val = props->Equalizer.Mid1Gain; case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; break;
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;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param};
} }
} }
void Equalizer_getParamfv(const EffectProps *props, ALenum param, float *vals) void EqualizerEffectHandler::GetParamfv(const EqualizerProps &props, ALenum param, float *vals)
{ Equalizer_getParamf(props, param, 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 #ifdef ALSOFT_EAX
namespace { namespace {
@ -319,91 +290,86 @@ template<>
throw Exception{message}; throw Exception{message};
} }
template<> bool EaxEqualizerCommitter::commit(const EAXEQUALIZERPROPERTIES &props)
bool EqualizerCommitter::commit(const EaxEffectProps &props)
{ {
if(props.mType == mEaxProps.mType && mEaxProps.mEqualizer.lLowGain == props.mEqualizer.lLowGain if(auto *cur = std::get_if<EAXEQUALIZERPROPERTIES>(&mEaxProps); cur && *cur == props)
&& 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)
return false; return false;
mEaxProps = props; mEaxProps = props;
mAlProps = [&]{
mAlProps.Equalizer.LowGain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lLowGain)); EqualizerProps ret{};
mAlProps.Equalizer.LowCutoff = props.mEqualizer.flLowCutOff; ret.LowGain = level_mb_to_gain(static_cast<float>(props.lLowGain));
mAlProps.Equalizer.Mid1Gain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lMid1Gain)); ret.LowCutoff = props.flLowCutOff;
mAlProps.Equalizer.Mid1Center = props.mEqualizer.flMid1Center; ret.Mid1Gain = level_mb_to_gain(static_cast<float>(props.lMid1Gain));
mAlProps.Equalizer.Mid1Width = props.mEqualizer.flMid1Width; ret.Mid1Center = props.flMid1Center;
mAlProps.Equalizer.Mid2Gain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lMid2Gain)); ret.Mid1Width = props.flMid1Width;
mAlProps.Equalizer.Mid2Center = props.mEqualizer.flMid2Center; ret.Mid2Gain = level_mb_to_gain(static_cast<float>(props.lMid2Gain));
mAlProps.Equalizer.Mid2Width = props.mEqualizer.flMid2Width; ret.Mid2Center = props.flMid2Center;
mAlProps.Equalizer.HighGain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lHighGain)); ret.Mid2Width = props.flMid2Width;
mAlProps.Equalizer.HighCutoff = props.mEqualizer.flHighCutOff; ret.HighGain = level_mb_to_gain(static_cast<float>(props.lHighGain));
ret.HighCutoff = props.flHighCutOff;
return ret;
}();
return true; return true;
} }
template<> void EaxEqualizerCommitter::SetDefaults(EaxEffectProps &props)
void EqualizerCommitter::SetDefaults(EaxEffectProps &props)
{ {
props.mType = EaxEffectType::Equalizer; static constexpr EAXEQUALIZERPROPERTIES defprops{[]
props.mEqualizer.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; {
props.mEqualizer.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; EAXEQUALIZERPROPERTIES ret{};
props.mEqualizer.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; ret.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN;
props.mEqualizer.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; ret.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF;
props.mEqualizer.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; ret.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN;
props.mEqualizer.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; ret.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER;
props.mEqualizer.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; ret.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH;
props.mEqualizer.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; ret.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN;
props.mEqualizer.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; ret.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER;
props.mEqualizer.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; ret.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH;
ret.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN;
ret.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF;
return ret;
}()};
props = defprops;
} }
template<> void EaxEqualizerCommitter::Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props)
void EqualizerCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{ {
switch(call.get_property_id()) switch(call.get_property_id())
{ {
case EAXEQUALIZER_NONE: break; case EAXEQUALIZER_NONE: break;
case EAXEQUALIZER_ALLPARAMETERS: call.set_value<Exception>(props.mEqualizer); break; case EAXEQUALIZER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXEQUALIZER_LOWGAIN: call.set_value<Exception>(props.mEqualizer.lLowGain); break; case EAXEQUALIZER_LOWGAIN: call.set_value<Exception>(props.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: call.set_value<Exception>(props.mEqualizer.flLowCutOff); break; case EAXEQUALIZER_LOWCUTOFF: call.set_value<Exception>(props.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: call.set_value<Exception>(props.mEqualizer.lMid1Gain); break; case EAXEQUALIZER_MID1GAIN: call.set_value<Exception>(props.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: call.set_value<Exception>(props.mEqualizer.flMid1Center); break; case EAXEQUALIZER_MID1CENTER: call.set_value<Exception>(props.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: call.set_value<Exception>(props.mEqualizer.flMid1Width); break; case EAXEQUALIZER_MID1WIDTH: call.set_value<Exception>(props.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: call.set_value<Exception>(props.mEqualizer.lMid2Gain); break; case EAXEQUALIZER_MID2GAIN: call.set_value<Exception>(props.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: call.set_value<Exception>(props.mEqualizer.flMid2Center); break; case EAXEQUALIZER_MID2CENTER: call.set_value<Exception>(props.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: call.set_value<Exception>(props.mEqualizer.flMid2Width); break; case EAXEQUALIZER_MID2WIDTH: call.set_value<Exception>(props.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: call.set_value<Exception>(props.mEqualizer.lHighGain); break; case EAXEQUALIZER_HIGHGAIN: call.set_value<Exception>(props.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: call.set_value<Exception>(props.mEqualizer.flHighCutOff); break; case EAXEQUALIZER_HIGHCUTOFF: call.set_value<Exception>(props.flHighCutOff); break;
default: fail_unknown_property_id(); default: fail_unknown_property_id();
} }
} }
template<> void EaxEqualizerCommitter::Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props)
void EqualizerCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{ {
switch(call.get_property_id()) switch(call.get_property_id())
{ {
case EAXEQUALIZER_NONE: break; case EAXEQUALIZER_NONE: break;
case EAXEQUALIZER_ALLPARAMETERS: defer<AllValidator>(call, props.mEqualizer); break; case EAXEQUALIZER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXEQUALIZER_LOWGAIN: defer<LowGainValidator>(call, props.mEqualizer.lLowGain); break; case EAXEQUALIZER_LOWGAIN: defer<LowGainValidator>(call, props.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: defer<LowCutOffValidator>(call, props.mEqualizer.flLowCutOff); break; case EAXEQUALIZER_LOWCUTOFF: defer<LowCutOffValidator>(call, props.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: defer<Mid1GainValidator>(call, props.mEqualizer.lMid1Gain); break; case EAXEQUALIZER_MID1GAIN: defer<Mid1GainValidator>(call, props.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: defer<Mid1CenterValidator>(call, props.mEqualizer.flMid1Center); break; case EAXEQUALIZER_MID1CENTER: defer<Mid1CenterValidator>(call, props.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: defer<Mid1WidthValidator>(call, props.mEqualizer.flMid1Width); break; case EAXEQUALIZER_MID1WIDTH: defer<Mid1WidthValidator>(call, props.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: defer<Mid2GainValidator>(call, props.mEqualizer.lMid2Gain); break; case EAXEQUALIZER_MID2GAIN: defer<Mid2GainValidator>(call, props.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: defer<Mid2CenterValidator>(call, props.mEqualizer.flMid2Center); break; case EAXEQUALIZER_MID2CENTER: defer<Mid2CenterValidator>(call, props.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: defer<Mid2WidthValidator>(call, props.mEqualizer.flMid2Width); break; case EAXEQUALIZER_MID2WIDTH: defer<Mid2WidthValidator>(call, props.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: defer<HighGainValidator>(call, props.mEqualizer.lHighGain); break; case EAXEQUALIZER_HIGHGAIN: defer<HighGainValidator>(call, props.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: defer<HighCutOffValidator>(call, props.mEqualizer.flHighCutOff); break; case EAXEQUALIZER_HIGHCUTOFF: defer<HighCutOffValidator>(call, props.flHighCutOff); break;
default: fail_unknown_property_id(); default: fail_unknown_property_id();
} }
} }

View file

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

View file

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

View file

@ -8,94 +8,92 @@
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #ifdef ALSOFT_EAX
#include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
namespace { namespace {
void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) constexpr EffectProps genDefaultProps() noexcept
{ {
switch(param) return std::monostate{};
{
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;
} }
} // namespace } // namespace
DEFINE_ALEFFECT_VTABLE(Null);
const EffectProps NullEffectProps{genDefaultProps()}; 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 #ifdef ALSOFT_EAX
namespace { namespace {
@ -117,30 +115,26 @@ template<>
throw Exception{message}; throw Exception{message};
} }
template<> bool EaxNullCommitter::commit(const std::monostate &props)
bool NullCommitter::commit(const EaxEffectProps &props)
{ {
const bool ret{props.mType != mEaxProps.mType}; const bool ret{std::holds_alternative<std::monostate>(mEaxProps)};
mEaxProps = props; mEaxProps = props;
mAlProps = std::monostate{};
return ret; return ret;
} }
template<> void EaxNullCommitter::SetDefaults(EaxEffectProps &props)
void NullCommitter::SetDefaults(EaxEffectProps &props)
{ {
props = EaxEffectProps{}; props = std::monostate{};
props.mType = EaxEffectType::None;
} }
template<> void EaxNullCommitter::Get(const EaxCall &call, const std::monostate&)
void NullCommitter::Get(const EaxCall &call, const EaxEffectProps&)
{ {
if(call.get_property_id() != 0) if(call.get_property_id() != 0)
fail_unknown_property_id(); fail_unknown_property_id();
} }
template<> void EaxNullCommitter::Set(const EaxCall &call, std::monostate&)
void NullCommitter::Set(const EaxCall &call, EaxEffectProps&)
{ {
if(call.get_property_id() != 0) if(call.get_property_id() != 0)
fail_unknown_property_id(); fail_unknown_property_id();

View file

@ -9,6 +9,7 @@
#ifdef ALSOFT_EAX #ifdef ALSOFT_EAX
#include "alnumeric.h" #include "alnumeric.h"
#include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -16,28 +17,32 @@
namespace { namespace {
void Pshifter_setParamf(EffectProps*, ALenum param, float) constexpr EffectProps genDefaultProps() noexcept
{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; }
void Pshifter_setParamfv(EffectProps*, ALenum param, const float*)
{ {
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", PshifterProps props{};
param}; 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) switch(param)
{ {
case AL_PITCH_SHIFTER_COARSE_TUNE: case AL_PITCH_SHIFTER_COARSE_TUNE:
if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE))
throw effect_exception{AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"}; throw effect_exception{AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"};
props->Pshifter.CoarseTune = val; props.CoarseTune = val;
break; break;
case AL_PITCH_SHIFTER_FINE_TUNE: case AL_PITCH_SHIFTER_FINE_TUNE:
if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE))
throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"}; throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"};
props->Pshifter.FineTune = val; props.FineTune = val;
break; break;
default: default:
@ -45,49 +50,40 @@ void Pshifter_setParami(EffectProps *props, ALenum param, int val)
param}; param};
} }
} }
void Pshifter_setParamiv(EffectProps *props, ALenum param, const int *vals) void PshifterEffectHandler::SetParamiv(PshifterProps &props, ALenum param, const int *vals)
{ Pshifter_setParami(props, param, vals[0]); } { 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) switch(param)
{ {
case AL_PITCH_SHIFTER_COARSE_TUNE: case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; break;
*val = props->Pshifter.CoarseTune; case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; break;
break;
case AL_PITCH_SHIFTER_FINE_TUNE:
*val = props->Pshifter.FineTune;
break;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
param}; param};
} }
} }
void Pshifter_getParamiv(const EffectProps *props, ALenum param, int *vals) void PshifterEffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals)
{ Pshifter_getParami(props, param, 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}; } { 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", throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x",
param}; 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 #ifdef ALSOFT_EAX
namespace { namespace {
@ -138,52 +134,48 @@ template<>
throw Exception{message}; throw Exception{message};
} }
template<> bool EaxPitchShifterCommitter::commit(const EAXPITCHSHIFTERPROPERTIES &props)
bool PitchShifterCommitter::commit(const EaxEffectProps &props)
{ {
if(props.mType == mEaxProps.mType if(auto *cur = std::get_if<EAXPITCHSHIFTERPROPERTIES>(&mEaxProps); cur && *cur == props)
&& mEaxProps.mPitchShifter.lCoarseTune == props.mPitchShifter.lCoarseTune
&& mEaxProps.mPitchShifter.lFineTune == props.mPitchShifter.lFineTune)
return false; return false;
mEaxProps = props; mEaxProps = props;
mAlProps = [&]{
mAlProps.Pshifter.CoarseTune = static_cast<int>(mEaxProps.mPitchShifter.lCoarseTune); PshifterProps ret{};
mAlProps.Pshifter.FineTune = static_cast<int>(mEaxProps.mPitchShifter.lFineTune); ret.CoarseTune = static_cast<int>(props.lCoarseTune);
ret.FineTune = static_cast<int>(props.lFineTune);
return ret;
}();
return true; return true;
} }
template<> void EaxPitchShifterCommitter::SetDefaults(EaxEffectProps &props)
void PitchShifterCommitter::SetDefaults(EaxEffectProps &props)
{ {
props.mType = EaxEffectType::PitchShifter; props = EAXPITCHSHIFTERPROPERTIES{EAXPITCHSHIFTER_DEFAULTCOARSETUNE,
props.mPitchShifter.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE; EAXPITCHSHIFTER_DEFAULTFINETUNE};
props.mPitchShifter.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE;
} }
template<> void EaxPitchShifterCommitter::Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props)
void PitchShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{ {
switch(call.get_property_id()) switch(call.get_property_id())
{ {
case EAXPITCHSHIFTER_NONE: break; case EAXPITCHSHIFTER_NONE: break;
case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props.mPitchShifter); break; case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXPITCHSHIFTER_COARSETUNE: call.set_value<Exception>(props.mPitchShifter.lCoarseTune); break; case EAXPITCHSHIFTER_COARSETUNE: call.set_value<Exception>(props.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: call.set_value<Exception>(props.mPitchShifter.lFineTune); break; case EAXPITCHSHIFTER_FINETUNE: call.set_value<Exception>(props.lFineTune); break;
default: fail_unknown_property_id(); default: fail_unknown_property_id();
} }
} }
template<> void EaxPitchShifterCommitter::Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props)
void PitchShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{ {
switch(call.get_property_id()) switch(call.get_property_id())
{ {
case EAXPITCHSHIFTER_NONE: break; case EAXPITCHSHIFTER_NONE: break;
case EAXPITCHSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props.mPitchShifter); break; case EAXPITCHSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXPITCHSHIFTER_COARSETUNE: defer<CoarseTuneValidator>(call, props.mPitchShifter.lCoarseTune); break; case EAXPITCHSHIFTER_COARSETUNE: defer<CoarseTuneValidator>(call, props.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: defer<FineTuneValidator>(call, props.mPitchShifter.lFineTune); break; case EAXPITCHSHIFTER_FINETUNE: defer<FineTuneValidator>(call, props.lFineTune); break;
default: fail_unknown_property_id(); 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 "config.h"
#include <optional>
#include <stdexcept> #include <stdexcept>
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h" #include "core/effects/base.h"
#include "aloptional.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX #ifdef ALSOFT_EAX
#include <cassert> #include <cassert>
#include "alnumeric.h" #include "al/eax/effect.h"
#include "al/eax/exception.h" #include "al/eax/exception.h"
#include "al/eax/utils.h" #include "al/eax/utils.h"
#endif // ALSOFT_EAX #endif // ALSOFT_EAX
@ -20,7 +20,7 @@
namespace { 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: \ #define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \
return VMorpherPhenome::x return VMorpherPhenome::x
@ -57,10 +57,10 @@ al::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val)
HANDLE_PHENOME(V); HANDLE_PHENOME(V);
HANDLE_PHENOME(Z); HANDLE_PHENOME(Z);
} }
return al::nullopt; return std::nullopt;
#undef HANDLE_PHENOME #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 #define HANDLE_PHENOME(x) case VMorpherPhenome::x: return AL_VOCAL_MORPHER_PHONEME_ ## x
switch(phenome) switch(phenome)
@ -100,7 +100,7 @@ ALenum EnumFromPhenome(VMorpherPhenome phenome)
#undef HANDLE_PHENOME #undef HANDLE_PHENOME
} }
al::optional<VMorpherWaveform> WaveformFromEmum(ALenum value) constexpr std::optional<VMorpherWaveform> WaveformFromEmum(ALenum value) noexcept
{ {
switch(value) 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_TRIANGLE: return VMorpherWaveform::Triangle;
case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return VMorpherWaveform::Sawtooth; 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) switch(type)
{ {
@ -122,13 +122,29 @@ ALenum EnumFromWaveform(VMorpherWaveform type)
std::to_string(static_cast<int>(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) switch(param)
{ {
case AL_VOCAL_MORPHER_PHONEMEA: case AL_VOCAL_MORPHER_PHONEMEA:
if(auto phenomeopt = PhenomeFromEnum(val)) if(auto phenomeopt = PhenomeFromEnum(val))
props->Vmorpher.PhonemeA = *phenomeopt; props.PhonemeA = *phenomeopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val}; throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val};
break; break;
@ -136,12 +152,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING)) if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING))
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"}; throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"};
props->Vmorpher.PhonemeACoarseTuning = val; props.PhonemeACoarseTuning = val;
break; break;
case AL_VOCAL_MORPHER_PHONEMEB: case AL_VOCAL_MORPHER_PHONEMEB:
if(auto phenomeopt = PhenomeFromEnum(val)) if(auto phenomeopt = PhenomeFromEnum(val))
props->Vmorpher.PhonemeB = *phenomeopt; props.PhonemeB = *phenomeopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val}; throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val};
break; break;
@ -149,12 +165,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING)) if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING))
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"}; throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"};
props->Vmorpher.PhonemeBCoarseTuning = val; props.PhonemeBCoarseTuning = val;
break; break;
case AL_VOCAL_MORPHER_WAVEFORM: case AL_VOCAL_MORPHER_WAVEFORM:
if(auto formopt = WaveformFromEmum(val)) if(auto formopt = WaveformFromEmum(val))
props->Vmorpher.Waveform = *formopt; props.Waveform = *formopt;
else else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val}; throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val};
break; break;
@ -164,19 +180,19 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
param}; 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", throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x",
param}; param};
} }
void Vmorpher_setParamf(EffectProps *props, ALenum param, float val) void VmorpherEffectHandler::SetParamf(VmorpherProps &props, ALenum param, float val)
{ {
switch(param) switch(param)
{ {
case AL_VOCAL_MORPHER_RATE: case AL_VOCAL_MORPHER_RATE:
if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE))
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"}; throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"};
props->Vmorpher.Rate = val; props.Rate = val;
break; break;
default: default:
@ -184,49 +200,35 @@ void Vmorpher_setParamf(EffectProps *props, ALenum param, float val)
param}; param};
} }
} }
void Vmorpher_setParamfv(EffectProps *props, ALenum param, const float *vals) void VmorpherEffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals)
{ Vmorpher_setParamf(props, param, vals[0]); } { 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) switch(param)
{ {
case AL_VOCAL_MORPHER_PHONEMEA: case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); break;
*val = EnumFromPhenome(props->Vmorpher.PhonemeA); case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; break;
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_PHONEMEA_COARSE_TUNING: case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break;
*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;
default: default:
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
param}; 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", throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x",
param}; param};
} }
void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val) void VmorpherEffectHandler::GetParamf(const VmorpherProps &props, ALenum param, float *val)
{ {
switch(param) switch(param)
{ {
case AL_VOCAL_MORPHER_RATE: case AL_VOCAL_MORPHER_RATE:
*val = props->Vmorpher.Rate; *val = props.Rate;
break; break;
default: default:
@ -234,26 +236,9 @@ void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val)
param}; param};
} }
} }
void Vmorpher_getParamfv(const EffectProps *props, ALenum param, float *vals) void VmorpherEffectHandler::GetParamfv(const VmorpherProps &props, ALenum param, float *vals)
{ Vmorpher_getParamf(props, param, 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 #ifdef ALSOFT_EAX
namespace { namespace {
@ -352,16 +337,9 @@ template<>
throw Exception{message}; throw Exception{message};
} }
template<> bool EaxVocalMorpherCommitter::commit(const EAXVOCALMORPHERPROPERTIES &props)
bool VocalMorpherCommitter::commit(const EaxEffectProps &props)
{ {
if(props.mType == mEaxProps.mType if(auto *cur = std::get_if<EAXVOCALMORPHERPROPERTIES>(&mEaxProps); cur && *cur == props)
&& 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)
return false; return false;
mEaxProps = props; mEaxProps = props;
@ -413,107 +391,65 @@ bool VocalMorpherCommitter::commit(const EaxEffectProps &props)
return VMorpherWaveform::Sinusoid; return VMorpherWaveform::Sinusoid;
}; };
mAlProps.Vmorpher.PhonemeA = get_phoneme(props.mVocalMorpher.ulPhonemeA); mAlProps = [&]{
mAlProps.Vmorpher.PhonemeACoarseTuning = static_cast<int>(props.mVocalMorpher.lPhonemeACoarseTuning); VmorpherProps ret{};
mAlProps.Vmorpher.PhonemeB = get_phoneme(props.mVocalMorpher.ulPhonemeB); ret.PhonemeA = get_phoneme(props.ulPhonemeA);
mAlProps.Vmorpher.PhonemeBCoarseTuning = static_cast<int>(props.mVocalMorpher.lPhonemeBCoarseTuning); ret.PhonemeACoarseTuning = static_cast<int>(props.lPhonemeACoarseTuning);
mAlProps.Vmorpher.Waveform = get_waveform(props.mVocalMorpher.ulWaveform); ret.PhonemeB = get_phoneme(props.ulPhonemeB);
mAlProps.Vmorpher.Rate = props.mVocalMorpher.flRate; ret.PhonemeBCoarseTuning = static_cast<int>(props.lPhonemeBCoarseTuning);
ret.Waveform = get_waveform(props.ulWaveform);
ret.Rate = props.flRate;
return ret;
}();
return true; return true;
} }
template<> void EaxVocalMorpherCommitter::SetDefaults(EaxEffectProps &props)
void VocalMorpherCommitter::SetDefaults(EaxEffectProps &props)
{ {
props.mType = EaxEffectType::VocalMorpher; static constexpr EAXVOCALMORPHERPROPERTIES defprops{[]
props.mVocalMorpher.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; {
props.mVocalMorpher.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; EAXVOCALMORPHERPROPERTIES ret{};
props.mVocalMorpher.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; ret.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA;
props.mVocalMorpher.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; ret.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING;
props.mVocalMorpher.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; ret.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB;
props.mVocalMorpher.flRate = EAXVOCALMORPHER_DEFAULTRATE; ret.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING;
ret.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM;
ret.flRate = EAXVOCALMORPHER_DEFAULTRATE;
return ret;
}()};
props = defprops;
} }
template<> void EaxVocalMorpherCommitter::Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props)
void VocalMorpherCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{ {
switch(call.get_property_id()) switch(call.get_property_id())
{ {
case EAXVOCALMORPHER_NONE: case EAXVOCALMORPHER_NONE: break;
break; case EAXVOCALMORPHER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXVOCALMORPHER_PHONEMEA: call.set_value<Exception>(props.ulPhonemeA); break;
case EAXVOCALMORPHER_ALLPARAMETERS: case EAXVOCALMORPHER_PHONEMEACOARSETUNING: call.set_value<Exception>(props.lPhonemeACoarseTuning); break;
call.set_value<Exception>(props.mVocalMorpher); case EAXVOCALMORPHER_PHONEMEB: call.set_value<Exception>(props.ulPhonemeB); break;
break; case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: call.set_value<Exception>(props.lPhonemeBCoarseTuning); break;
case EAXVOCALMORPHER_WAVEFORM: call.set_value<Exception>(props.ulWaveform); break;
case EAXVOCALMORPHER_PHONEMEA: case EAXVOCALMORPHER_RATE: call.set_value<Exception>(props.flRate); break;
call.set_value<Exception>(props.mVocalMorpher.ulPhonemeA); default: fail_unknown_property_id();
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();
} }
} }
template<> void EaxVocalMorpherCommitter::Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props)
void VocalMorpherCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{ {
switch(call.get_property_id()) switch(call.get_property_id())
{ {
case EAXVOCALMORPHER_NONE: case EAXVOCALMORPHER_NONE: break;
break; case EAXVOCALMORPHER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXVOCALMORPHER_PHONEMEA: defer<PhonemeAValidator>(call, props.ulPhonemeA); break;
case EAXVOCALMORPHER_ALLPARAMETERS: case EAXVOCALMORPHER_PHONEMEACOARSETUNING: defer<PhonemeACoarseTuningValidator>(call, props.lPhonemeACoarseTuning); break;
defer<AllValidator>(call, props.mVocalMorpher); case EAXVOCALMORPHER_PHONEMEB: defer<PhonemeBValidator>(call, props.ulPhonemeB); break;
break; case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: defer<PhonemeBCoarseTuningValidator>(call, props.lPhonemeBCoarseTuning); break;
case EAXVOCALMORPHER_WAVEFORM: defer<WaveformValidator>(call, props.ulWaveform); break;
case EAXVOCALMORPHER_PHONEMEA: case EAXVOCALMORPHER_RATE: defer<RateValidator>(call, props.flRate); break;
defer<PhonemeAValidator>(call, props.mVocalMorpher.ulPhonemeA); default: fail_unknown_property_id();
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();
} }
} }

View file

@ -20,6 +20,8 @@
#include "config.h" #include "config.h"
#include "error.h"
#ifdef _WIN32 #ifdef _WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
@ -29,27 +31,45 @@
#include <csignal> #include <csignal>
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <cstdlib>
#include <cstring> #include <cstring>
#include <mutex> #include <limits>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "al/debug.h"
#include "alc/alconfig.h"
#include "alc/context.h" #include "alc/context.h"
#include "almalloc.h" #include "alc/inprogext.h"
#include "core/except.h"
#include "core/logging.h" #include "core/logging.h"
#include "opthelpers.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, ...) 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_start(args, msg);
va_copy(args2, args); va_copy(args2, args);
int msglen{std::vsnprintf(message.data(), message.size(), msg, 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(args2);
va_end(args); va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
if(msglen >= 0) msg = message.data(); if(msglen >= 0)
else msg = "<internal error constructing message>"; 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", WARN("Error generated on context %p, code 0x%04x, \"%s\"\n",
decltype(std::declval<void*>()){this}, errorCode, msg); decltype(std::declval<void*>()){this}, errorCode, msg);
@ -77,30 +103,55 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
#endif #endif
} }
ALenum curerr{AL_NO_ERROR}; if(mLastThreadError.get() == AL_NO_ERROR)
mLastError.compare_exchange_strong(curerr, errorCode); mLastThreadError.set(errorCode);
debugMessage(DebugSource::API, DebugType::Error, 0, DebugSeverity::High,
{msg, static_cast<uint>(msglen)});
} }
AL_API ALenum AL_APIENTRY alGetError(void) /* Special-case alGetError since it (potentially) raises a debug signal and
START_API_FUNC * returns a non-default value for a null context.
*/
AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum
{ {
ContextRef context{GetContextRef()}; if(auto context = GetContextRef()) LIKELY
if(!context) UNLIKELY return alGetErrorDirect(context.get());
{
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;
}
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 "event.h"
#include <algorithm> #include <array>
#include <atomic> #include <atomic>
#include <cstring> #include <bitset>
#include <exception> #include <exception>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <new> #include <new>
#include <optional>
#include <string> #include <string>
#include <string_view>
#include <thread> #include <thread>
#include <tuple>
#include <utility> #include <utility>
#include <variant>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "AL/alext.h"
#include "albyte.h"
#include "alc/context.h" #include "alc/context.h"
#include "alc/effects/base.h" #include "alsem.h"
#include "alc/inprogext.h" #include "alspan.h"
#include "almalloc.h"
#include "core/async_event.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/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 "opthelpers.h"
#include "ringbuffer.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()}; RingBuffer *ring{context->mAsyncEvents.get()};
bool quitnow{false}; bool quitnow{false};
@ -44,76 +58,98 @@ static int EventThread(ALCcontext *context)
continue; continue;
} }
std::lock_guard<std::mutex> _{context->mEventCbLock}; std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
do { auto evt_span = al::span{std::launder(reinterpret_cast<AsyncEvent*>(evt_data.buf)),
auto *evt_ptr = reinterpret_cast<AsyncEvent*>(evt_data.buf); evt_data.len};
evt_data.buf += sizeof(AsyncEvent); for(auto &event : evt_span)
evt_data.len -= 1; {
quitnow = std::holds_alternative<AsyncKillThread>(event);
AsyncEvent evt{*evt_ptr};
al::destroy_at(evt_ptr);
ring->readAdvance(1);
quitnow = evt.EnumType == AsyncEvent::KillThread;
if(quitnow) UNLIKELY break; 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); auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire);
if(!context->mEventCb || !enabledevts.test(evt.EnumType)) auto proc_killthread = [](AsyncKillThread&) { };
continue; auto proc_release = [](AsyncEffectReleaseEvent &evt)
if(evt.EnumType == AsyncEvent::SourceStateChange)
{ {
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{}; 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 "; msg += " state has changed to ";
switch(evt.u.srcstate.state) switch(evt.mState)
{ {
case AsyncEvent::SrcState::Reset: case AsyncSrcState::Reset:
msg += "AL_INITIAL"; msg += "AL_INITIAL";
state = AL_INITIAL; state = AL_INITIAL;
break; break;
case AsyncEvent::SrcState::Stop: case AsyncSrcState::Stop:
msg += "AL_STOPPED"; msg += "AL_STOPPED";
state = AL_STOPPED; state = AL_STOPPED;
break; break;
case AsyncEvent::SrcState::Play: case AsyncSrcState::Play:
msg += "AL_PLAYING"; msg += "AL_PLAYING";
state = AL_PLAYING; state = AL_PLAYING;
break; break;
case AsyncEvent::SrcState::Pause: case AsyncSrcState::Pause:
msg += "AL_PAUSED"; msg += "AL_PAUSED";
state = AL_PAUSED; state = AL_PAUSED;
break; break;
} }
context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id, context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state,
state, static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam); static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam);
} };
else if(evt.EnumType == AsyncEvent::BufferCompleted) auto proc_buffercomp = [context,enabledevts](AsyncBufferCompleteEvent &evt)
{ {
std::string msg{std::to_string(evt.u.bufcomp.count)}; if(!context->mEventCb
if(evt.u.bufcomp.count == 1) msg += " buffer completed"; || !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"; else msg += " buffers completed";
context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id, context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount,
evt.u.bufcomp.count, static_cast<ALsizei>(msg.length()), msg.c_str(), static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam);
context->mEventParam); };
} auto proc_disconnect = [context,enabledevts](AsyncDisconnectEvent &evt)
else if(evt.EnumType == AsyncEvent::Disconnected)
{ {
context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, context->debugMessage(DebugSource::System, DebugType::Error, 0,
static_cast<ALsizei>(strlen(evt.u.disconnect.msg)), evt.u.disconnect.msg, DebugSeverity::High, evt.msg);
context->mEventParam);
} if(context->mEventCb
} while(evt_data.len != 0); && 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; 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) void StartEventThrd(ALCcontext *ctx)
{ {
try { try {
@ -138,7 +174,7 @@ void StopEventThrd(ALCcontext *ctx)
evt_data = ring->getWriteVector().first; evt_data = ring->getWriteVector().first;
} while(evt_data.len == 0); } 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); ring->writeAdvance(1);
ctx->mEventSem.post(); ctx->mEventSem.post();
@ -146,34 +182,25 @@ void StopEventThrd(ALCcontext *ctx)
ctx->mEventThread.join(); ctx->mEventThread.join();
} }
AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable) AL_API DECL_FUNCEXT3(void, alEventControl,SOFT, ALsizei,count, const ALenum*,types, ALboolean,enable)
START_API_FUNC FORCE_ALIGN void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count,
{ const ALenum *types, ALboolean enable) noexcept
ContextRef context{GetContextRef()}; try {
if(!context) UNLIKELY return; 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(!types)
if(count <= 0) return; throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
if(!types) return context->setError(AL_INVALID_VALUE, "NULL pointer");
ContextBase::AsyncEventBitset flags{}; ContextBase::AsyncEventBitset flags{};
const ALenum *types_end = types+count; for(ALenum evttype : al::span{types, static_cast<uint>(count)})
auto bad_type = std::find_if_not(types, types_end, {
[&flags](ALenum type) noexcept -> bool auto etype = GetEventType(evttype);
{ if(!etype)
if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) throw al::context_error{AL_INVALID_ENUM, "Invalid event type 0x%04x", evttype};
flags.set(AsyncEvent::BufferCompleted); flags.set(al::to_underlying(*etype));
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);
if(enable) if(enable)
{ {
@ -196,20 +223,18 @@ START_API_FUNC
/* Wait to ensure the event handler sees the changed flags before /* Wait to ensure the event handler sees the changed flags before
* returning. * 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) AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam)
START_API_FUNC FORCE_ALIGN void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context,
ALEVENTPROCSOFT callback, void *userParam) noexcept
{ {
ContextRef context{GetContextRef()}; std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
if(!context) UNLIKELY return;
std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> __{context->mEventCbLock};
context->mEventCb = callback; context->mEventCb = callback;
context->mEventParam = userParam; context->mEventParam = userParam;
} }
END_API_FUNC

View file

@ -20,63 +20,59 @@
#include "config.h" #include "config.h"
#include <cctype> #include <string_view>
#include <cstdlib> #include <vector>
#include <cstring>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "alc/context.h" #include "alc/context.h"
#include "alc/inprogext.h"
#include "alstring.h" #include "alstring.h"
#include "core/except.h" #include "direct_defs.h"
#include "opthelpers.h" #include "opthelpers.h"
AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName) AL_API DECL_FUNC1(ALboolean, alIsExtensionPresent, const ALchar*,extName)
START_API_FUNC FORCE_ALIGN ALboolean AL_APIENTRY alIsExtensionPresentDirect(ALCcontext *context, const ALchar *extName) noexcept
{ {
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return AL_FALSE;
if(!extName) UNLIKELY if(!extName) UNLIKELY
{ {
context->setError(AL_INVALID_VALUE, "NULL pointer"); context->setError(AL_INVALID_VALUE, "NULL pointer");
return AL_FALSE; return AL_FALSE;
} }
size_t len{strlen(extName)}; const std::string_view tofind{extName};
const char *ptr{context->mExtensionList}; for(std::string_view ext : context->mExtensions)
while(ptr && *ptr)
{ {
if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) if(al::case_compare(ext, tofind) == 0)
return AL_TRUE; return AL_TRUE;
if((ptr=strchr(ptr, ' ')) != nullptr)
{
do {
++ptr;
} while(isspace(*ptr));
}
} }
return AL_FALSE; return AL_FALSE;
} }
END_API_FUNC
AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName) AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName) noexcept
START_API_FUNC
{ {
if(!funcName) return nullptr; if(!funcName) return nullptr;
return alcGetProcAddress(nullptr, funcName); return alcGetProcAddress(nullptr, funcName);
} }
END_API_FUNC
AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName) FORCE_ALIGN ALvoid* AL_APIENTRY alGetProcAddressDirect(ALCcontext*, const ALchar *funcName) noexcept
START_API_FUNC
{ {
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); 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 #ifndef AL_FILTER_H
#define AL_FILTER_H #define AL_FILTER_H
#include <array>
#include <cstdint>
#include <string_view>
#include <utility>
#include <variant>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "AL/alext.h" #include "AL/efx.h"
#include "almalloc.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 { struct ALfilter {
@ -17,36 +42,35 @@ struct ALfilter {
float Gain{1.0f}; float Gain{1.0f};
float GainHF{1.0f}; float GainHF{1.0f};
float HFReference{LOWPASSFREQREF}; float HFReference{LowPassFreqRef};
float GainLF{1.0f}; float GainLF{1.0f};
float LFReference{HIGHPASSFREQREF}; float LFReference{HighPassFreqRef};
struct Vtable { using TableTypes = std::variant<NullFilterTable,LowpassFilterTable,HighpassFilterTable,
void (*const setParami )(ALfilter *filter, ALenum param, int val); BandpassFilterTable>;
void (*const setParamiv)(ALfilter *filter, ALenum param, const int *vals); TableTypes mTypeVariant;
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};
/* Self ID */ /* Self ID */
ALuint id{0}; ALuint id{0};
void setParami(ALenum param, int value) { vtab->setParami(this, param, value); } static void SetName(ALCcontext *context, ALuint id, std::string_view name);
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); }
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 #endif

View file

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

File diff suppressed because it is too large Load diff

View file

@ -2,26 +2,26 @@
#define AL_SOURCE_H #define AL_SOURCE_H
#include <array> #include <array>
#include <atomic>
#include <cstddef> #include <cstddef>
#include <iterator> #include <cstdint>
#include <limits>
#include <deque> #include <deque>
#include <limits>
#include <string_view>
#include <utility>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.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 "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "atomic.h" #include "alspan.h"
#include "core/context.h"
#include "core/voice.h" #include "core/voice.h"
#include "vector.h"
#ifdef ALSOFT_EAX #ifdef ALSOFT_EAX
#include "eax/api.h"
#include "eax/call.h" #include "eax/call.h"
#include "eax/exception.h" #include "eax/exception.h"
#include "eax/fx_slot_index.h" #include "eax/fx_slot_index.h"
@ -30,23 +30,23 @@
struct ALbuffer; struct ALbuffer;
struct ALeffectslot; struct ALeffectslot;
enum class Resampler : uint8_t;
enum class SourceStereo : bool { enum class SourceStereo : bool {
Normal = AL_NORMAL_SOFT, Normal = AL_NORMAL_SOFT,
Enhanced = AL_SUPER_STEREO_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 { struct ALbufferQueueItem : public VoiceBufferItem {
ALbuffer *mBuffer{nullptr}; ALbuffer *mBuffer{nullptr};
DISABLE_ALLOC() DISABLE_ALLOC
}; };
@ -88,6 +88,7 @@ struct ALsource {
DirectMode DirectChannels{DirectMode::Off}; DirectMode DirectChannels{DirectMode::Off};
SpatializeMode mSpatialize{SpatializeMode::Auto}; SpatializeMode mSpatialize{SpatializeMode::Auto};
SourceStereo mStereoMode{SourceStereo::Normal}; SourceStereo mStereoMode{SourceStereo::Normal};
bool mPanningEnabled{false};
bool DryGainHFAuto{true}; bool DryGainHFAuto{true};
bool WetGainAuto{true}; bool WetGainAuto{true};
@ -105,24 +106,27 @@ struct ALsource {
float Radius{0.0f}; float Radius{0.0f};
float EnhWidth{0.593f}; float EnhWidth{0.593f};
float mPan{0.0f};
/** Direct filter and auxiliary send info. */ /** Direct filter and auxiliary send info. */
struct { struct DirectData {
float Gain; float Gain{};
float GainHF; float GainHF{};
float HFReference; float HFReference{};
float GainLF; float GainLF{};
float LFReference; float LFReference{};
} Direct;
struct SendData {
ALeffectslot *Slot;
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 * Last user-specified offset, and the offset type (bytes, samples, or
@ -138,26 +142,28 @@ struct ALsource {
ALenum state{AL_INITIAL}; ALenum state{AL_INITIAL};
/** Source Buffer Queue head. */ /** Source Buffer Queue head. */
al::deque<ALbufferQueueItem> mQueue; std::deque<ALbufferQueueItem> mQueue;
bool mPropsDirty{true}; bool mPropsDirty{true};
/* Index into the context's Voices array. Lazily updated, only checked and /* Index into the context's Voices array. Lazily updated, only checked and
* reset when looking up the voice. * reset when looking up the voice.
*/ */
ALuint VoiceIdx{INVALID_VOICE_IDX}; ALuint VoiceIdx{InvalidVoiceIndex};
/** Self ID */ /** Self ID */
ALuint id{0}; ALuint id{0};
ALsource(); ALsource() noexcept;
~ALsource(); ~ALsource();
ALsource(const ALsource&) = delete; ALsource(const ALsource&) = delete;
ALsource& operator=(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 #ifdef ALSOFT_EAX
public: public:
@ -171,18 +177,18 @@ public:
private: private:
using Exception = EaxSourceException; 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_FXSlot0,
&EAXPROPERTYID_EAX40_FXSlot1, &EAXPROPERTYID_EAX40_FXSlot1,
&EAXPROPERTYID_EAX40_FXSlot2, &EAXPROPERTYID_EAX40_FXSlot2,
&EAXPROPERTYID_EAX40_FXSlot3, &EAXPROPERTYID_EAX40_FXSlot3,
}; };
static constexpr const EaxFxSlotIds eax5_fx_slot_ids = { static constexpr const EaxFxSlotIds eax5_fx_slot_ids{
&EAXPROPERTYID_EAX50_FXSlot0, &EAXPROPERTYID_EAX50_FXSlot0,
&EAXPROPERTYID_EAX50_FXSlot1, &EAXPROPERTYID_EAX50_FXSlot1,
&EAXPROPERTYID_EAX50_FXSlot2, &EAXPROPERTYID_EAX50_FXSlot2,
@ -215,11 +221,6 @@ private:
Eax3Props source; Eax3Props source;
EaxSends sends; EaxSends sends;
EAX40ACTIVEFXSLOTS active_fx_slots; EAX40ACTIVEFXSLOTS active_fx_slots;
bool operator==(const Eax4Props& rhs) noexcept
{
return std::memcmp(this, &rhs, sizeof(Eax4Props)) == 0;
}
}; };
struct Eax4State { struct Eax4State {
@ -232,11 +233,6 @@ private:
EaxSends sends; EaxSends sends;
EAX50ACTIVEFXSLOTS active_fx_slots; EAX50ACTIVEFXSLOTS active_fx_slots;
EaxSpeakerLevels speaker_levels; EaxSpeakerLevels speaker_levels;
bool operator==(const Eax5Props& rhs) noexcept
{
return std::memcmp(this, &rhs, sizeof(Eax5Props)) == 0;
}
}; };
struct Eax5State { struct Eax5State {
@ -546,7 +542,24 @@ private:
struct Eax5SourceAllValidator { struct Eax5SourceAllValidator {
void operator()(const EAX50SOURCEPROPERTIES& props) const 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); Eax5SourceMacroFXFactorValidator{}(props.flMacroFXFactor);
} }
}; };
@ -809,39 +822,38 @@ private:
[[noreturn]] static void eax_fail_unknown_active_fx_slot_id(); [[noreturn]] static void eax_fail_unknown_active_fx_slot_id();
[[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id(); [[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id();
void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept; static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept;
void eax1_set_defaults(Eax1Props& props) noexcept; static void eax1_set_defaults(Eax1Props& props) noexcept;
void eax1_set_defaults() 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 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 eax3_set_defaults() noexcept;
void eax4_set_sends_defaults(EaxSends& sends) noexcept; static void eax4_set_sends_defaults(EaxSends& sends) noexcept;
void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept; static void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept;
void eax4_set_defaults() noexcept; void eax4_set_defaults() noexcept;
void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept; static void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept;
void eax5_set_sends_defaults(EaxSends& sends) noexcept; static void eax5_set_sends_defaults(EaxSends& sends) noexcept;
void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept; static void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept;
void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept; static void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept;
void eax5_set_defaults(Eax5Props& props) noexcept; static void eax5_set_defaults(Eax5Props& props) noexcept;
void eax5_set_defaults() noexcept; void eax5_set_defaults() noexcept;
void eax_set_defaults() noexcept; void eax_set_defaults() noexcept;
void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept; static void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept;
void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept; static void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept;
void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept; static void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept;
void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept; static void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept;
static float eax_calculate_dst_occlusion_mb( static float eax_calculate_dst_occlusion_mb(
long src_occlusion_mb, long src_occlusion_mb,
float path_ratio, float path_ratio,
float lf_ratio) noexcept; 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( [[nodiscard]] auto eax_create_room_filter_param(const ALeffectslot& fx_slot,
const ALeffectslot& fx_slot, const EAXSOURCEALLSENDPROPERTIES& send) const noexcept -> EaxAlLowPassParam;
const EAXSOURCEALLSENDPROPERTIES& send) const noexcept;
void eax_update_direct_filter(); void eax_update_direct_filter();
void eax_update_room_filters(); 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); static void eax_get_active_fx_slot_id(const EaxCall& call, const al::span<const GUID> src_ids);
void eax1_get(const EaxCall& call, const Eax1Props& props); static void eax1_get(const EaxCall& call, const Eax1Props& props);
void eax2_get(const EaxCall& call, const Eax2Props& props); static void eax2_get(const EaxCall& call, const Eax2Props& props);
void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); static void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props);
void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props); static void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props);
void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props); static void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props);
void eax3_get(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 eax4_get(const EaxCall& call, const Eax4Props& props);
void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props); static 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_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props);
void eax5_get(const EaxCall& call, const Eax5Props& props); void eax5_get(const EaxCall& call, const Eax5Props& props);
void eax_get(const EaxCall& call); void eax_get(const EaxCall& call);
@ -976,21 +988,21 @@ private:
} }
template<typename TValidator, size_t TIdCount> 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); const auto src_ids = call.get_values<const GUID>(TIdCount);
std::for_each(src_ids.cbegin(), src_ids.cend(), TValidator{}); 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> 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); eax_defer_active_fx_slot_id<Eax4ActiveFxSlotIdValidator>(call, dst_ids);
} }
template<size_t TIdCount> 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); 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_auto();
void eax_set_efx_wet_gain_hf_auto(); void eax_set_efx_wet_gain_hf_auto();
void eax1_set(const EaxCall& call, Eax1Props& props); static void eax1_set(const EaxCall& call, Eax1Props& props);
void eax2_set(const EaxCall& call, Eax2Props& props); static void eax2_set(const EaxCall& call, Eax2Props& props);
void eax3_set(const EaxCall& call, Eax3Props& props); void eax3_set(const EaxCall& call, Eax3Props& props);
void eax4_set(const EaxCall& call, Eax4Props& props); void eax4_set(const EaxCall& call, Eax4Props& props);
void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props); static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props);
void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props); static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props);
void eax5_set(const EaxCall& call, Eax5Props& props); void eax5_set(const EaxCall& call, Eax5Props& props);
void eax_set(const EaxCall& call); void eax_set(const EaxCall& call);
@ -1041,4 +1053,19 @@ private:
void UpdateAllSourceProps(ALCcontext *context); 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 #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 "alconfig.h"
#include <cstdlib>
#include <cctype>
#include <cstring>
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#include <shlobj.h> #include <shlobj.h>
@ -34,25 +31,47 @@
#endif #endif
#include <algorithm> #include <algorithm>
#include <cstdio> #include <array>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <istream>
#include <limits>
#include <string> #include <string>
#include <string_view>
#include <utility> #include <utility>
#include <vector>
#include "alfstream.h" #include "almalloc.h"
#include "alstring.h" #include "alstring.h"
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "strutils.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 { 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 { struct ConfigEntry {
std::string key; std::string key;
std::string value; std::string value;
}; };
al::vector<ConfigEntry> ConfOpts; std::vector<ConfigEntry> ConfOpts;
std::string &lstrip(std::string &line) 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(); 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 output;
std::string envval; while(!str.empty())
while(*str != '\0')
{ {
const char *addstr; if(auto nextpos = str.find('$'))
size_t addstrlen;
if(str[0] != '$')
{ {
const char *next = std::strchr(str, '$'); output += str.substr(0, nextpos);
addstr = str; if(nextpos == std::string_view::npos)
addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str); break;
str += addstrlen; str.remove_prefix(nextpos);
} }
else
str.remove_prefix(1);
if(str.empty())
{ {
str++; output += '$';
if(*str == '$') break;
{
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();
}
} }
if(addstrlen == 0) if(str.front() == '$')
{
output += '$';
str.remove_prefix(1);
continue; 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; return output;
@ -140,44 +150,43 @@ void LoadConfigFromFile(std::istream &f)
if(buffer[0] == '[') if(buffer[0] == '[')
{ {
auto line = const_cast<char*>(buffer.data()); auto endpos = buffer.find(']', 1);
char *section = line+1; if(endpos == 1 || endpos == std::string::npos)
char *endsection;
endsection = std::strchr(section, ']');
if(!endsection || section == endsection)
{ {
ERR(" config parse error: bad line \"%s\"\n", line); ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
continue; continue;
} }
if(endsection[1] != 0) if(buffer[endpos+1] != '\0')
{ {
char *end = endsection+1; size_t last{endpos+1};
while(std::isspace(*end)) while(last < buffer.size() && std::isspace(buffer[last]))
++end; ++last;
if(*end != 0 && *end != '#')
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; continue;
} }
} }
*endsection = 0;
auto section = std::string_view{buffer}.substr(1, endpos-1);
curSection.clear(); curSection.clear();
if(al::strcasecmp(section, "general") != 0) if(al::case_compare(section, "general"sv) != 0)
{ {
do { do {
char *nextp = std::strchr(section, '%'); auto nextp = section.find('%');
if(!nextp) if(nextp == std::string_view::npos)
{ {
curSection += section; curSection += section;
break; break;
} }
curSection.append(section, nextp); curSection += section.substr(0, nextp);
section = 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[1] >= 'A' && section[1] <= 'F')) && (section[1] >= 'A' && section[1] <= 'F')) &&
((section[2] >= '0' && section[2] <= '9') || ((section[2] >= '0' && section[2] <= '9') ||
@ -198,19 +207,19 @@ void LoadConfigFromFile(std::istream &f)
else if(section[2] >= 'A' && section[2] <= 'F') else if(section[2] >= 'A' && section[2] <= 'F')
b |= (section[2]-'A'+0x0a); b |= (section[2]-'A'+0x0a);
curSection += static_cast<char>(b); curSection += static_cast<char>(b);
section += 3; section.remove_prefix(3);
} }
else if(section[1] == '%') else if(section.size() > 1 && section[1] == '%')
{ {
curSection += '%'; curSection += '%';
section += 2; section.remove_prefix(2);
} }
else else
{ {
curSection += '%'; curSection += '%';
section += 1; section.remove_prefix(1);
} }
} while(*section != 0); } while(!section.empty());
} }
continue; continue;
@ -228,16 +237,17 @@ void LoadConfigFromFile(std::istream &f)
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
continue; continue;
} }
auto keyend = sep++; auto keypart = std::string_view{buffer}.substr(0, sep++);
while(keyend > 0 && std::isspace(buffer[keyend-1])) while(!keypart.empty() && std::isspace(keypart.back()))
--keyend; keypart.remove_suffix(1);
if(!keyend) if(keypart.empty())
{ {
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
continue; continue;
} }
while(sep < buffer.size() && std::isspace(buffer[sep])) auto valpart = std::string_view{buffer}.substr(sep);
sep++; while(!valpart.empty() && std::isspace(valpart.front()))
valpart.remove_prefix(1);
std::string fullKey; std::string fullKey;
if(!curSection.empty()) if(!curSection.empty())
@ -245,20 +255,24 @@ void LoadConfigFromFile(std::istream &f)
fullKey += curSection; fullKey += curSection;
fullKey += '/'; fullKey += '/';
} }
fullKey += buffer.substr(0u, keyend); fullKey += keypart;
std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}}; if(valpart.size() > size_t{std::numeric_limits<int>::max()})
if(value.size() > 1)
{ {
if((value.front() == '"' && value.back() == '"') ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str());
|| (value.front() == '\'' && value.back() == '\'')) continue;
}
if(valpart.size() > 1)
{
if((valpart.front() == '"' && valpart.back() == '"')
|| (valpart.front() == '\'' && valpart.back() == '\''))
{ {
value.pop_back(); valpart.remove_prefix(1);
value.erase(value.begin()); 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 */ /* Check if we already have this option set */
auto find_key = [&fullKey](const ConfigEntry &entry) -> bool 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); auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
if(ent != ConfOpts.end()) if(ent != ConfOpts.end())
{ {
if(!value.empty()) if(!valpart.empty())
ent->value = expdup(value.c_str()); ent->value = expdup(valpart);
else else
ConfOpts.erase(ent); ConfOpts.erase(ent);
} }
else if(!value.empty()) else if(!valpart.empty())
ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())}); ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(valpart)});
} }
ConfOpts.shrink_to_fit(); 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; return nullptr;
std::string key; std::string key;
if(blockName && al::strcasecmp(blockName, "general") != 0) if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0)
{ {
key = blockName; key = blockName;
if(devName)
{
key += '/';
key += devName;
}
key += '/'; key += '/';
key += keyName;
} }
else if(!devName.empty())
{ {
if(devName) key += devName;
{ key += '/';
key = devName;
key += '/';
}
key += keyName;
} }
key += keyName;
auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
[&key](const ConfigEntry &entry) -> bool [&key](const ConfigEntry &entry) -> bool { return entry.key == key; });
{ return entry.key == key; });
if(iter != ConfOpts.cend()) 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()) if(!iter->value.empty())
return iter->value.c_str(); return iter->value.c_str();
return nullptr; return nullptr;
} }
if(!devName) if(devName.empty())
{
TRACE("Key %s not found\n", key.c_str());
return nullptr; return nullptr;
} return GetConfigValue({}, blockName, keyName);
return GetConfigValue(nullptr, blockName, keyName);
} }
} // namespace } // namespace
@ -329,33 +331,48 @@ const char *GetConfigValue(const char *devName, const char *blockName, const cha
#ifdef _WIN32 #ifdef _WIN32
void ReadALConfig() void ReadALConfig()
{ {
WCHAR buffer[MAX_PATH]; namespace fs = std::filesystem;
if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE) fs::path path;
{
std::string filepath{wstr_to_utf8(buffer)};
filepath += "\\alsoft.ini";
TRACE("Loading config %s...\n", filepath.c_str()); #if !defined(_GAMING_XBOX)
al::ifstream f{filepath}; {
if(f.is_open()) #if !defined(ALSOFT_UWP)
LoadConfigFromFile(f); 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}; path = fs::u8path(GetProcBinary().path);
if(!ppath.empty()) if(!path.empty())
{ {
ppath += "\\alsoft.ini"; path /= "alsoft.ini";
TRACE("Loading config %s...\n", ppath.c_str()); TRACE("Loading config %s...\n", path.u8string().c_str());
al::ifstream f{ppath}; if(std::ifstream f{path}; f.is_open())
if(f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
if(auto confpath = al::getenv(L"ALSOFT_CONF")) if(auto confpath = al::getenv(L"ALSOFT_CONF"))
{ {
TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str()); path = *confpath;
al::ifstream f{*confpath}; TRACE("Loading config %s...\n", path.u8string().c_str());
if(f.is_open()) if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
} }
@ -364,13 +381,12 @@ void ReadALConfig()
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); TRACE("Loading config %s...\n", path.u8string().c_str());
al::ifstream f{str}; if(std::ifstream f{path}; f.is_open())
if(f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
f.close();
std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")}; std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
/* Go through the list in reverse, since "the order of base directories /* 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 * important". Ergo, we need to load the settings from the later dirs
* first so that the settings in the earlier dirs override them. * first so that the settings in the earlier dirs override them.
*/ */
std::string fname;
while(!confpaths.empty()) while(!confpaths.empty())
{ {
auto next = confpaths.find_last_of(':'); auto next = confpaths.rfind(':');
if(next < confpaths.length()) if(next < confpaths.length())
{ {
fname = confpaths.substr(next+1); path = fs::path{std::string_view{confpaths}.substr(next+1)}.lexically_normal();
confpaths.erase(next); confpaths.erase(next);
} }
else else
{ {
fname = confpaths; path = fs::path{confpaths}.lexically_normal();
confpaths.clear(); confpaths.clear();
} }
if(fname.empty() || fname.front() != '/') if(!path.is_absolute())
WARN("Ignoring XDG config dir: %s\n", fname.c_str()); WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str());
else else
{ {
if(fname.back() != '/') fname += "/alsoft.conf"; path /= "alsoft.conf";
else fname += "alsoft.conf";
TRACE("Loading config %s...\n", fname.c_str()); TRACE("Loading config %s...\n", path.u8string().c_str());
f = al::ifstream{fname}; if(std::ifstream f{path}; f.is_open())
if(f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
fname.clear();
} }
#ifdef __APPLE__ #ifdef __APPLE__
CFBundleRef mainBundle = CFBundleGetMainBundle(); CFBundleRef mainBundle = CFBundleGetMainBundle();
if(mainBundle) if(mainBundle)
{ {
unsigned char fileName[PATH_MAX]; CFURLRef configURL{CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""),
CFURLRef configURL; nullptr)};
if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) && std::array<unsigned char,PATH_MAX> fileName{};
CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName))) if(configURL && CFURLGetFileSystemRepresentation(configURL, true, fileName.data(), fileName.size()))
{ {
f = al::ifstream{reinterpret_cast<char*>(fileName)}; if(std::ifstream f{reinterpret_cast<char*>(fileName.data())}; f.is_open())
if(f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
} }
@ -427,102 +438,100 @@ void ReadALConfig()
if(auto homedir = al::getenv("HOME")) if(auto homedir = al::getenv("HOME"))
{ {
fname = *homedir; path = *homedir;
if(fname.back() != '/') fname += "/.alsoftrc"; path /= ".alsoftrc";
else fname += ".alsoftrc";
TRACE("Loading config %s...\n", fname.c_str()); TRACE("Loading config %s...\n", path.u8string().c_str());
f = al::ifstream{fname}; if(std::ifstream f{path}; f.is_open())
if(f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
if(auto configdir = al::getenv("XDG_CONFIG_HOME")) if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
{ {
fname = *configdir; path = *configdir;
if(fname.back() != '/') fname += "/alsoft.conf"; path /= "alsoft.conf";
else fname += "alsoft.conf";
} }
else else
{ {
fname.clear(); path.clear();
if(auto homedir = al::getenv("HOME")) if(auto homedir = al::getenv("HOME"))
{ {
fname = *homedir; path = *homedir;
if(fname.back() != '/') fname += "/.config/alsoft.conf"; path /= ".config/alsoft.conf";
else fname += ".config/alsoft.conf";
} }
} }
if(!fname.empty()) if(!path.empty())
{ {
TRACE("Loading config %s...\n", fname.c_str()); TRACE("Loading config %s...\n", path.u8string().c_str());
f = al::ifstream{fname}; if(std::ifstream f{path}; f.is_open())
if(f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
std::string ppath{GetProcBinary().path}; path = GetProcBinary().path;
if(!ppath.empty()) if(!path.empty())
{ {
if(ppath.back() != '/') ppath += "/alsoft.conf"; path /= "alsoft.conf";
else ppath += "alsoft.conf";
TRACE("Loading config %s...\n", ppath.c_str()); TRACE("Loading config %s...\n", path.u8string().c_str());
f = al::ifstream{ppath}; if(std::ifstream f{path}; f.is_open())
if(f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
if(auto confname = al::getenv("ALSOFT_CONF")) if(auto confname = al::getenv("ALSOFT_CONF"))
{ {
TRACE("Loading config %s...\n", confname->c_str()); TRACE("Loading config %s...\n", confname->c_str());
f = al::ifstream{*confname}; if(std::ifstream f{*confname}; f.is_open())
if(f.is_open())
LoadConfigFromFile(f); LoadConfigFromFile(f);
} }
} }
#endif #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)}) if(const char *val{GetConfigValue(devName, blockName, keyName)})
return val; 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)}) if(const char *val{GetConfigValue(devName, blockName, keyName)})
return static_cast<int>(std::strtol(val, nullptr, 0)); 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)}) if(const char *val{GetConfigValue(devName, blockName, keyName)})
return static_cast<unsigned int>(std::strtoul(val, nullptr, 0)); 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)}) if(const char *val{GetConfigValue(devName, blockName, keyName)})
return std::strtof(val, nullptr); 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)}) if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true")==0 || atoi(val) != 0; || al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
return al::nullopt; 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)}) if(const char *val{GetConfigValue(devName, blockName, keyName)})
return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0); || al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
return def; return def;
} }

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -5,15 +5,15 @@
struct AlsaBackendFactory final : public BackendFactory { struct AlsaBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_ALSA_H */

View file

@ -7,17 +7,6 @@
#include <array> #include <array>
#include <atomic> #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" #include "core/devformat.h"
@ -25,10 +14,12 @@ namespace al {
backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code} backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{ {
/* NOLINTBEGIN(*-array-to-pointer-decay) */
std::va_list args; std::va_list args;
va_start(args, msg); va_start(args, msg);
setMessage(msg, args); setMessage(msg, args);
va_end(args); va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
} }
backend_exception::~backend_exception() = default; backend_exception::~backend_exception() = default;
@ -38,7 +29,7 @@ backend_exception::~backend_exception() = default;
bool BackendBase::reset() bool BackendBase::reset()
{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } { 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() uint BackendBase::availableSamples()
@ -46,27 +37,26 @@ uint BackendBase::availableSamples()
ClockLatency BackendBase::getClockLatency() ClockLatency BackendBase::getClockLatency()
{ {
ClockLatency ret; ClockLatency ret{};
uint refcount; uint refcount;
do { do {
refcount = mDevice->waitForMix(); refcount = mDevice->waitForMix();
ret.ClockTime = GetDeviceClockTime(mDevice); ret.ClockTime = mDevice->getClockTime();
std::atomic_thread_fence(std::memory_order_acquire); 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 /* NOTE: The device will generally have about all but one periods filled at
* any given time during playback. Without a more accurate measurement from * any given time during playback. Without a more accurate measurement from
* the output, this is an okay approximation. * the output, this is an okay approximation.
*/ */
ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize}, ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize};
std::chrono::seconds::zero());
ret.Latency /= mDevice->Frequency; ret.Latency /= mDevice->Frequency;
return ret; return ret;
} }
void BackendBase::setDefaultWFXChannelOrder() void BackendBase::setDefaultWFXChannelOrder() const
{ {
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
@ -126,6 +116,24 @@ void BackendBase::setDefaultWFXChannelOrder()
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11; mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break; 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: case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[FrontRight] = 1;
@ -141,7 +149,7 @@ void BackendBase::setDefaultWFXChannelOrder()
} }
} }
void BackendBase::setDefaultChannelOrder() void BackendBase::setDefaultChannelOrder() const
{ {
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
@ -179,6 +187,24 @@ void BackendBase::setDefaultChannelOrder()
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11; mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break; 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: case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[FrontRight] = 1;

View file

@ -3,13 +3,16 @@
#include <chrono> #include <chrono>
#include <cstdarg> #include <cstdarg>
#include <cstddef>
#include <memory> #include <memory>
#include <ratio> #include <ratio>
#include <string> #include <string>
#include <string_view>
#include <vector>
#include "albyte.h"
#include "core/device.h" #include "core/device.h"
#include "core/except.h" #include "core/except.h"
#include "alc/events.h"
using uint = unsigned int; using uint = unsigned int;
@ -20,27 +23,33 @@ struct ClockLatency {
}; };
struct BackendBase { struct BackendBase {
virtual void open(const char *name) = 0; virtual void open(std::string_view name) = 0;
virtual bool reset(); virtual bool reset();
virtual void start() = 0; virtual void start() = 0;
virtual void stop() = 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 uint availableSamples();
virtual ClockLatency getClockLatency(); virtual ClockLatency getClockLatency();
DeviceBase *const mDevice; DeviceBase *const mDevice;
BackendBase() = delete;
BackendBase(const BackendBase&) = delete;
BackendBase(BackendBase&&) = delete;
BackendBase(DeviceBase *device) noexcept : mDevice{device} { } BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
virtual ~BackendBase() = default; virtual ~BackendBase() = default;
void operator=(const BackendBase&) = delete;
void operator=(BackendBase&&) = delete;
protected: protected:
/** Sets the default channel order used by most non-WaveFormatEx-based APIs. */ /** 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. */ /** Sets the default channel order used by WaveFormatEx. */
void setDefaultWFXChannelOrder(); void setDefaultWFXChannelOrder() const;
}; };
using BackendPtr = std::unique_ptr<BackendBase>; 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 /* Helper to get the device latency from the backend, including any fixed
* latency from post-processing. * latency from post-processing.
*/ */
@ -74,16 +71,24 @@ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
struct BackendFactory { struct BackendFactory {
virtual bool init() = 0; BackendFactory() = default;
BackendFactory(const BackendFactory&) = delete;
virtual bool querySupport(BackendType type) = 0; BackendFactory(BackendFactory&&) = delete;
virtual std::string probe(BackendType type) = 0;
virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
protected:
virtual ~BackendFactory() = default; 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 { namespace al {
@ -98,15 +103,15 @@ class backend_exception final : public base_exception {
backend_error mErrorCode; backend_error mErrorCode;
public: public:
#ifdef __USE_MINGW_ANSI_STDIO #ifdef __MINGW32__
[[gnu::format(gnu_printf, 3, 4)]] [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
#else #else
[[gnu::format(printf, 3, 4)]] [[gnu::format(printf, 3, 4)]]
#endif #endif
backend_exception(backend_error code, const char *msg, ...); backend_exception(backend_error code, const char *msg, ...);
~backend_exception() override; ~backend_exception() override;
backend_error errorCode() const noexcept { return mErrorCode; } [[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; }
}; };
} // namespace al } // namespace al

View file

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

View file

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

View file

@ -5,15 +5,15 @@
struct DSoundBackendFactory final : public BackendFactory { struct DSoundBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_DSOUND_H */

View file

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

View file

@ -5,15 +5,15 @@
struct JackBackendFactory final : public BackendFactory { struct JackBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_JACK_H */

View file

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

View file

@ -5,15 +5,15 @@
struct LoopbackBackendFactory final : public BackendFactory { struct LoopbackBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_LOOPBACK_H */

View file

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

View file

@ -5,15 +5,15 @@
struct NullBackendFactory final : public BackendFactory { struct NullBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_NULL_H */

View file

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

View file

@ -5,15 +5,15 @@
struct OboeBackendFactory final : public BackendFactory { struct OboeBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_OBOE_H */

View file

@ -23,23 +23,26 @@
#include "opensl.h" #include "opensl.h"
#include <stdlib.h>
#include <jni.h> #include <jni.h>
#include <new>
#include <array> #include <array>
#include <cstdlib>
#include <cstring> #include <cstring>
#include <mutex>
#include <new>
#include <thread> #include <thread>
#include <functional> #include <functional>
#include "albit.h" #include "albit.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h" #include "core/device.h"
#include "core/helpers.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include "threads.h"
#include <SLES/OpenSLES.h> #include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h> #include <SLES/OpenSLES_Android.h>
@ -48,15 +51,17 @@
namespace { namespace {
using namespace std::string_view_literals;
/* Helper macros */ /* Helper macros */
#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__)) #define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS #define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
#define VCALL0(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 constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
{ {
switch(chans) 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_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_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT |
SL_SPEAKER_TOP_BACK_RIGHT; SL_SPEAKER_TOP_BACK_RIGHT;
case DevFmtX7144:
case DevFmtAmbi3D: case DevFmtAmbi3D:
break; break;
} }
@ -159,12 +165,10 @@ struct OpenSLPlayback final : public BackendBase {
~OpenSLPlayback() override; ~OpenSLPlayback() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept; void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLPlayback*>(context)->process(bq); }
int mixerProc(); int mixerProc();
void open(const char *name) override; void open(std::string_view name) override;
bool reset() override; bool reset() override;
void start() override; void start() override;
void stop() override; void stop() override;
@ -189,8 +193,6 @@ struct OpenSLPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true}; std::atomic<bool> mKillNow{true};
std::thread mThread; std::thread mThread;
DEF_NEWDEL(OpenSLPlayback)
}; };
OpenSLPlayback::~OpenSLPlayback() OpenSLPlayback::~OpenSLPlayback()
@ -229,7 +231,7 @@ void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept
int OpenSLPlayback::mixerProc() int OpenSLPlayback::mixerProc()
{ {
SetRTPriority(); SetRTPriority();
althrd_setname(MIXER_THREAD_NAME); althrd_setname(GetMixerThreadName());
SLPlayItf player; SLPlayItf player;
SLAndroidSimpleBufferQueueItf bufferQueue; SLAndroidSimpleBufferQueueItf bufferQueue;
@ -312,13 +314,13 @@ int OpenSLPlayback::mixerProc()
} }
void OpenSLPlayback::open(const char *name) void OpenSLPlayback::open(std::string_view name)
{ {
if(!name) if(name.empty())
name = opensl_device; name = GetDeviceName();
else if(strcmp(name, opensl_device) != 0) else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
name}; al::sizei(name), name.data()};
/* There's only one device, so if it's already open, there's nothing to do. */ /* There's only one device, so if it's already open, there's nothing to do. */
if(mEngineObj) return; if(mEngineObj) return;
@ -375,74 +377,6 @@ bool OpenSLPlayback::reset()
mRing = nullptr; 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->FmtChans = DevFmtStereo;
mDevice->FmtType = DevFmtShort; mDevice->FmtType = DevFmtShort;
@ -564,7 +498,9 @@ void OpenSLPlayback::start()
PrintErr(result, "bufferQueue->GetInterface"); PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result) 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"); PrintErr(result, "bufferQueue->RegisterCallback");
} }
if(SL_RESULT_SUCCESS != result) if(SL_RESULT_SUCCESS != result)
@ -628,8 +564,8 @@ ClockLatency OpenSLPlayback::getClockLatency()
{ {
ClockLatency ret; ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex}; std::lock_guard<std::mutex> dlock{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice); ret.ClockTime = mDevice->getClockTime();
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize}; ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency; ret.Latency /= mDevice->Frequency;
@ -642,13 +578,11 @@ struct OpenSLCapture final : public BackendBase {
~OpenSLCapture() override; ~OpenSLCapture() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept; 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 start() override;
void stop() override; void stop() override;
void captureSamples(al::byte *buffer, uint samples) override; void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override; uint availableSamples() override;
/* engine interfaces */ /* engine interfaces */
@ -662,8 +596,6 @@ struct OpenSLCapture final : public BackendBase {
uint mSplOffset{0u}; uint mSplOffset{0u};
uint mFrameSize{0}; uint mFrameSize{0};
DEF_NEWDEL(OpenSLCapture)
}; };
OpenSLCapture::~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) if(name.empty())
name = opensl_device; name = GetDeviceName();
else if(strcmp(name, opensl_device) != 0) else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
name}; al::sizei(name), name.data()};
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PrintErr(result, "slCreateEngine"); PrintErr(result, "slCreateEngine");
@ -710,10 +642,10 @@ void OpenSLCapture::open(const char* name)
{ {
mFrameSize = mDevice->frameSizeFromFmt(); mFrameSize = mDevice->frameSizeFromFmt();
/* Ensure the total length is at least 100ms */ /* Ensure the total length is at least 100ms */
uint length{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. */ /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100, uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u,
mDevice->Frequency/100*5)}; mDevice->Frequency/100u*5u)};
uint num_updates{(length+update_len-1) / update_len}; uint num_updates{(length+update_len-1) / update_len};
mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
@ -813,13 +745,15 @@ void OpenSLCapture::open(const char* name)
} }
if(SL_RESULT_SUCCESS == result) 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"); PrintErr(result, "bufferQueue->RegisterCallback");
} }
if(SL_RESULT_SUCCESS == result) if(SL_RESULT_SUCCESS == result)
{ {
const uint chunk_size{mDevice->UpdateSize * mFrameSize}; 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(); auto data = mRing->getWriteVector();
std::fill_n(data.first.buf, data.first.len*chunk_size, silence); 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 update_size{mDevice->UpdateSize};
const uint chunk_size{update_size * mFrameSize}; const uint chunk_size{update_size * mFrameSize};
@ -895,7 +829,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
auto rdata = mRing->getReadVector(); auto rdata = mRing->getReadVector();
for(uint i{0};i < samples;) 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}, std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
buffer + i*size_t{mFrameSize}); buffer + i*size_t{mFrameSize});
@ -932,7 +866,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
return; return;
/* For each buffer chunk that was fully read, queue another writable buffer /* 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 * 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 * given time. The end of the write vector increments when the read pointer
* advances, which will "expose" a previously unwritable element. So for * advances, which will "expose" a previously unwritable element. So for
@ -975,18 +909,15 @@ bool OSLBackendFactory::init() { return true; }
bool OSLBackendFactory::querySupport(BackendType type) bool OSLBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); } { return (type == BackendType::Playback || type == BackendType::Capture); }
std::string OSLBackendFactory::probe(BackendType type) auto OSLBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{ {
std::string outnames;
switch(type) switch(type)
{ {
case BackendType::Playback: case BackendType::Playback:
case BackendType::Capture: case BackendType::Capture:
/* Includes null char. */ return std::vector{std::string{GetDeviceName()}};
outnames.append(opensl_device, sizeof(opensl_device));
break;
} }
return outnames; return {};
} }
BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type) BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct OSLBackendFactory final : public BackendFactory { struct OSLBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_OSL_H */

View file

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

View file

@ -5,15 +5,15 @@
struct OSSBackendFactory final : public BackendFactory { struct OSSBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_OSS_H */

File diff suppressed because it is too large Load diff

View file

@ -2,22 +2,26 @@
#define BACKENDS_PIPEWIRE_H #define BACKENDS_PIPEWIRE_H
#include <string> #include <string>
#include <vector>
#include "alc/events.h"
#include "base.h" #include "base.h"
struct DeviceBase; struct DeviceBase;
struct PipeWireBackendFactory final : public BackendFactory { struct PipeWireBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_PIPEWIRE_H */

View file

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

View file

@ -5,15 +5,15 @@
struct PortBackendFactory final : public BackendFactory { struct PortBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_PORTAUDIO_H */

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,27 @@
#ifndef BACKENDS_PULSEAUDIO_H #ifndef BACKENDS_PULSEAUDIO_H
#define BACKENDS_PULSEAUDIO_H #define BACKENDS_PULSEAUDIO_H
#include <string>
#include <vector>
#include "alc/events.h"
#include "base.h" #include "base.h"
struct DeviceBase;
class PulseBackendFactory final : public BackendFactory { class PulseBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_PULSEAUDIO_H */

View file

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

View file

@ -5,15 +5,15 @@
struct SDL2BackendFactory final : public BackendFactory { struct SDL2BackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_SDL2_H */

View file

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

View file

@ -5,15 +5,15 @@
struct SndIOBackendFactory final : public BackendFactory { struct SndIOBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_SNDIO_H */

View file

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

View file

@ -5,15 +5,15 @@
struct SolarisBackendFactory final : public BackendFactory { struct SolarisBackendFactory final : public BackendFactory {
public: 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 */ #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 { struct WasapiBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_WASAPI_H */

View file

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

View file

@ -5,15 +5,15 @@
struct WaveBackendFactory final : public BackendFactory { struct WaveBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_WAVE_H */

View file

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

View file

@ -5,15 +5,15 @@
struct WinMMBackendFactory final : public BackendFactory { struct WinMMBackendFactory final : public BackendFactory {
public: 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 */ #endif /* BACKENDS_WINMM_H */

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