Browse Source

Update OpenAL soft

master
C. J. Howard 2 years ago
parent
commit
bccfe418ab
348 changed files with 64421 additions and 37091 deletions
  1. +19
    -2
      CMakeLists.txt
  2. +66
    -0
      modules/openal-soft/.github/workflows/ci.yml
  3. +8
    -4
      modules/openal-soft/.gitignore
  4. +73
    -16
      modules/openal-soft/.travis.yml
  5. +1875
    -2181
      modules/openal-soft/Alc/alc.cpp
  6. +163
    -188
      modules/openal-soft/Alc/alconfig.cpp
  7. +9
    -18
      modules/openal-soft/Alc/alconfig.h
  8. +0
    -214
      modules/openal-soft/Alc/alcontext.h
  9. +1340
    -1123
      modules/openal-soft/Alc/alu.cpp
  10. +38
    -0
      modules/openal-soft/Alc/alu.h
  11. +0
    -119
      modules/openal-soft/Alc/ambidefs.h
  12. +354
    -371
      modules/openal-soft/Alc/backends/alsa.cpp
  13. +3
    -3
      modules/openal-soft/Alc/backends/alsa.h
  14. +164
    -28
      modules/openal-soft/Alc/backends/base.cpp
  15. +81
    -36
      modules/openal-soft/Alc/backends/base.h
  16. +554
    -322
      modules/openal-soft/Alc/backends/coreaudio.cpp
  17. +3
    -3
      modules/openal-soft/Alc/backends/coreaudio.h
  18. +215
    -307
      modules/openal-soft/Alc/backends/dsound.cpp
  19. +3
    -3
      modules/openal-soft/Alc/backends/dsound.h
  20. +396
    -220
      modules/openal-soft/Alc/backends/jack.cpp
  21. +3
    -3
      modules/openal-soft/Alc/backends/jack.h
  22. +16
    -18
      modules/openal-soft/Alc/backends/loopback.cpp
  23. +3
    -3
      modules/openal-soft/Alc/backends/loopback.h
  24. +37
    -42
      modules/openal-soft/Alc/backends/null.cpp
  25. +3
    -3
      modules/openal-soft/Alc/backends/null.h
  26. +384
    -0
      modules/openal-soft/Alc/backends/oboe.cpp
  27. +19
    -0
      modules/openal-soft/Alc/backends/oboe.h
  28. +299
    -256
      modules/openal-soft/Alc/backends/opensl.cpp
  29. +3
    -3
      modules/openal-soft/Alc/backends/opensl.h
  30. +193
    -254
      modules/openal-soft/Alc/backends/oss.cpp
  31. +3
    -3
      modules/openal-soft/Alc/backends/oss.h
  32. +2008
    -0
      modules/openal-soft/Alc/backends/pipewire.cpp
  33. +23
    -0
      modules/openal-soft/Alc/backends/pipewire.h
  34. +137
    -155
      modules/openal-soft/Alc/backends/portaudio.cpp
  35. +3
    -3
      modules/openal-soft/Alc/backends/portaudio.h
  36. +662
    -722
      modules/openal-soft/Alc/backends/pulseaudio.cpp
  37. +3
    -3
      modules/openal-soft/Alc/backends/pulseaudio.h
  38. +0
    -955
      modules/openal-soft/Alc/backends/qsa.cpp
  39. +0
    -19
      modules/openal-soft/Alc/backends/qsa.h
  40. +88
    -91
      modules/openal-soft/Alc/backends/sdl2.cpp
  41. +3
    -3
      modules/openal-soft/Alc/backends/sdl2.h
  42. +273
    -237
      modules/openal-soft/Alc/backends/sndio.cpp
  43. +3
    -3
      modules/openal-soft/Alc/backends/sndio.h
  44. +102
    -101
      modules/openal-soft/Alc/backends/solaris.cpp
  45. +3
    -3
      modules/openal-soft/Alc/backends/solaris.h
  46. +722
    -611
      modules/openal-soft/Alc/backends/wasapi.cpp
  47. +3
    -3
      modules/openal-soft/Alc/backends/wasapi.h
  48. +135
    -129
      modules/openal-soft/Alc/backends/wave.cpp
  49. +3
    -3
      modules/openal-soft/Alc/backends/wave.h
  50. +156
    -167
      modules/openal-soft/Alc/backends/winmm.cpp
  51. +3
    -3
      modules/openal-soft/Alc/backends/winmm.h
  52. +0
    -201
      modules/openal-soft/Alc/bformatdec.cpp
  53. +0
    -56
      modules/openal-soft/Alc/bformatdec.h
  54. +0
    -236
      modules/openal-soft/Alc/compat.h
  55. +1299
    -0
      modules/openal-soft/Alc/context.cpp
  56. +504
    -0
      modules/openal-soft/Alc/context.h
  57. +0
    -369
      modules/openal-soft/Alc/converter.cpp
  58. +0
    -70
      modules/openal-soft/Alc/converter.h
  59. +0
    -23
      modules/openal-soft/Alc/cpu_caps.h
  60. +90
    -0
      modules/openal-soft/Alc/device.cpp
  61. +165
    -0
      modules/openal-soft/Alc/device.h
  62. +93
    -172
      modules/openal-soft/Alc/effects/autowah.cpp
  63. +3
    -164
      modules/openal-soft/Alc/effects/base.h
  64. +151
    -379
      modules/openal-soft/Alc/effects/chorus.cpp
  65. +82
    -116
      modules/openal-soft/Alc/effects/compressor.cpp
  66. +612
    -0
      modules/openal-soft/Alc/effects/convolution.cpp
  67. +45
    -87
      modules/openal-soft/Alc/effects/dedicated.cpp
  68. +66
    -163
      modules/openal-soft/Alc/effects/distortion.cpp
  69. +69
    -161
      modules/openal-soft/Alc/effects/echo.cpp
  70. +67
    -213
      modules/openal-soft/Alc/effects/equalizer.cpp
  71. +136
    -191
      modules/openal-soft/Alc/effects/fshifter.cpp
  72. +74
    -174
      modules/openal-soft/Alc/effects/modulator.cpp
  73. +27
    -104
      modules/openal-soft/Alc/effects/null.cpp
  74. +148
    -280
      modules/openal-soft/Alc/effects/pshifter.cpp
  75. +780
    -1156
      modules/openal-soft/Alc/effects/reverb.cpp
  76. +337
    -0
      modules/openal-soft/Alc/effects/vmorpher.cpp
  77. +0
    -137
      modules/openal-soft/Alc/filters/biquad.h
  78. +0
    -132
      modules/openal-soft/Alc/filters/splitter.cpp
  79. +0
    -63
      modules/openal-soft/Alc/filters/splitter.h
  80. +0
    -25
      modules/openal-soft/Alc/fpu_modes.h
  81. +0
    -740
      modules/openal-soft/Alc/helpers.cpp
  82. +0
    -1394
      modules/openal-soft/Alc/hrtf.cpp
  83. +0
    -121
      modules/openal-soft/Alc/hrtf.h
  84. +30
    -49
      modules/openal-soft/Alc/inprogext.h
  85. +0
    -65
      modules/openal-soft/Alc/logging.h
  86. +0
    -107
      modules/openal-soft/Alc/mastering.h
  87. +0
    -57
      modules/openal-soft/Alc/mixer/defs.h
  88. +0
    -132
      modules/openal-soft/Alc/mixer/hrtfbase.h
  89. +0
    -208
      modules/openal-soft/Alc/mixer/mixer_c.cpp
  90. +0
    -309
      modules/openal-soft/Alc/mixer/mixer_neon.cpp
  91. +0
    -263
      modules/openal-soft/Alc/mixer/mixer_sse.cpp
  92. +0
    -85
      modules/openal-soft/Alc/mixer/mixer_sse2.cpp
  93. +0
    -878
      modules/openal-soft/Alc/mixvoice.cpp
  94. +880
    -742
      modules/openal-soft/Alc/panning.cpp
  95. +0
    -126
      modules/openal-soft/Alc/uhjfilter.cpp
  96. +0
    -53
      modules/openal-soft/Alc/uhjfilter.h
  97. +31
    -0
      modules/openal-soft/BSD-3Clause
  98. +1455
    -1406
      modules/openal-soft/CMakeLists.txt
  99. +253
    -0
      modules/openal-soft/ChangeLog
  100. +0
    -100
      modules/openal-soft/OpenAL32/Include/alAuxEffectSlot.h

+ 19
- 2
CMakeLists.txt View File

@ -77,8 +77,26 @@ ExternalProject_Add(SDL2
"-DCMAKE_INSTALL_PREFIX=${MODULE_INSTALL_DIR}"
"-DSDL_SHARED=OFF"
"-DSDL_STATIC=ON"
"-SDL_LIBC=ON"
"-DSDL_LIBC=ON"
"-DSDL_FORCE_STATIC_VCRT=ON"
"-DSDL_ATOMIC=OFF"
"-DSDL_AUDIO=OFF"
"-DSDL_VIDEO=ON"
"-DSDL_RENDER=OFF"
"-DSDL_EVENTS=ON"
"-DSDL_JOYSTICK=ON"
"-DSDL_HAPTIC=OFF"
"-DSDL_HIDAPI=ON"
"-DSDL_POWER=OFF"
"-DSDL_THREADS=OFF"
"-DSDL_TIMERS=ON"
"-DSDL_FILE=OFF"
"-DSDL_LOADSO=ON"
"-DSDL_CPUINFO=OFF"
"-DSDL_FILESYSTEM=OFF"
"-DSDL_SENSOR=OFF"
"-DSDL_LOCALE=OFF"
"-DSDL_MISC=OFF"
BUILD_ALWAYS 0)
# Build OpenAL Soft module
@ -93,7 +111,6 @@ ExternalProject_Add(openal-soft
"-DFORCE_STATIC_VCRT=ON"
"-DALSOFT_UTILS=OFF"
"-DALSOFT_EXAMPLES=OFF"
"-DALSOFT_TESTS=OFF"
BUILD_ALWAYS 0)
# Build EnTT module

+ 66
- 0
modules/openal-soft/.github/workflows/ci.yml View File

@ -0,0 +1,66 @@
name: CI
on: [push]
jobs:
build:
name: ${{matrix.config.name}}
runs-on: ${{matrix.config.os}}
strategy:
fail-fast: false
matrix:
config:
- {
name: "Visual Studio 64-bit",
os: windows-latest,
cmake_opts: "-A x64 \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \
-DALSOFT_REQUIRE_WASAPI=ON",
build_type: "Release"
}
- {
name: "macOS",
os: macos-latest,
cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON",
build_type: "Release"
}
- {
name: "Linux",
os: ubuntu-latest,
cmake_opts: "-DALSOFT_REQUIRE_RTKIT=ON \
-DALSOFT_REQUIRE_ALSA=ON \
-DALSOFT_REQUIRE_OSS=ON \
-DALSOFT_REQUIRE_PORTAUDIO=ON \
-DALSOFT_REQUIRE_PULSEAUDIO=ON \
-DALSOFT_REQUIRE_JACK=ON",
deps_cmdline: "sudo apt update && sudo apt-get install -qq \
libpulse-dev \
portaudio19-dev \
libasound2-dev \
libjack-dev \
qtbase5-dev \
libdbus-1-dev",
build_type: "Release"
}
steps:
- uses: actions/checkout@v1
- name: Install Dependencies
shell: bash
run: |
if [[ ! -z "${{matrix.config.deps_cmdline}}" ]]; then
eval ${{matrix.config.deps_cmdline}}
fi
- name: Configure
shell: bash
run: |
cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ${{matrix.config.cmake_opts}} .
- name: Build
shell: bash
run: |
cmake --build build --config ${{matrix.config.build_type}}

+ 8
- 4
modules/openal-soft/.gitignore View File

@ -1,5 +1,9 @@
build*/
winbuild/
win64build/
openal-soft.kdev4
.kdev4/
winbuild
win64build
## kdevelop
*.kdev4
## qt-creator
CMakeLists.txt.user*

+ 73
- 16
modules/openal-soft/.travis.yml View File

@ -1,4 +1,4 @@
language: c
language: cpp
matrix:
include:
- os: linux
@ -7,7 +7,13 @@ matrix:
dist: trusty
env:
- BUILD_ANDROID=true
- os: freebsd
compiler: clang
- os: osx
- os: osx
osx_image: xcode11
env:
- BUILD_IOS=true
sudo: required
install:
- >
@ -20,24 +26,49 @@ install:
portaudio19-dev \
libasound2-dev \
libjack-dev \
qtbase5-dev
qtbase5-dev \
libdbus-1-dev
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip
curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip
unzip -q ~/android-ndk.zip -d ~ \
'android-ndk-r16b/build/cmake/*' \
'android-ndk-r16b/build/core/toolchains/arm-linux-androideabi-*/*' \
'android-ndk-r16b/platforms/android-14/arch-arm/*' \
'android-ndk-r16b/source.properties' \
'android-ndk-r16b/sources/android/support/include/*' \
'android-ndk-r16b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*' \
'android-ndk-r16b/sources/cxx-stl/llvm-libc++/include/*' \
'android-ndk-r16b/sysroot/*' \
'android-ndk-r16b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \
'android-ndk-r16b/toolchains/llvm/prebuilt/linux-x86_64/*'
'android-ndk-r21/build/cmake/*' \
'android-ndk-r21/build/core/toolchains/arm-linux-androideabi-*/*' \
'android-ndk-r21/platforms/android-16/arch-arm/*' \
'android-ndk-r21/source.properties' \
'android-ndk-r21/sources/android/support/include/*' \
'android-ndk-r21/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*' \
'android-ndk-r21/sources/cxx-stl/llvm-libc++/include/*' \
'android-ndk-r21/sysroot/*' \
'android-ndk-r21/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \
'android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/*'
export OBOE_LOC=~/oboe
git clone --depth 1 -b 1.3-stable https://github.com/google/oboe "$OBOE_LOC"
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "freebsd" ]]; then
# Install Ninja as it's used downstream.
# Install dependencies for all supported backends.
# Install Qt5 dependency for alsoft-config.
# Install ffmpeg for examples.
sudo pkg install -y \
alsa-lib \
ffmpeg \
jackit \
libmysofa \
ninja \
portaudio \
pulseaudio \
qt5-buildtools \
qt5-qmake \
qt5-widgets \
sdl2 \
sndio \
$NULL
fi
script:
- cmake --version
- >
if [[ "${TRAVIS_OS_NAME}" == "linux" && -z "${BUILD_ANDROID}" ]]; then
cmake \
@ -53,16 +84,42 @@ script:
if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
cmake \
-DANDROID_STL=c++_shared \
-DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r16b/build/cmake/android.toolchain.cmake \
-DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r21/build/cmake/android.toolchain.cmake \
-DOBOE_SOURCE="$OBOE_LOC" \
-DALSOFT_REQUIRE_OBOE=ON \
-DALSOFT_REQUIRE_OPENSL=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
.
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
if [[ "${TRAVIS_OS_NAME}" == "freebsd" ]]; then
cmake -GNinja \
-DALSOFT_REQUIRE_ALSA=ON \
-DALSOFT_REQUIRE_JACK=ON \
-DALSOFT_REQUIRE_OSS=ON \
-DALSOFT_REQUIRE_PORTAUDIO=ON \
-DALSOFT_REQUIRE_PULSEAUDIO=ON \
-DALSOFT_REQUIRE_SDL2=ON \
-DALSOFT_REQUIRE_SNDIO=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
.
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "osx" && -z "${BUILD_IOS}" ]]; then
cmake \
-DALSOFT_REQUIRE_COREAUDIO=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
.
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "osx" && "${BUILD_IOS}" == "true" ]]; then
cmake \
-GXcode \
-DCMAKE_SYSTEM_NAME=iOS \
-DALSOFT_OSX_FRAMEWORK=ON \
-DALSOFT_REQUIRE_COREAUDIO=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
"-DCMAKE_OSX_ARCHITECTURES=armv7;arm64" \
.
fi
- make -j2
- cmake --build . --clean-first

+ 1875
- 2181
modules/openal-soft/Alc/alc.cpp
File diff suppressed because it is too large
View File


+ 163
- 188
modules/openal-soft/Alc/alconfig.cpp View File

@ -18,20 +18,14 @@
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#ifdef _WIN32
#ifdef __MINGW32__
#define _WIN32_IE 0x501
#else
#define _WIN32_IE 0x400
#endif
#endif
#include "config.h"
#include "alconfig.h"
#include <cstdlib>
#include <cctype>
#include <cstring>
#ifdef _WIN32_IE
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#endif
@ -39,14 +33,17 @@
#include <CoreFoundation/CoreFoundation.h>
#endif
#include <vector>
#include <string>
#include <algorithm>
#include <cstdio>
#include <string>
#include <utility>
#include "alMain.h"
#include "alconfig.h"
#include "logging.h"
#include "compat.h"
#include "alfstream.h"
#include "alstring.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "strutils.h"
#include "vector.h"
namespace {
@ -54,11 +51,6 @@ namespace {
struct ConfigEntry {
std::string key;
std::string value;
template<typename T0, typename T1>
ConfigEntry(T0&& key_, T1&& val_)
: key{std::forward<T0>(key_)}, value{std::forward<T1>(val_)}
{ }
};
al::vector<ConfigEntry> ConfOpts;
@ -80,10 +72,11 @@ bool readline(std::istream &f, std::string &output)
return std::getline(f, output) && !output.empty();
}
std:: string expdup(const char *str)
std::string expdup(const char *str)
{
std::string output;
std::string envval;
while(*str != '\0')
{
const char *addstr;
@ -110,20 +103,20 @@ std:: string expdup(const char *str)
}
else
{
bool hasbraces{(*str == '{')};
if(hasbraces) str++;
std::string envname;
while((std::isalnum(*str) || *str == '_'))
envname += *(str++);
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++;
if((addstr=std::getenv(envname.c_str())) == nullptr)
continue;
addstrlen = std::strlen(addstr);
envval = al::getenv(envname.c_str()).value_or(std::string{});
addstr = envval.data();
addstrlen = envval.length();
}
}
if(addstrlen == 0)
@ -142,23 +135,19 @@ void LoadConfigFromFile(std::istream &f)
while(readline(f, buffer))
{
while(!buffer.empty() && std::isspace(buffer.back()))
buffer.pop_back();
if(lstrip(buffer).empty())
continue;
buffer.push_back(0);
char *line{&buffer[0]};
if(line[0] == '[')
if(buffer[0] == '[')
{
char *line{&buffer[0]};
char *section = line+1;
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", line);
continue;
}
if(endsection[1] != 0)
@ -168,14 +157,14 @@ void LoadConfigFromFile(std::istream &f)
++end;
if(*end != 0 && *end != '#')
{
ERR("config parse error: bad line \"%s\"\n", line);
ERR(" config parse error: bad line \"%s\"\n", line);
continue;
}
}
*endsection = 0;
curSection.clear();
if(strcasecmp(section, "general") != 0)
if(al::strcasecmp(section, "general") != 0)
{
do {
char *nextp = std::strchr(section, '%');
@ -195,7 +184,7 @@ void LoadConfigFromFile(std::istream &f)
(section[2] >= 'a' && section[2] <= 'f') ||
(section[2] >= 'A' && section[2] <= 'F')))
{
unsigned char b = 0;
int b{0};
if(section[1] >= '0' && section[1] <= '9')
b = (section[1]-'0') << 4;
else if(section[1] >= 'a' && section[1] <= 'f')
@ -227,31 +216,28 @@ void LoadConfigFromFile(std::istream &f)
continue;
}
char *comment{std::strchr(line, '#')};
if(comment) *(comment++) = 0;
if(!line[0]) continue;
auto cmtpos = std::min(buffer.find('#'), buffer.size());
while(cmtpos > 0 && std::isspace(buffer[cmtpos-1]))
--cmtpos;
if(!cmtpos) continue;
buffer.erase(cmtpos);
char key[256]{};
char value[256]{};
if(std::sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 ||
std::sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 ||
std::sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2)
{
/* sscanf doesn't handle '' or "" as empty values, so clip it
* manually. */
if(std::strcmp(value, "\"\"") == 0 || std::strcmp(value, "''") == 0)
value[0] = 0;
}
else if(sscanf(line, "%255[^=] %255[=]", key, value) == 2)
auto sep = buffer.find('=');
if(sep == std::string::npos)
{
/* Special case for 'key =' */
value[0] = 0;
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
continue;
}
else
auto keyend = sep++;
while(keyend > 0 && std::isspace(buffer[keyend-1]))
--keyend;
if(!keyend)
{
ERR("config parse error: malformed option line: \"%s\"\n\n", line);
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
continue;
}
while(sep < buffer.size() && std::isspace(buffer[sep]))
sep++;
std::string fullKey;
if(!curSection.empty())
@ -259,33 +245,89 @@ void LoadConfigFromFile(std::istream &f)
fullKey += curSection;
fullKey += '/';
}
fullKey += key;
while(!fullKey.empty() && std::isspace(fullKey.back()))
fullKey.pop_back();
fullKey += buffer.substr(0u, keyend);
std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}};
if(value.size() > 1)
{
if((value.front() == '"' && value.back() == '"')
|| (value.front() == '\'' && value.back() == '\''))
{
value.pop_back();
value.erase(value.begin());
}
}
TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str());
/* Check if we already have this option set */
auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(),
[&fullKey](const ConfigEntry &entry) -> bool
{ return entry.key == fullKey; }
);
auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
{ return entry.key == fullKey; };
auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
if(ent != ConfOpts.end())
ent->value = expdup(value);
else
{
ConfOpts.emplace_back(std::move(fullKey), expdup(value));
ent = ConfOpts.end()-1;
if(!value.empty())
ent->value = expdup(value.c_str());
else
ConfOpts.erase(ent);
}
TRACE("found '%s' = '%s'\n", ent->key.c_str(), ent->value.c_str());
else if(!value.empty())
ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())});
}
ConfOpts.shrink_to_fit();
}
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName)
{
if(!keyName)
return nullptr;
std::string key;
if(blockName && al::strcasecmp(blockName, "general") != 0)
{
key = blockName;
if(devName)
{
key += '/';
key += devName;
}
key += '/';
key += keyName;
}
else
{
if(devName)
{
key = devName;
key += '/';
}
key += keyName;
}
auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
[&key](const ConfigEntry &entry) -> bool
{ return entry.key == key; });
if(iter != ConfOpts.cend())
{
TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
if(!iter->value.empty())
return iter->value.c_str();
return nullptr;
}
if(!devName)
{
TRACE("Key %s not found\n", key.c_str());
return nullptr;
}
return GetConfigValue(nullptr, blockName, keyName);
}
} // namespace
#ifdef _WIN32
void ReadALConfig(void) noexcept
void ReadALConfig()
{
WCHAR buffer[MAX_PATH];
if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
@ -309,19 +351,18 @@ void ReadALConfig(void) noexcept
LoadConfigFromFile(f);
}
const WCHAR *str{_wgetenv(L"ALSOFT_CONF")};
if(str != nullptr && *str)
if(auto confpath = al::getenv(L"ALSOFT_CONF"))
{
std::string filepath{wstr_to_utf8(str)};
TRACE("Loading config %s...\n", filepath.c_str());
al::ifstream f{filepath};
TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
al::ifstream f{*confpath};
if(f.is_open())
LoadConfigFromFile(f);
}
}
#else
void ReadALConfig(void) noexcept
void ReadALConfig()
{
const char *str{"/etc/openal/alsoft.conf"};
@ -331,9 +372,7 @@ void ReadALConfig(void) noexcept
LoadConfigFromFile(f);
f.close();
if(!(str=getenv("XDG_CONFIG_DIRS")) || str[0] == 0)
str = "/etc/xdg";
std::string confpaths = str;
std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
/* Go through the list in reverse, since "the order of base directories
* denotes their importance; the first directory listed is the most
* important". Ergo, we need to load the settings from the later dirs
@ -362,7 +401,7 @@ void ReadALConfig(void) noexcept
else fname += "alsoft.conf";
TRACE("Loading config %s...\n", fname.c_str());
al::ifstream f{fname};
f = al::ifstream{fname};
if(f.is_open())
LoadConfigFromFile(f);
}
@ -379,37 +418,37 @@ void ReadALConfig(void) noexcept
if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
{
al::ifstream f{reinterpret_cast<char*>(fileName)};
f = al::ifstream{reinterpret_cast<char*>(fileName)};
if(f.is_open())
LoadConfigFromFile(f);
}
}
#endif
if((str=getenv("HOME")) != nullptr && *str)
if(auto homedir = al::getenv("HOME"))
{
fname = str;
fname = *homedir;
if(fname.back() != '/') fname += "/.alsoftrc";
else fname += ".alsoftrc";
TRACE("Loading config %s...\n", fname.c_str());
al::ifstream f{fname};
f = al::ifstream{fname};
if(f.is_open())
LoadConfigFromFile(f);
}
if((str=getenv("XDG_CONFIG_HOME")) != nullptr && str[0] != 0)
if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
{
fname = str;
fname = *configdir;
if(fname.back() != '/') fname += "/alsoft.conf";
else fname += "alsoft.conf";
}
else
{
fname.clear();
if((str=getenv("HOME")) != nullptr && str[0] != 0)
if(auto homedir = al::getenv("HOME"))
{
fname = str;
fname = *homedir;
if(fname.back() != '/') fname += "/.config/alsoft.conf";
else fname += ".config/alsoft.conf";
}
@ -417,7 +456,7 @@ void ReadALConfig(void) noexcept
if(!fname.empty())
{
TRACE("Loading config %s...\n", fname.c_str());
al::ifstream f{fname};
f = al::ifstream{fname};
if(f.is_open())
LoadConfigFromFile(f);
}
@ -429,125 +468,61 @@ void ReadALConfig(void) noexcept
else ppath += "alsoft.conf";
TRACE("Loading config %s...\n", ppath.c_str());
al::ifstream f{ppath};
f = al::ifstream{ppath};
if(f.is_open())
LoadConfigFromFile(f);
}
if((str=getenv("ALSOFT_CONF")) != nullptr && *str)
if(auto confname = al::getenv("ALSOFT_CONF"))
{
TRACE("Loading config %s...\n", str);
al::ifstream f{str};
TRACE("Loading config %s...\n", confname->c_str());
f = al::ifstream{*confname};
if(f.is_open())
LoadConfigFromFile(f);
}
}
#endif
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
{
if(!keyName)
return def;
std::string key;
if(blockName && strcasecmp(blockName, "general") != 0)
{
key = blockName;
if(devName)
{
key += '/';
key += devName;
}
key += '/';
key += keyName;
}
else
{
if(devName)
{
key = devName;
key += '/';
}
key += keyName;
}
auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
[&key](const ConfigEntry &entry) -> bool
{ return entry.key == key; }
);
if(iter != ConfOpts.cend())
{
TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
if(!iter->value.empty())
return iter->value.c_str();
return def;
}
if(!devName)
{
TRACE("Key %s not found\n", key.c_str());
return def;
}
return GetConfigValue(nullptr, blockName, keyName, def);
}
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
return val[0] != 0;
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional<std::string>(val);
return al::nullopt;
}
int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret)
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return 0;
*ret = val;
return 1;
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
return al::nullopt;
}
int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret)
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return 0;
*ret = std::strtol(val, nullptr, 0);
return 1;
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
return al::nullopt;
}
int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret)
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return 0;
*ret = std::strtoul(val, nullptr, 0);
return 1;
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(std::strtof(val, nullptr));
return al::nullopt;
}
int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret)
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return 0;
*ret = std::strtof(val, nullptr);
return 1;
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true")==0 || atoi(val) != 0);
return al::nullopt;
}
int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret)
bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return 0;
*ret = (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
strcasecmp(val, "on") == 0 || atoi(val) != 0);
return 1;
}
int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return def != 0;
return (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
strcasecmp(val, "on") == 0 || atoi(val) != 0);
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0);
return def;
}

+ 9
- 18
modules/openal-soft/Alc/alconfig.h View File

@ -1,27 +1,18 @@
#ifndef ALCONFIG_H
#define ALCONFIG_H
#ifdef __cplusplus
#define NOEXCEPT noexcept
extern "C" {
#else
#define NOEXCEPT
#endif
#include <string>
void ReadALConfig(void) NOEXCEPT;
#include "aloptional.h"
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName);
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def);
int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def);
void ReadALConfig();
int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret);
int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret);
int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret);
int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret);
int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret);
bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def);
#ifdef __cplusplus
} // extern "C"
#endif
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName);
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName);
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName);
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName);
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName);
#endif /* ALCONFIG_H */

+ 0
- 214
modules/openal-soft/Alc/alcontext.h View File

@ -1,214 +0,0 @@
#ifndef ALCONTEXT_H
#define ALCONTEXT_H
#include <mutex>
#include <atomic>
#include <memory>
#include <thread>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "inprogext.h"
#include "atomic.h"
#include "vector.h"
#include "threads.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alListener.h"
struct ALsource;
struct ALeffectslot;
struct ALcontextProps;
struct ALlistenerProps;
struct ALvoiceProps;
struct ALeffectslotProps;
struct ALvoice;
struct RingBuffer;
enum class DistanceModel {
InverseClamped = AL_INVERSE_DISTANCE_CLAMPED,
LinearClamped = AL_LINEAR_DISTANCE_CLAMPED,
ExponentClamped = AL_EXPONENT_DISTANCE_CLAMPED,
Inverse = AL_INVERSE_DISTANCE,
Linear = AL_LINEAR_DISTANCE,
Exponent = AL_EXPONENT_DISTANCE,
Disable = AL_NONE,
Default = InverseClamped
};
struct SourceSubList {
uint64_t FreeMask{~0_u64};
ALsource *Sources{nullptr}; /* 64 */
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; }
};
struct EffectSlotSubList {
uint64_t FreeMask{~0_u64};
ALeffectslot *EffectSlots{nullptr}; /* 64 */
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; }
};
struct ALCcontext {
RefCount ref{1u};
al::vector<SourceSubList> SourceList;
ALuint NumSources{0};
std::mutex SourceLock;
al::vector<EffectSlotSubList> EffectSlotList;
ALuint NumEffectSlots{0u};
std::mutex EffectSlotLock;
std::atomic<ALenum> LastError{AL_NO_ERROR};
DistanceModel mDistanceModel{DistanceModel::Default};
ALboolean SourceDistanceModel{AL_FALSE};
ALfloat DopplerFactor{1.0f};
ALfloat DopplerVelocity{1.0f};
ALfloat SpeedOfSound{};
ALfloat MetersPerUnit{1.0f};
std::atomic_flag PropsClean;
std::atomic<bool> DeferUpdates{false};
std::mutex PropLock;
/* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
* indicates if updates are currently happening).
*/
RefCount UpdateCount{0u};
std::atomic<bool> HoldUpdates{false};
ALfloat GainBoost{1.0f};
std::atomic<ALcontextProps*> Update{nullptr};
/* Linked lists of unused property containers, free to use for future
* updates.
*/
std::atomic<ALcontextProps*> FreeContextProps{nullptr};
std::atomic<ALlistenerProps*> FreeListenerProps{nullptr};
std::atomic<ALvoiceProps*> FreeVoiceProps{nullptr};
std::atomic<ALeffectslotProps*> FreeEffectslotProps{nullptr};
ALvoice **Voices{nullptr};
std::atomic<ALsizei> VoiceCount{0};
ALsizei MaxVoices{0};
using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
std::atomic<ALeffectslotArray*> ActiveAuxSlots{nullptr};
std::thread EventThread;
al::semaphore EventSem;
std::unique_ptr<RingBuffer> AsyncEvents;
std::atomic<ALbitfieldSOFT> EnabledEvts{0u};
std::mutex EventCbLock;
ALEVENTPROCSOFT EventCb{};
void *EventParam{nullptr};
/* Default effect slot */
std::unique_ptr<ALeffectslot> DefaultSlot;
ALCdevice *const Device;
const ALCchar *ExtensionList{nullptr};
std::atomic<ALCcontext*> next{nullptr};
ALlistener Listener{};
ALCcontext(ALCdevice *device);
ALCcontext(const ALCcontext&) = delete;
ALCcontext& operator=(const ALCcontext&) = delete;
~ALCcontext();
static constexpr inline const char *CurrentPrefix() noexcept { return "ALCcontext::"; }
DEF_NEWDEL(ALCcontext)
};
void ALCcontext_DecRef(ALCcontext *context);
void UpdateContextProps(ALCcontext *context);
void ALCcontext_DeferUpdates(ALCcontext *context);
void ALCcontext_ProcessUpdates(ALCcontext *context);
/* Simple RAII context reference. Takes the reference of the provided
* ALCcontext, and decrements it when leaving scope. Movable (transfer
* reference) but not copyable (no new references).
*/
class ContextRef {
ALCcontext *mCtx{nullptr};
void reset() noexcept
{
if(mCtx)
ALCcontext_DecRef(mCtx);
mCtx = nullptr;
}
public:
ContextRef() noexcept = default;
ContextRef(ContextRef&& rhs) noexcept : mCtx{rhs.mCtx}
{ rhs.mCtx = nullptr; }
explicit ContextRef(ALCcontext *ctx) noexcept : mCtx(ctx) { }
~ContextRef() { reset(); }
ContextRef& operator=(const ContextRef&) = delete;
ContextRef& operator=(ContextRef&& rhs) noexcept
{ std::swap(mCtx, rhs.mCtx); return *this; }
operator bool() const noexcept { return mCtx != nullptr; }
ALCcontext* operator->() noexcept { return mCtx; }
ALCcontext* get() noexcept { return mCtx; }
ALCcontext* release() noexcept
{
ALCcontext *ret{mCtx};
mCtx = nullptr;
return ret;
}
};
ContextRef GetContextRef(void);
struct ALcontextProps {
ALfloat DopplerFactor;
ALfloat DopplerVelocity;
ALfloat SpeedOfSound;
ALboolean SourceDistanceModel;
DistanceModel mDistanceModel;
ALfloat MetersPerUnit;
std::atomic<ALcontextProps*> next;
};
#endif /* ALCONTEXT_H */

+ 1340
- 1123
modules/openal-soft/Alc/alu.cpp
File diff suppressed because it is too large
View File


+ 38
- 0
modules/openal-soft/Alc/alu.h View File

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

+ 0
- 119
modules/openal-soft/Alc/ambidefs.h View File

@ -1,119 +0,0 @@
#ifndef AMBIDEFS_H
#define AMBIDEFS_H
#include <array>
/* The maximum number of Ambisonics channels. For a given order (o), the size
* needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second-
* order has 9, third-order has 16, and fourth-order has 25.
*/
#define MAX_AMBI_ORDER 3
constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept
{ return (order+1) * (order+1); }
#define MAX_AMBI_CHANNELS AmbiChannelsFromOrder(MAX_AMBI_ORDER)
/* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up
* to 4th order, which is the highest order a 32-bit mask value can specify (a
* 64-bit mask could handle up to 7th order).
*/
#define AMBI_0ORDER_MASK 0x00000001
#define AMBI_1ORDER_MASK 0x0000000f
#define AMBI_2ORDER_MASK 0x000001ff
#define AMBI_3ORDER_MASK 0x0000ffff
#define AMBI_4ORDER_MASK 0x01ffffff
/* A bitmask of ambisonic channels with height information. If none of these
* channels are used/needed, there's no height (e.g. with most surround sound
* speaker setups). This is ACN ordering, with bit 0 being ACN 0, etc.
*/
#define AMBI_PERIPHONIC_MASK (0xfe7ce4)
/* The maximum number of ambisonic channels for 2D (non-periphonic)
* representation. This is 2 per each order above zero-order, plus 1 for zero-
* order. Or simply, o*2 + 1.
*/
constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept
{ return order*2 + 1; }
#define MAX_AMBI2D_CHANNELS Ambi2DChannelsFromOrder(MAX_AMBI_ORDER)
/* NOTE: These are scale factors as applied to Ambisonics content. Decoder
* coefficients should be divided by these values to get proper scalings.
*/
struct AmbiScale {
static constexpr std::array<float,MAX_AMBI_CHANNELS> FromN3D{{
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
}};
static constexpr std::array<float,MAX_AMBI_CHANNELS> FromSN3D{{
1.000000000f, /* ACN 0, sqrt(1) */
1.732050808f, /* ACN 1, sqrt(3) */
1.732050808f, /* ACN 2, sqrt(3) */
1.732050808f, /* ACN 3, sqrt(3) */
2.236067978f, /* ACN 4, sqrt(5) */
2.236067978f, /* ACN 5, sqrt(5) */
2.236067978f, /* ACN 6, sqrt(5) */
2.236067978f, /* ACN 7, sqrt(5) */
2.236067978f, /* ACN 8, sqrt(5) */
2.645751311f, /* ACN 9, sqrt(7) */
2.645751311f, /* ACN 10, sqrt(7) */
2.645751311f, /* ACN 11, sqrt(7) */
2.645751311f, /* ACN 12, sqrt(7) */
2.645751311f, /* ACN 13, sqrt(7) */
2.645751311f, /* ACN 14, sqrt(7) */
2.645751311f, /* ACN 15, sqrt(7) */
}};
static constexpr std::array<float,MAX_AMBI_CHANNELS> FromFuMa{{
1.414213562f, /* ACN 0 (W), sqrt(2) */
1.732050808f, /* ACN 1 (Y), sqrt(3) */
1.732050808f, /* ACN 2 (Z), sqrt(3) */
1.732050808f, /* ACN 3 (X), sqrt(3) */
1.936491673f, /* ACN 4 (V), sqrt(15)/2 */
1.936491673f, /* ACN 5 (T), sqrt(15)/2 */
2.236067978f, /* ACN 6 (R), sqrt(5) */
1.936491673f, /* ACN 7 (S), sqrt(15)/2 */
1.936491673f, /* ACN 8 (U), sqrt(15)/2 */
2.091650066f, /* ACN 9 (Q), sqrt(35/8) */
1.972026594f, /* ACN 10 (O), sqrt(35)/3 */
2.231093404f, /* ACN 11 (M), sqrt(224/45) */
2.645751311f, /* ACN 12 (K), sqrt(7) */
2.231093404f, /* ACN 13 (L), sqrt(224/45) */
1.972026594f, /* ACN 14 (N), sqrt(35)/3 */
2.091650066f, /* ACN 15 (P), sqrt(35/8) */
}};
};
struct AmbiIndex {
static constexpr std::array<int,MAX_AMBI_CHANNELS> FromFuMa{{
0, /* W */
3, /* X */
1, /* Y */
2, /* Z */
6, /* R */
7, /* S */
5, /* T */
8, /* U */
4, /* V */
12, /* K */
13, /* L */
11, /* M */
14, /* N */
10, /* O */
15, /* P */
9, /* Q */
}};
static constexpr std::array<int,MAX_AMBI_CHANNELS> FromACN{{
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15
}};
static constexpr std::array<int,MAX_AMBI2D_CHANNELS> From2D{{
0, 1,3, 4,8, 9,15
}};
static constexpr std::array<int,MAX_AMBI_CHANNELS> From3D{{
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15
}};
};
#endif /* AMBIDEFS_H */

+ 354
- 371
modules/openal-soft/Alc/backends/alsa.cpp
File diff suppressed because it is too large
View File


+ 3
- 3
modules/openal-soft/Alc/backends/alsa.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_ALSA_H
#define BACKENDS_ALSA_H
#include "backends/base.h"
#include "base.h"
struct AlsaBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 164
- 28
modules/openal-soft/Alc/backends/base.cpp View File

@ -1,58 +1,194 @@
#include "config.h"
#include <cstdlib>
#include "base.h"
#include <thread>
#include <algorithm>
#include <array>
#include <atomic>
#include "alMain.h"
#include "alu.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmreg.h>
#include "backends/base.h"
#include "albit.h"
#include "core/logging.h"
#include "aloptional.h"
#endif
#include "atomic.h"
#include "core/devformat.h"
ClockLatency GetClockLatency(ALCdevice *device)
{
BackendBase *backend{device->Backend.get()};
ClockLatency ret{backend->getClockLatency()};
ret.Latency += device->FixedLatency;
return ret;
}
bool BackendBase::reset()
{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; }
/* BackendBase method implementations. */
BackendBase::BackendBase(ALCdevice *device) noexcept : mDevice{device}
void BackendBase::captureSamples(al::byte*, uint)
{ }
BackendBase::~BackendBase() = default;
ALCboolean BackendBase::reset()
{ return ALC_FALSE; }
ALCenum BackendBase::captureSamples(void* UNUSED(buffer), ALCuint UNUSED(samples))
{ return ALC_INVALID_DEVICE; }
ALCuint BackendBase::availableSamples()
uint BackendBase::availableSamples()
{ return 0; }
ClockLatency BackendBase::getClockLatency()
{
ClockLatency ret;
ALuint refcount;
uint refcount;
do {
while(((refcount=mDevice->MixCount.load(std::memory_order_acquire))&1))
std::this_thread::yield();
refcount = mDevice->waitForMix();
ret.ClockTime = GetDeviceClockTime(mDevice);
std::atomic_thread_fence(std::memory_order_acquire);
} while(refcount != mDevice->MixCount.load(std::memory_order_relaxed));
} while(refcount != ReadRef(mDevice->MixCount));
/* NOTE: The device will generally have about all but one periods filled at
* any given time during playback. Without a more accurate measurement from
* the output, this is an okay approximation.
*/
ret.Latency = std::chrono::seconds{maxi(mDevice->BufferSize-mDevice->UpdateSize, 0)};
ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize},
std::chrono::seconds::zero());
ret.Latency /= mDevice->Frequency;
return ret;
}
void BackendBase::setDefaultWFXChannelOrder()
{
mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
switch(mDevice->FmtChans)
{
case DevFmtMono:
mDevice->RealOut.ChannelIndex[FrontCenter] = 0;
break;
case DevFmtStereo:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
break;
case DevFmtQuad:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3;
break;
case DevFmtX51:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[SideLeft] = 4;
mDevice->RealOut.ChannelIndex[SideRight] = 5;
break;
case DevFmtX61:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[BackCenter] = 4;
mDevice->RealOut.ChannelIndex[SideLeft] = 5;
mDevice->RealOut.ChannelIndex[SideRight] = 6;
break;
case DevFmtX71:
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;
break;
case DevFmtAmbi3D:
break;
}
}
void BackendBase::setDefaultChannelOrder()
{
mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
switch(mDevice->FmtChans)
{
case DevFmtX51:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[SideLeft] = 2;
mDevice->RealOut.ChannelIndex[SideRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
return;
case DevFmtX71:
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;
return;
/* Same as WFX order */
case DevFmtMono:
case DevFmtStereo:
case DevFmtQuad:
case DevFmtX61:
case DevFmtAmbi3D:
setDefaultWFXChannelOrder();
break;
}
}
#ifdef _WIN32
void BackendBase::setChannelOrderFromWFXMask(uint chanmask)
{
static constexpr uint x51{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER
| SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT};
static constexpr uint x51rear{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER
| SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT};
/* Swap a 5.1 mask using the back channels for one with the sides. */
if(chanmask == x51rear) chanmask = x51;
auto get_channel = [](const DWORD chanbit) noexcept -> al::optional<Channel>
{
switch(chanbit)
{
case SPEAKER_FRONT_LEFT: return al::make_optional(FrontLeft);
case SPEAKER_FRONT_RIGHT: return al::make_optional(FrontRight);
case SPEAKER_FRONT_CENTER: return al::make_optional(FrontCenter);
case SPEAKER_LOW_FREQUENCY: return al::make_optional(LFE);
case SPEAKER_BACK_LEFT: return al::make_optional(BackLeft);
case SPEAKER_BACK_RIGHT: return al::make_optional(BackRight);
case SPEAKER_FRONT_LEFT_OF_CENTER: break;
case SPEAKER_FRONT_RIGHT_OF_CENTER: break;
case SPEAKER_BACK_CENTER: return al::make_optional(BackCenter);
case SPEAKER_SIDE_LEFT: return al::make_optional(SideLeft);
case SPEAKER_SIDE_RIGHT: return al::make_optional(SideRight);
case SPEAKER_TOP_CENTER: return al::make_optional(TopCenter);
case SPEAKER_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft);
case SPEAKER_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter);
case SPEAKER_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight);
case SPEAKER_TOP_BACK_LEFT: return al::make_optional(TopBackLeft);
case SPEAKER_TOP_BACK_CENTER: return al::make_optional(TopBackCenter);
case SPEAKER_TOP_BACK_RIGHT: return al::make_optional(TopBackRight);
}
WARN("Unhandled WFX channel bit 0x%lx\n", chanbit);
return al::nullopt;
};
const uint numchans{mDevice->channelsFromFmt()};
uint idx{0};
while(chanmask)
{
const int bit{al::countr_zero(chanmask)};
const uint mask{1u << bit};
chanmask &= ~mask;
if(auto label = get_channel(mask))
{
mDevice->RealOut.ChannelIndex[*label] = idx;
if(++idx == numchans) break;
}
}
}
#endif

+ 81
- 36
modules/openal-soft/Alc/backends/base.h View File

@ -1,68 +1,81 @@
#ifndef ALC_BACKENDS_BASE_H
#define ALC_BACKENDS_BASE_H
#include <memory>
#include <chrono>
#include <cstdarg>
#include <memory>
#include <ratio>
#include <string>
#include <mutex>
#include "alMain.h"
#include "albyte.h"
#include "core/device.h"
#include "core/except.h"
using uint = unsigned int;
struct ClockLatency {
std::chrono::nanoseconds ClockTime;
std::chrono::nanoseconds Latency;
};
/* Helper to get the current clock time from the device's ClockBase, and
* SamplesDone converted from the sample rate.
*/
inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device)
{
using std::chrono::seconds;
using std::chrono::nanoseconds;
auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
return device->ClockBase + ns;
}
ClockLatency GetClockLatency(ALCdevice *device);
struct BackendBase {
virtual ALCenum open(const ALCchar *name) = 0;
virtual void open(const char *name) = 0;
virtual ALCboolean reset();
virtual ALCboolean start() = 0;
virtual bool reset();
virtual void start() = 0;
virtual void stop() = 0;
virtual ALCenum captureSamples(void *buffer, ALCuint samples);
virtual ALCuint availableSamples();
virtual void captureSamples(al::byte *buffer, uint samples);
virtual uint availableSamples();
virtual ClockLatency getClockLatency();
virtual void lock() { mMutex.lock(); }
virtual void unlock() { mMutex.unlock(); }
DeviceBase *const mDevice;
ALCdevice *mDevice;
BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
virtual ~BackendBase() = default;
std::recursive_mutex mMutex;
protected:
/** Sets the default channel order used by most non-WaveFormatEx-based APIs. */
void setDefaultChannelOrder();
/** Sets the default channel order used by WaveFormatEx. */
void setDefaultWFXChannelOrder();
BackendBase(ALCdevice *device) noexcept;
virtual ~BackendBase();
#ifdef _WIN32
/** Sets the channel order given the WaveFormatEx mask. */
void setChannelOrderFromWFXMask(uint chanmask);
#endif
};
using BackendPtr = std::unique_ptr<BackendBase>;
using BackendUniqueLock = std::unique_lock<BackendBase>;
using BackendLockGuard = std::lock_guard<BackendBase>;
enum class BackendType {
Playback,
Capture
};
enum class DevProbe {
Playback,
Capture
};
/* Helper to get the current clock time from the device's ClockBase, and
* SamplesDone converted from the sample rate.
*/
inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device)
{
using std::chrono::seconds;
using std::chrono::nanoseconds;
auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
return device->ClockBase + ns;
}
/* Helper to get the device latency from the backend, including any fixed
* latency from post-processing.
*/
inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
{
ClockLatency ret{backend->getClockLatency()};
ret.Latency += device->FixedLatency;
return ret;
}
struct BackendFactory {
@ -70,9 +83,41 @@ struct BackendFactory {
virtual bool querySupport(BackendType type) = 0;
virtual void probe(DevProbe type, std::string *outnames) = 0;
virtual std::string probe(BackendType type) = 0;
virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
protected:
virtual ~BackendFactory() = default;
};
namespace al {
virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0;
enum class backend_error {
NoDevice,
DeviceError,
OutOfMemory
};
class backend_exception final : public base_exception {
backend_error mErrorCode;
public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
}
backend_error errorCode() const noexcept { return mErrorCode; }
};
} // namespace al
#endif /* ALC_BACKENDS_BASE_H */

+ 554
- 322
modules/openal-soft/Alc/backends/coreaudio.cpp
File diff suppressed because it is too large
View File


+ 3
- 3
modules/openal-soft/Alc/backends/coreaudio.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_COREAUDIO_H
#define BACKENDS_COREAUDIO_H
#include "backends/base.h"
#include "base.h"
struct CoreAudioBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 215
- 307
modules/openal-soft/Alc/backends/dsound.cpp View File

@ -20,7 +20,10 @@
#include "config.h"
#include "backends/dsound.h"
#include "dsound.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
@ -34,16 +37,22 @@
#endif
#include <atomic>
#include <cassert>
#include <thread>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include "alMain.h"
#include "alu.h"
#include "alnumeric.h"
#include "comptr.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "compat.h"
#include "strutils.h"
#include "threads.h"
/* MinGW-w64 needs this for some unknown reason now. */
using LPCWAVEFORMATEX = const WAVEFORMATEX*;
@ -99,6 +108,14 @@ HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallbac
#endif
#define MONO SPEAKER_FRONT_CENTER
#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define MAX_UPDATES 128
struct DevMap {
@ -116,13 +133,12 @@ al::vector CaptureDevices;
bool checkName(const al::vector<DevMap> &list, const std::string &name)
{
return std::find_if(list.cbegin(), list.cend(),
[&name](const DevMap &entry) -> bool
{ return entry.name == name; }
) != list.cend();
auto match_name = [&name](const DevMap &entry) -> bool
{ return entry.name == name; };
return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
}
BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR* UNUSED(drvname), void *data)
BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
{
if(!guid)
return TRUE;
@ -154,44 +170,35 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR* UNUS
struct DSoundPlayback final : public BackendBase {
DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundPlayback() override;
int mixerProc();
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
IDirectSound *mDS{nullptr};
IDirectSoundBuffer *mPrimaryBuffer{nullptr};
IDirectSoundBuffer *mBuffer{nullptr};
IDirectSoundNotify *mNotifies{nullptr};
HANDLE mNotifyEvent{nullptr};
ComPtr<IDirectSound> mDS;
ComPtr<IDirectSoundBuffer> mPrimaryBuffer;
ComPtr<IDirectSoundBuffer> mBuffer;
ComPtr<IDirectSoundNotify> mNotifies;
HANDLE mNotifyEvent{nullptr};
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "DSoundPlayback::"; }
DEF_NEWDEL(DSoundPlayback)
};
DSoundPlayback::~DSoundPlayback()
{
if(mNotifies)
mNotifies->Release();
mNotifies = nullptr;
if(mBuffer)
mBuffer->Release();
mBuffer = nullptr;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
if(mDS)
mDS->Release();
mDS = nullptr;
if(mNotifyEvent)
CloseHandle(mNotifyEvent);
mNotifyEvent = nullptr;
@ -209,18 +216,19 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
if(FAILED(err))
{
ERR("Failed to get buffer caps: 0x%lx\n", err);
aluHandleDisconnect(mDevice, "Failure retrieving playback buffer info: 0x%lx", err);
mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
return 1;
}
ALsizei FrameSize{mDevice->frameSizeFromFmt()};
const size_t FrameStep{mDevice->channelsFromFmt()};
uint FrameSize{mDevice->frameSizeFromFmt()};
DWORD FragSize{mDevice->UpdateSize * FrameSize};
bool Playing{false};
DWORD LastCursor{0u};
mBuffer->GetCurrentPosition(&LastCursor, nullptr);
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
// Get current play cursor
DWORD PlayCursor;
@ -235,7 +243,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
if(FAILED(err))
{
ERR("Failed to play buffer: 0x%lx\n", err);
aluHandleDisconnect(mDevice, "Failure starting playback: 0x%lx", err);
mDevice->handleDisconnect("Failure starting playback: 0x%lx", err);
return 1;
}
Playing = true;
@ -269,18 +277,16 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
if(SUCCEEDED(err))
{
lock();
aluMixData(mDevice, WritePtr1, WriteCnt1/FrameSize);
mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
if(WriteCnt2 > 0)
aluMixData(mDevice, WritePtr2, WriteCnt2/FrameSize);
unlock();
mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
}
else
{
ERR("Buffer lock error: %#lx\n", err);
aluHandleDisconnect(mDevice, "Failed to lock output buffer: 0x%lx", err);
mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
return 1;
}
@ -292,7 +298,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
return 0;
}
ALCenum DSoundPlayback::open(const ALCchar *name)
void DSoundPlayback::open(const char *name)
{
HRESULT hr;
if(PlaybackDevices.empty())
@ -315,64 +321,71 @@ ALCenum DSoundPlayback::open(const ALCchar *name)
else
{
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
[name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
[name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == PlaybackDevices.cend())
return ALC_INVALID_VALUE;
{
GUID id{};
hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
if(SUCCEEDED(hr))
iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
}
guid = &iter->guid;
}
hr = DS_OK;
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(!mNotifyEvent) hr = E_FAIL;
if(!mNotifyEvent)
{
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(!mNotifyEvent) hr = E_FAIL;
}
//DirectSound Init code
ComPtr<IDirectSound> ds;
if(SUCCEEDED(hr))
hr = DirectSoundCreate(guid, &mDS, nullptr);
hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
if(SUCCEEDED(hr))
hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
if(FAILED(hr))
{
ERR("Device init failed: 0x%08lx\n", hr);
return ALC_INVALID_VALUE;
}
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
hr};
mNotifies = nullptr;
mBuffer = nullptr;
mPrimaryBuffer = nullptr;
mDS = std::move(ds);
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean DSoundPlayback::reset()
bool DSoundPlayback::reset()
{
if(mNotifies)
mNotifies->Release();
mNotifies = nullptr;
if(mBuffer)
mBuffer->Release();
mBuffer = nullptr;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
switch(mDevice->FmtType)
{
case DevFmtByte:
mDevice->FmtType = DevFmtUByte;
break;
case DevFmtFloat:
if((mDevice->Flags&DEVICE_SAMPLE_TYPE_REQUEST))
break;
/* fall-through */
case DevFmtUShort:
mDevice->FmtType = DevFmtShort;
break;
case DevFmtUInt:
mDevice->FmtType = DevFmtInt;
break;
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtByte:
mDevice->FmtType = DevFmtUByte;
break;
case DevFmtFloat:
if(mDevice->Flags.test(SampleTypeRequest))
break;
/* fall-through */
case DevFmtUShort:
mDevice->FmtType = DevFmtShort;
break;
case DevFmtUInt:
mDevice->FmtType = DevFmtInt;
break;
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
break;
}
WAVEFORMATEXTENSIBLE OutputType{};
@ -381,7 +394,7 @@ ALCboolean DSoundPlayback::reset()
if(SUCCEEDED(hr))
{
speakers = DSSPEAKER_CONFIG(speakers);
if(!(mDevice->Flags&DEVICE_CHANNELS_REQUEST))
if(!mDevice->Flags.test(ChannelsRequest))
{
if(speakers == DSSPEAKER_MONO)
mDevice->FmtChans = DevFmtMono;
@ -389,81 +402,37 @@ ALCboolean DSoundPlayback::reset()
mDevice->FmtChans = DevFmtStereo;
else if(speakers == DSSPEAKER_QUAD)
mDevice->FmtChans = DevFmtQuad;
else if(speakers == DSSPEAKER_5POINT1_SURROUND)
else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
mDevice->FmtChans = DevFmtX51;
else if(speakers == DSSPEAKER_5POINT1_BACK)
mDevice->FmtChans = DevFmtX51Rear;
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
mDevice->FmtChans = DevFmtX71;
else
ERR("Unknown system speaker config: 0x%lx\n", speakers);
}
mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo &&
speakers == DSSPEAKER_HEADPHONE);
mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
switch(mDevice->FmtChans)
{
case DevFmtMono:
OutputType.dwChannelMask = SPEAKER_FRONT_CENTER;
break;
case DevFmtAmbi3D:
mDevice->FmtChans = DevFmtStereo;
/*fall-through*/
case DevFmtStereo:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT;
break;
case DevFmtQuad:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT;
break;
case DevFmtX51:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT;
break;
case DevFmtX51Rear:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT;
break;
case DevFmtX61:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_CENTER |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT;
break;
case DevFmtX71:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT;
break;
case DevFmtMono: OutputType.dwChannelMask = MONO; break;
case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
/*fall-through*/
case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
}
retry_open:
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = mDevice->channelsFromFmt();
OutputType.Format.wBitsPerSample = mDevice->bytesFromFmt() * 8;
OutputType.Format.nBlockAlign = OutputType.Format.nChannels*OutputType.Format.wBitsPerSample/8;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec*OutputType.Format.nBlockAlign;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
}
@ -477,8 +446,6 @@ retry_open:
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
}
else
@ -488,7 +455,7 @@ retry_open:
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr);
hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
@ -496,19 +463,19 @@ retry_open:
if(SUCCEEDED(hr))
{
ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
if(num_updates > MAX_UPDATES)
num_updates = MAX_UPDATES;
mDevice->BufferSize = mDevice->UpdateSize * num_updates;
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 |
DSBCAPS_GLOBALFOCUS;
DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2
| DSBCAPS_GLOBALFOCUS;
DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
DSBDescription.lpwfxFormat = &OutputType.Format;
hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr);
hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
{
mDevice->FmtType = DevFmtShort;
@ -522,56 +489,46 @@ retry_open:
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
if(SUCCEEDED(hr))
{
auto Notifies = static_cast<IDirectSoundNotify*>(ptr);
mNotifies = Notifies;
mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
assert(num_updates <= MAX_UPDATES);
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
for(ALuint 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].hEventNotify = mNotifyEvent;
}
if(Notifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
hr = E_FAIL;
}
}
if(FAILED(hr))
{
if(mNotifies)
mNotifies->Release();
mNotifies = nullptr;
if(mBuffer)
mBuffer->Release();
mBuffer = nullptr;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
return ALC_FALSE;
return false;
}
ResetEvent(mNotifyEvent);
SetDefaultWFXChannelOrder(mDevice);
setChannelOrderFromWFXMask(OutputType.dwChannelMask);
return ALC_TRUE;
return true;
}
ALCboolean DSoundPlayback::start()
void DSoundPlayback::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: %s\n", e.what());
}
catch(...) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
return ALC_FALSE;
}
void DSoundPlayback::stop()
@ -585,23 +542,22 @@ void DSoundPlayback::stop()
struct DSoundCapture final : public BackendBase {
DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { }
DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundCapture() override;
ALCenum open(const ALCchar *name) override;
ALCboolean start() override;
void open(const char *name) override;
void start() override;
void stop() override;
ALCenum captureSamples(void *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
IDirectSoundCapture *mDSC{nullptr};
IDirectSoundCaptureBuffer *mDSCbuffer{nullptr};
ComPtr<IDirectSoundCapture> mDSC;
ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer;
DWORD mBufferBytes{0u};
DWORD mCursor{0u};
RingBufferPtr mRing;
static constexpr inline const char *CurrentPrefix() noexcept { return "DSoundCapture::"; }
DEF_NEWDEL(DSoundCapture)
};
@ -610,17 +566,13 @@ DSoundCapture::~DSoundCapture()
if(mDSCbuffer)
{
mDSCbuffer->Stop();
mDSCbuffer->Release();
mDSCbuffer = nullptr;
}
if(mDSC)
mDSC->Release();
mDSC = nullptr;
}
ALCenum DSoundCapture::open(const ALCchar *name)
void DSoundCapture::open(const char *name)
{
HRESULT hr;
if(CaptureDevices.empty())
@ -643,91 +595,60 @@ ALCenum DSoundCapture::open(const ALCchar *name)
else
{
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
[name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
[name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == CaptureDevices.cend())
return ALC_INVALID_VALUE;
{
GUID id{};
hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
if(SUCCEEDED(hr))
iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
}
guid = &iter->guid;
}
switch(mDevice->FmtType)
{
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
return ALC_INVALID_ENUM;
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtFloat:
break;
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtFloat:
break;
}
WAVEFORMATEXTENSIBLE InputType{};
switch(mDevice->FmtChans)
{
case DevFmtMono:
InputType.dwChannelMask = SPEAKER_FRONT_CENTER;
break;
case DevFmtStereo:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT;
break;
case DevFmtQuad:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT;
break;
case DevFmtX51:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT;
break;
case DevFmtX51Rear:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT;
break;
case DevFmtX61:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_CENTER |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT;
break;
case DevFmtX71:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT;
break;
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
return ALC_INVALID_ENUM;
case DevFmtMono: InputType.dwChannelMask = MONO; break;
case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
InputType.Format.nChannels = mDevice->channelsFromFmt();
InputType.Format.wBitsPerSample = mDevice->bytesFromFmt() * 8;
InputType.Format.nBlockAlign = InputType.Format.nChannels*InputType.Format.wBitsPerSample/8;
InputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
InputType.Format.wBitsPerSample / 8);
InputType.Format.nSamplesPerSec = mDevice->Frequency;
InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec*InputType.Format.nBlockAlign;
InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
InputType.Format.nBlockAlign;
InputType.Format.cbSize = 0;
InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
if(mDevice->FmtType == DevFmtFloat)
@ -741,7 +662,7 @@ ALCenum DSoundCapture::open(const ALCchar *name)
InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
}
ALuint samples{mDevice->BufferSize};
uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
DSCBUFFERDESC DSCBDescription{};
@ -751,47 +672,34 @@ ALCenum DSoundCapture::open(const ALCchar *name)
DSCBDescription.lpwfxFormat = &InputType.Format;
//DirectSoundCapture Init code
hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr);
hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
if(SUCCEEDED(hr))
mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr);
mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
if(SUCCEEDED(hr))
{
mRing = CreateRingBuffer(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
if(!mRing) hr = DSERR_OUTOFMEMORY;
}
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
if(FAILED(hr))
{
ERR("Device init failed: 0x%08lx\n", hr);
mRing = nullptr;
if(mDSCbuffer)
mDSCbuffer->Release();
mDSCbuffer = nullptr;
if(mDSC)
mDSC->Release();
mDSC = nullptr;
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
hr};
}
mBufferBytes = DSCBDescription.dwBufferBytes;
SetDefaultWFXChannelOrder(mDevice);
setChannelOrderFromWFXMask(InputType.dwChannelMask);
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean DSoundCapture::start()
void DSoundCapture::start()
{
HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
if(FAILED(hr))
{
ERR("start failed: 0x%08lx\n", hr);
aluHandleDisconnect(mDevice, "Failure starting capture: 0x%lx", hr);
return ALC_FALSE;
}
return ALC_TRUE;
throw al::backend_exception{al::backend_error::DeviceError,
"Failure starting capture: 0x%lx", hr};
}
void DSoundCapture::stop()
@ -800,33 +708,30 @@ void DSoundCapture::stop()
if(FAILED(hr))
{
ERR("stop failed: 0x%08lx\n", hr);
aluHandleDisconnect(mDevice, "Failure stopping capture: 0x%lx", hr);
mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
}
}
ALCenum DSoundCapture::captureSamples(void *buffer, ALCuint samples)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
ALCuint DSoundCapture::availableSamples()
uint DSoundCapture::availableSamples()
{
if(!mDevice->Connected.load(std::memory_order_acquire))
return static_cast<ALCuint>(mRing->readSpace());
return static_cast<uint>(mRing->readSpace());
ALsizei FrameSize{mDevice->frameSizeFromFmt()};
DWORD BufferBytes{mBufferBytes};
DWORD LastCursor{mCursor};
const uint FrameSize{mDevice->frameSizeFromFmt()};
const DWORD BufferBytes{mBufferBytes};
const DWORD LastCursor{mCursor};
DWORD ReadCursor;
void *ReadPtr1, *ReadPtr2;
DWORD ReadCnt1, ReadCnt2;
DWORD ReadCursor{};
void *ReadPtr1{}, *ReadPtr2{};
DWORD ReadCnt1{}, ReadCnt2{};
HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
if(SUCCEEDED(hr))
{
DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes};
if(!NumBytes) return static_cast<ALCubyte>(mRing->readSpace());
const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes};
if(!NumBytes) return static_cast<uint>(mRing->readSpace());
hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
}
if(SUCCEEDED(hr))
@ -835,16 +740,16 @@ ALCuint DSoundCapture::availableSamples()
if(ReadPtr2 != nullptr && ReadCnt2 > 0)
mRing->write(ReadPtr2, ReadCnt2/FrameSize);
hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
mCursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes;
mCursor = ReadCursor;
}
if(FAILED(hr))
{
ERR("update failed: 0x%08lx\n", hr);
aluHandleDisconnect(mDevice, "Failure retrieving capture data: 0x%lx", hr);
mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
}
return static_cast<ALCuint>(mRing->readSpace());
return static_cast<uint>(mRing->readSpace());
}
} // namespace
@ -890,14 +795,15 @@ bool DSoundBackendFactory::init()
bool DSoundBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void DSoundBackendFactory::probe(DevProbe type, std::string *outnames)
std::string DSoundBackendFactory::probe(BackendType type)
{
auto add_device = [outnames](const DevMap &entry) -> void
std::string outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames->append(entry.name.c_str(), entry.name.length()+1);
outnames.append(entry.name.c_str(), entry.name.length()+1);
};
/* Initialize COM to prevent name truncation */
@ -905,27 +811,29 @@ void DSoundBackendFactory::probe(DevProbe type, std::string *outnames)
HRESULT hrcom{CoInitialize(nullptr)};
switch(type)
{
case DevProbe::Playback:
PlaybackDevices.clear();
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Playback:
PlaybackDevices.clear();
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case DevProbe::Capture:
CaptureDevices.clear();
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices.clear();
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
if(SUCCEEDED(hrcom))
CoUninitialize();
return outnames;
}
BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new DSoundPlayback{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/dsound.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_DSOUND_H
#define BACKENDS_DSOUND_H
#include "backends/base.h"
#include "base.h"
struct DSoundBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 396
- 220
modules/openal-soft/Alc/backends/jack.cpp View File

@ -20,21 +20,25 @@
#include "config.h"
#include "backends/jack.h"
#include "jack.h"
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <memory.h>
#include <array>
#include <thread>
#include <functional>
#include "alMain.h"
#include "alu.h"
#include "alconfig.h"
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "threads.h"
#include "compat.h"
#include <jack/jack.h>
#include <jack/ringbuffer.h>
@ -42,9 +46,6 @@
namespace {
constexpr ALCchar jackDevice[] = "JACK Default";
#ifdef HAVE_DYNLOAD
#define JACK_FUNCS(MAGIC) \
MAGIC(jack_client_open); \
@ -69,7 +70,7 @@ constexpr ALCchar jackDevice[] = "JACK Default";
void *jack_handle;
#define MAKE_FUNC(f) decltype(f) * p##f
JACK_FUNCS(MAKE_FUNC);
JACK_FUNCS(MAKE_FUNC)
decltype(jack_error_callback) * pjack_error_callback;
#undef MAKE_FUNC
@ -98,11 +99,13 @@ decltype(jack_error_callback) * pjack_error_callback;
#endif
constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
jack_options_t ClientOptions = JackNullOption;
ALCboolean jack_load()
bool jack_load()
{
ALCboolean error = ALC_FALSE;
bool error{false};
#ifdef HAVE_DYNLOAD
if(!jack_handle)
@ -118,14 +121,14 @@ ALCboolean jack_load()
if(!jack_handle)
{
WARN("Failed to load %s\n", JACKLIB);
return ALC_FALSE;
return false;
}
error = ALC_FALSE;
error = false;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
if(p##f == nullptr) { \
error = ALC_TRUE; \
error = true; \
missing_funcs += "\n" #f; \
} \
} while(0)
@ -149,34 +152,165 @@ ALCboolean jack_load()
}
struct JackDeleter {
void operator()(void *ptr) { jack_free(ptr); }
};
using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
struct DeviceEntry {
std::string mName;
std::string mPattern;
};
al::vector<DeviceEntry> PlaybackList;
void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
{
std::remove_reference_t<decltype(list)>{}.swap(list);
if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
{
for(size_t i{0};ports[i];++i)
{
const char *sep{std::strchr(ports[i], ':')};
if(!sep || ports[i] == sep) continue;
const al::span<const char> portdev{ports[i], sep};
auto check_name = [portdev](const DeviceEntry &entry) -> bool
{
const size_t len{portdev.size()};
return entry.mName.length() == len
&& entry.mName.compare(0, len, portdev.data(), len) == 0;
};
if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
continue;
std::string name{portdev.data(), portdev.size()};
list.emplace_back(DeviceEntry{name, name+":"});
const auto &entry = list.back();
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
* generic entry.
*/
if(ports[0] && list.empty())
{
WARN("No device names found in available ports, adding a generic name.\n");
list.emplace_back(DeviceEntry{"JACK", ""});
}
}
if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"))
{
for(size_t strpos{0};strpos < listopt->size();)
{
size_t nextpos{listopt->find(';', strpos)};
size_t seppos{listopt->find('=', strpos)};
if(seppos >= nextpos || seppos == strpos)
{
const std::string entry{listopt->substr(strpos, nextpos-strpos)};
ERR("Invalid device entry: \"%s\"\n", entry.c_str());
if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos;
continue;
}
const al::span<const char> name{listopt->data()+strpos, seppos-strpos};
const al::span<const char> pattern{listopt->data()+(seppos+1),
std::min(nextpos, listopt->size())-(seppos+1)};
/* Check if this custom pattern already exists in the list. */
auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
{
const size_t len{pattern.size()};
return entry.mPattern.length() == len
&& entry.mPattern.compare(0, len, pattern.data(), len) == 0;
};
auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
if(itemmatch != list.end())
{
/* If so, replace the name with this custom one. */
itemmatch->mName.assign(name.data(), name.size());
TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
itemmatch->mPattern.c_str());
}
else
{
/* Otherwise, add a new device entry. */
list.emplace_back(DeviceEntry{std::string{name.data(), name.size()},
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());
}
if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos;
}
}
if(list.size() > 1)
{
/* Rename entries that have matching names, by appending '#2', '#3',
* etc, as needed.
*/
for(auto curitem = list.begin()+1;curitem != list.end();++curitem)
{
auto check_match = [curitem](const DeviceEntry &entry) -> bool
{ return entry.mName == curitem->mName; };
if(std::find_if(list.begin(), curitem, check_match) != curitem)
{
std::string name{curitem->mName};
size_t count{1};
auto check_name = [&name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
do {
name = curitem->mName;
name += " #";
name += std::to_string(++count);
} while(std::find_if(list.begin(), curitem, check_name) != curitem);
curitem->mName = std::move(name);
}
}
}
}
struct JackPlayback final : public BackendBase {
JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~JackPlayback() override;
static int bufferSizeNotifyC(jack_nframes_t numframes, void *arg);
int bufferSizeNotify(jack_nframes_t numframes);
int processRt(jack_nframes_t numframes) noexcept;
static int processRtC(jack_nframes_t numframes, void *arg) noexcept
{ return static_cast<JackPlayback*>(arg)->processRt(numframes); }
static int processC(jack_nframes_t numframes, void *arg);
int process(jack_nframes_t numframes);
int process(jack_nframes_t numframes) noexcept;
static int processC(jack_nframes_t numframes, void *arg) noexcept
{ return static_cast<JackPlayback*>(arg)->process(numframes); }
int mixerProc();
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
ClockLatency getClockLatency() override;
std::string mPortPattern;
jack_client_t *mClient{nullptr};
jack_port_t *mPort[MAX_OUTPUT_CHANNELS]{};
std::array<jack_port_t*,MAX_OUTPUT_CHANNELS> mPort{};
std::mutex mMutex;
std::atomic<bool> mPlaying{false};
bool mRTMixing{false};
RingBufferPtr mRing;
al::semaphore mSem;
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "JackPlayback::"; }
DEF_NEWDEL(JackPlayback)
};
@ -185,111 +319,100 @@ JackPlayback::~JackPlayback()
if(!mClient)
return;
std::for_each(std::begin(mPort), std::end(mPort),
[this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); }
);
std::fill(std::begin(mPort), std::end(mPort), nullptr);
auto unregister_port = [this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); };
std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(nullptr);
jack_client_close(mClient);
mClient = nullptr;
}
int JackPlayback::bufferSizeNotifyC(jack_nframes_t numframes, void *arg)
{ return static_cast<JackPlayback*>(arg)->bufferSizeNotify(numframes); }
int JackPlayback::bufferSizeNotify(jack_nframes_t numframes)
int JackPlayback::processRt(jack_nframes_t numframes) noexcept
{
std::lock_guard<std::mutex> _{mDevice->StateLock};
mDevice->UpdateSize = numframes;
mDevice->BufferSize = numframes*2;
ALuint bufsize{mDevice->UpdateSize};
if(ConfigValueUInt(mDevice->DeviceName.c_str(), "jack", "buffer-size", &bufsize))
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
TRACE("%u / %u buffer\n", mDevice->UpdateSize, mDevice->BufferSize);
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
size_t numchans{0};
for(auto port : mPort)
{
if(!port || numchans == mDevice->RealOut.Buffer.size())
break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
mRing = nullptr;
mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true);
if(!mRing)
if LIKELY(mPlaying.load(std::memory_order_acquire))
mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
else
{
ERR("Failed to reallocate ringbuffer\n");
aluHandleDisconnect(mDevice, "Failed to reallocate %u-sample buffer", bufsize);
auto clear_buf = [numframes](float *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
}
return 0;
}
int JackPlayback::processC(jack_nframes_t numframes, void *arg)
{ return static_cast<JackPlayback*>(arg)->process(numframes); }
int JackPlayback::process(jack_nframes_t numframes)
int JackPlayback::process(jack_nframes_t numframes) noexcept
{
jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS];
ALsizei numchans{0};
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
size_t numchans{0};
for(auto port : mPort)
{
if(!port) break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
auto data = mRing->getReadVector();
jack_nframes_t todo{minu(numframes, data.first.len)};
std::transform(out, out+numchans, out,
[&data,numchans,todo](ALfloat *outbuf) -> ALfloat*
jack_nframes_t total{0};
if LIKELY(mPlaying.load(std::memory_order_acquire))
{
auto data = mRing->getReadVector();
jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))};
auto write_first = [&data,numchans,todo](float *outbuf) -> float*
{
const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.first.buf);
std::generate_n(outbuf, todo,
[&in,numchans]() noexcept -> ALfloat
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));
if(todo > 0)
{
auto write_second = [&data,numchans,todo](float *outbuf) -> float*
{
const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf);
auto deinterlace_input = [&in,numchans]() noexcept -> float
{
ALfloat ret{*in};
float ret{*in};
in += numchans;
return ret;
}
);
data.first.buf += sizeof(ALfloat);
return outbuf + todo;
};
std::generate_n(outbuf, todo, deinterlace_input);
data.second.buf += sizeof(float);
return outbuf + todo;
};
std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second);
total += todo;
}
);
jack_nframes_t total{todo};
todo = minu(numframes-total, data.second.len);
if(todo > 0)
{
std::transform(out, out+numchans, out,
[&data,numchans,todo](ALfloat *outbuf) -> ALfloat*
{
const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.second.buf);
std::generate_n(outbuf, todo,
[&in,numchans]() noexcept -> ALfloat
{
ALfloat ret{*in};
in += numchans;
return ret;
}
);
data.second.buf += sizeof(ALfloat);
return outbuf + todo;
}
);
total += todo;
mRing->readAdvance(total);
mSem.post();
}
mRing->readAdvance(total);
mSem.post();
if(numframes > total)
{
todo = numframes-total;
std::transform(out, out+numchans, out,
[todo](ALfloat *outbuf) -> ALfloat*
{
std::fill_n(outbuf, todo, 0.0f);
return outbuf + todo;
}
);
const jack_nframes_t todo{numframes - total};
auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
}
return 0;
@ -300,110 +423,134 @@ int JackPlayback::mixerProc()
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
lock();
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
const size_t frame_step{mDevice->channelsFromFmt()};
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() < mDevice->UpdateSize)
{
unlock();
mSem.wait();
lock();
continue;
}
auto data = mRing->getWriteVector();
auto todo = static_cast<ALuint>(data.first.len + data.second.len);
size_t todo{data.first.len + data.second.len};
todo -= todo%mDevice->UpdateSize;
ALuint len1{minu(data.first.len, todo)};
ALuint len2{minu(data.second.len, todo-len1)};
const auto len1 = static_cast<uint>(minz(data.first.len, todo));
const auto len2 = static_cast<uint>(minz(data.second.len, todo-len1));
aluMixData(mDevice, data.first.buf, len1);
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(data.first.buf, len1, frame_step);
if(len2 > 0)
aluMixData(mDevice, data.second.buf, len2);
mDevice->renderSamples(data.second.buf, len2, frame_step);
mRing->writeAdvance(todo);
}
unlock();
return 0;
}
ALCenum JackPlayback::open(const ALCchar *name)
void JackPlayback::open(const char *name)
{
if(!name)
name = jackDevice;
else if(strcmp(name, jackDevice) != 0)
return ALC_INVALID_VALUE;
if(!mClient)
{
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
if(mClient == nullptr)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to open client connection: 0x%02x", status};
if((status&JackServerStarted))
TRACE("JACK server started\n");
if((status&JackNameNotUnique))
{
client_name = jack_get_client_name(mClient);
TRACE("Client name not unique, got '%s' instead\n", client_name);
}
}
const char *client_name{"alsoft"};
jack_status_t status;
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
if(mClient == nullptr)
if(PlaybackList.empty())
EnumerateDevices(mClient, PlaybackList);
if(!name && !PlaybackList.empty())
{
ERR("jack_client_open() failed, status = 0x%02x\n", status);
return ALC_INVALID_VALUE;
name = PlaybackList[0].mName.c_str();
mPortPattern = PlaybackList[0].mPattern;
}
if((status&JackServerStarted))
TRACE("JACK server started\n");
if((status&JackNameNotUnique))
else
{
client_name = jack_get_client_name(mClient);
TRACE("Client name not unique, got `%s' instead\n", client_name);
auto check_name = [name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
if(iter == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name?name:""};
mPortPattern = iter->mPattern;
}
jack_set_process_callback(mClient, &JackPlayback::processC, this);
jack_set_buffer_size_callback(mClient, &JackPlayback::bufferSizeNotifyC, this);
mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean JackPlayback::reset()
bool JackPlayback::reset()
{
std::for_each(std::begin(mPort), std::end(mPort),
[this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); }
);
std::fill(std::begin(mPort), std::end(mPort), nullptr);
auto unregister_port = [this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); };
std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(nullptr);
/* Ignore the requested buffer metrics and just keep one JACK-sized buffer
* ready for when requested.
*/
mDevice->Frequency = jack_get_sample_rate(mClient);
mDevice->UpdateSize = jack_get_buffer_size(mClient);
mDevice->BufferSize = mDevice->UpdateSize * 2;
ALuint bufsize{mDevice->UpdateSize};
if(ConfigValueUInt(mDevice->DeviceName.c_str(), "jack", "buffer-size", &bufsize))
if(mRTMixing)
{
/* Assume only two periods when directly mixing. Should try to query
* the total port latency when connected.
*/
mDevice->BufferSize = mDevice->UpdateSize * 2;
}
else
{
const char *devname{mDevice->DeviceName.c_str()};
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
}
/* Force 32-bit float output. */
mDevice->FmtType = DevFmtFloat;
ALsizei numchans{mDevice->channelsFromFmt()};
auto ports_end = std::begin(mPort) + numchans;
auto bad_port = std::find_if_not(std::begin(mPort), ports_end,
[this](jack_port_t *&port) -> bool
{
std::string name{"channel_" + std::to_string(&port - mPort + 1)};
port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
return port != nullptr;
}
);
int port_num{0};
auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
auto bad_port = mPort.begin();
while(bad_port != ports_end)
{
std::string name{"channel_" + std::to_string(++port_num)};
*bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
JackPortIsOutput | JackPortIsTerminal, 0);
if(!*bad_port) break;
++bad_port;
}
if(bad_port != ports_end)
{
ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == std::begin(mPort)) return ALC_FALSE;
ERR("Failed to register enough JACK ports for %s output\n",
DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == mPort.begin()) return false;
if(bad_port == std::begin(mPort)+1)
if(bad_port == mPort.begin()+1)
mDevice->FmtChans = DevFmtMono;
else
{
ports_end = mPort+2;
ports_end = mPort.begin()+2;
while(bad_port != ports_end)
{
jack_port_unregister(mClient, *(--bad_port));
@ -411,78 +558,89 @@ ALCboolean JackPlayback::reset()
}
mDevice->FmtChans = DevFmtStereo;
}
numchans = std::distance(std::begin(mPort), bad_port);
}
mRing = nullptr;
mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true);
if(!mRing)
{
ERR("Failed to allocate ringbuffer\n");
return ALC_FALSE;
}
SetDefaultChannelOrder(mDevice);
setDefaultChannelOrder();
return ALC_TRUE;
return true;
}
ALCboolean JackPlayback::start()
void JackPlayback::start()
{
if(jack_activate(mClient))
{
ERR("Failed to activate client\n");
return ALC_FALSE;
}
throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
const char **ports{jack_get_ports(mClient, nullptr, nullptr,
JackPortIsPhysical|JackPortIsInput)};
if(ports == nullptr)
const char *devname{mDevice->DeviceName.c_str()};
if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
{
ERR("No physical playback ports found\n");
jack_deactivate(mClient);
return ALC_FALSE;
}
std::mismatch(std::begin(mPort), std::end(mPort), ports,
[this](const jack_port_t *port, const char *pname) -> bool
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
JackPortIsInput)};
if(!pnames)
{
if(!port) return false;
if(!pname)
jack_deactivate(mClient);
throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
}
for(size_t i{0};i < al::size(mPort) && mPort[i];++i)
{
if(!pnames[i])
{
ERR("No physical playback port for \"%s\"\n", jack_port_name(port));
return false;
ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i]));
break;
}
if(jack_connect(mClient, jack_port_name(port), pname))
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port),
pname);
return true;
if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i]))
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]),
pnames[i]);
}
);
jack_free(ports);
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
/* Reconfigure buffer metrics in case the server changed it since the reset
* (it won't change again after jack_activate), then allocate the ring
* buffer with the appropriate size.
*/
mDevice->Frequency = jack_get_sample_rate(mClient);
mDevice->UpdateSize = jack_get_buffer_size(mClient);
mDevice->BufferSize = mDevice->UpdateSize * 2;
mRing = nullptr;
if(mRTMixing)
mPlaying.store(true, std::memory_order_release);
else
{
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
try {
mPlaying.store(true, std::memory_order_release);
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
}
catch(std::exception& e) {
jack_deactivate(mClient);
mPlaying.store(false, std::memory_order_release);
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
}
jack_deactivate(mClient);
return ALC_FALSE;
}
void JackPlayback::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mSem.post();
mThread.join();
if(mPlaying.load(std::memory_order_acquire))
{
mKillNow.store(true, std::memory_order_release);
if(mThread.joinable())
{
mSem.post();
mThread.join();
}
jack_deactivate(mClient);
jack_deactivate(mClient);
mPlaying.store(false, std::memory_order_release);
}
}
@ -490,11 +648,10 @@ ClockLatency JackPlayback::getClockLatency()
{
ClockLatency ret;
lock();
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.Latency = std::chrono::seconds{mRing->readSpace()};
ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
unlock();
return ret;
}
@ -515,10 +672,13 @@ bool JackBackendFactory::init()
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
jack_set_error_function(jack_msg_handler);
jack_status_t status;
jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)};
jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
jack_set_error_function(old_error_cb);
if(!client)
{
@ -535,21 +695,37 @@ bool JackBackendFactory::init()
bool JackBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
void JackBackendFactory::probe(DevProbe type, std::string *outnames)
std::string JackBackendFactory::probe(BackendType type)
{
switch(type)
std::string outnames;
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
case DevProbe::Playback:
/* Includes null char. */
outnames->append(jackDevice, sizeof(jackDevice));
break;
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
case DevProbe::Capture:
break;
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
switch(type)
{
case BackendType::Playback:
if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)})
{
EnumerateDevices(client, PlaybackList);
jack_client_close(client);
}
else
WARN("jack_client_open() failed, 0x%02x\n", status);
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:
break;
}
return outnames;
}
BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new JackPlayback{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/jack.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_JACK_H
#define BACKENDS_JACK_H
#include "backends/base.h"
#include "base.h"
struct JackBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 16
- 18
modules/openal-soft/Alc/backends/loopback.cpp View File

@ -20,40 +20,38 @@
#include "config.h"
#include "backends/loopback.h"
#include "loopback.h"
#include "alMain.h"
#include "alu.h"
#include "core/device.h"
namespace {
struct LoopbackBackend final : public BackendBase {
LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { }
LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
DEF_NEWDEL(LoopbackBackend)
};
ALCenum LoopbackBackend::open(const ALCchar *name)
void LoopbackBackend::open(const char *name)
{
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean LoopbackBackend::reset()
bool LoopbackBackend::reset()
{
SetDefaultWFXChannelOrder(mDevice);
return ALC_TRUE;
setDefaultWFXChannelOrder();
return true;
}
ALCboolean LoopbackBackend::start()
{ return ALC_TRUE; }
void LoopbackBackend::start()
{ }
void LoopbackBackend::stop()
{ }
@ -64,13 +62,13 @@ void LoopbackBackend::stop()
bool LoopbackBackendFactory::init()
{ return true; }
bool LoopbackBackendFactory::querySupport(BackendType UNUSED(type))
bool LoopbackBackendFactory::querySupport(BackendType)
{ return true; }
void LoopbackBackendFactory::probe(DevProbe, std::string*)
{ }
std::string LoopbackBackendFactory::probe(BackendType)
{ return std::string{}; }
BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType UNUSED(type))
BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
{ return BackendPtr{new LoopbackBackend{device}}; }
BackendFactory &LoopbackBackendFactory::getFactory()

+ 3
- 3
modules/openal-soft/Alc/backends/loopback.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_LOOPBACK_H
#define BACKENDS_LOOPBACK_H
#include "backends/base.h"
#include "base.h"
struct LoopbackBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 37
- 42
modules/openal-soft/Alc/backends/null.cpp View File

@ -20,20 +20,20 @@
#include "config.h"
#include "backends/null.h"
#include <cstdlib>
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#include "null.h"
#include <exception>
#include <atomic>
#include <chrono>
#include <thread>
#include <cstdint>
#include <cstring>
#include <functional>
#include <thread>
#include "alMain.h"
#include "alu.h"
#include "compat.h"
#include "core/device.h"
#include "almalloc.h"
#include "core/helpers.h"
#include "threads.h"
namespace {
@ -42,23 +42,22 @@ using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
constexpr ALCchar nullDevice[] = "No Output";
constexpr char nullDevice[] = "No Output";
struct NullBackend final : public BackendBase {
NullBackend(ALCdevice *device) noexcept : BackendBase{device} { }
NullBackend(DeviceBase *device) noexcept : BackendBase{device} { }
int mixerProc();
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "NullBackend::"; }
DEF_NEWDEL(NullBackend)
};
@ -71,8 +70,8 @@ int NullBackend::mixerProc()
int64_t done{0};
auto start = std::chrono::steady_clock::now();
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
auto now = std::chrono::steady_clock::now();
@ -85,9 +84,7 @@ int NullBackend::mixerProc()
}
while(avail-done >= mDevice->UpdateSize)
{
lock();
aluMixData(mDevice, nullptr, mDevice->UpdateSize);
unlock();
mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u);
done += mDevice->UpdateSize;
}
@ -108,37 +105,33 @@ int NullBackend::mixerProc()
}
ALCenum NullBackend::open(const ALCchar *name)
void NullBackend::open(const char *name)
{
if(!name)
name = nullDevice;
else if(strcmp(name, nullDevice) != 0)
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean NullBackend::reset()
bool NullBackend::reset()
{
SetDefaultWFXChannelOrder(mDevice);
return ALC_TRUE;
setDefaultWFXChannelOrder();
return true;
}
ALCboolean NullBackend::start()
void NullBackend::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: %s\n", e.what());
}
catch(...) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
return ALC_FALSE;
}
void NullBackend::stop()
@ -157,20 +150,22 @@ bool NullBackendFactory::init()
bool NullBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
void NullBackendFactory::probe(DevProbe type, std::string *outnames)
std::string NullBackendFactory::probe(BackendType type)
{
std::string outnames;
switch(type)
{
case DevProbe::Playback:
/* Includes null char. */
outnames->append(nullDevice, sizeof(nullDevice));
break;
case DevProbe::Capture:
break;
case BackendType::Playback:
/* Includes null char. */
outnames.append(nullDevice, sizeof(nullDevice));
break;
case BackendType::Capture:
break;
}
return outnames;
}
BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new NullBackend{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/null.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_NULL_H
#define BACKENDS_NULL_H
#include "backends/base.h"
#include "base.h"
struct NullBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 384
- 0
modules/openal-soft/Alc/backends/oboe.cpp View File

@ -0,0 +1,384 @@
#include "config.h"
#include "oboe.h"
#include <cassert>
#include <cstring>
#include <stdint.h>
#include "alnumeric.h"
#include "core/device.h"
#include "core/logging.h"
#include "oboe/Oboe.h"
namespace {
constexpr char device_name[] = "Oboe Default";
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
OboePlayback(DeviceBase *device) : BackendBase{device} { }
oboe::ManagedStream mStream;
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
};
oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames)
{
assert(numFrames > 0);
const int32_t numChannels{oboeStream->getChannelCount()};
mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
static_cast<uint32_t>(numChannels));
return oboe::DataCallbackResult::Continue;
}
void OboePlayback::open(const char *name)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
/* Open a basic output stream, just to ensure it can work. */
oboe::ManagedStream stream;
oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->openManagedStream(stream)};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
mDevice->DeviceName = name;
}
bool OboePlayback::reset()
{
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Output);
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
/* Don't let Oboe convert. We should be able to handle anything it gives
* back.
*/
builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
builder.setChannelConversionAllowed(false);
builder.setFormatConversionAllowed(false);
builder.setCallback(this);
if(mDevice->Flags.test(FrequencyRequest))
builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
if(mDevice->Flags.test(ChannelsRequest))
{
/* Only use mono or stereo at user request. There's no telling what
* other counts may be inferred as.
*/
builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
: (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
: oboe::ChannelCount::Unspecified);
}
if(mDevice->Flags.test(SampleTypeRequest))
{
oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
switch(mDevice->FmtType)
{
case DevFmtByte:
case DevFmtUByte:
case DevFmtShort:
case DevFmtUShort:
format = oboe::AudioFormat::I16;
break;
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
format = oboe::AudioFormat::Float;
break;
}
builder.setFormat(format);
}
oboe::Result result{builder.openManagedStream(mStream)};
/* If the format failed, try asking for the defaults. */
while(result == oboe::Result::ErrorInvalidFormat)
{
if(builder.getFormat() != oboe::AudioFormat::Unspecified)
builder.setFormat(oboe::AudioFormat::Unspecified);
else if(builder.getSampleRate() != oboe::kUnspecified)
builder.setSampleRate(oboe::kUnspecified);
else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
builder.setChannelCount(oboe::ChannelCount::Unspecified);
else
break;
result = builder.openManagedStream(mStream);
}
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
mStream->getBufferCapacityInFrames()));
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
{
if(mStream->getChannelCount() >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(mStream->getChannelCount() == 1)
mDevice->FmtChans = DevFmtMono;
else
throw al::backend_exception{al::backend_error::DeviceError,
"Got unhandled channel count: %d", mStream->getChannelCount()};
}
setDefaultWFXChannelOrder();
switch(mStream->getFormat())
{
case oboe::AudioFormat::I16:
mDevice->FmtType = DevFmtShort;
break;
case oboe::AudioFormat::Float:
mDevice->FmtType = DevFmtFloat;
break;
case oboe::AudioFormat::Unspecified:
case oboe::AudioFormat::Invalid:
throw al::backend_exception{al::backend_error::DeviceError,
"Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
}
mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
/* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
* indicating variable updates, but OpenAL should have a reasonable minimum update size set.
* FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
* update size.
*/
mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
static_cast<uint32_t>(mStream->getFramesPerBurst()));
mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
return true;
}
void OboePlayback::start()
{
const oboe::Result result{mStream->start()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
oboe::convertToText(result)};
}
void OboePlayback::stop()
{
oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
}
struct OboeCapture final : public BackendBase {
OboeCapture(DeviceBase *device) : BackendBase{device} { }
oboe::ManagedStream mStream;
std::vector<al::byte> mSamples;
uint mLastAvail{0u};
void open(const char *name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
};
void OboeCapture::open(const char *name)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Input)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
->setChannelConversionAllowed(true)
->setFormatConversionAllowed(true)
->setBufferCapacityInFrames(static_cast<int32_t>(mDevice->BufferSize))
->setSampleRate(static_cast<int32_t>(mDevice->Frequency));
/* Only use mono or stereo at user request. There's no telling what
* other counts may be inferred as.
*/
switch(mDevice->FmtChans)
{
case DevFmtMono:
builder.setChannelCount(oboe::ChannelCount::Mono);
break;
case DevFmtStereo:
builder.setChannelCount(oboe::ChannelCount::Stereo);
break;
case DevFmtQuad:
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
/* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
* use a temp buffer and convert.
*/
switch(mDevice->FmtType)
{
case DevFmtShort:
builder.setFormat(oboe::AudioFormat::I16);
break;
case DevFmtFloat:
builder.setFormat(oboe::AudioFormat::Float);
break;
case DevFmtByte:
case DevFmtUByte:
case DevFmtUShort:
case DevFmtInt:
case DevFmtUInt:
throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
}
oboe::Result result{builder.openManagedStream(mStream)};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
if(static_cast<int32_t>(mDevice->BufferSize) > mStream->getBufferCapacityInFrames())
throw al::backend_exception{al::backend_error::DeviceError,
"Buffer size too large (%u > %d)", mDevice->BufferSize,
mStream->getBufferCapacityInFrames()};
auto buffer_result = mStream->setBufferSizeInFrames(static_cast<int32_t>(mDevice->BufferSize));
if(!buffer_result)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set buffer size: %s", oboe::convertToText(buffer_result.error())};
else if(buffer_result.value() < static_cast<int32_t>(mDevice->BufferSize))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set large enough buffer size (%u > %d)", mDevice->BufferSize,
buffer_result.value()};
mDevice->BufferSize = static_cast<uint>(buffer_result.value());
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
mDevice->DeviceName = name;
}
void OboeCapture::start()
{
const oboe::Result result{mStream->start()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
oboe::convertToText(result)};
}
void OboeCapture::stop()
{
/* Capture any unread samples before stopping. Oboe drops whatever's left
* in the stream.
*/
if(auto availres = mStream->getAvailableFrames())
{
const auto avail = std::max(static_cast<uint>(availres.value()), mLastAvail);
const size_t frame_size{static_cast<uint32_t>(mStream->getBytesPerFrame())};
const size_t pos{mSamples.size()};
mSamples.resize(pos + avail*frame_size);
auto result = mStream->read(&mSamples[pos], availres.value(), 0);
uint got{bool{result} ? static_cast<uint>(result.value()) : 0u};
if(got < avail)
std::fill_n(&mSamples[pos + got*frame_size], (avail-got)*frame_size, al::byte{});
mLastAvail = 0;
}
const oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
}
uint OboeCapture::availableSamples()
{
/* Keep track of the max available frame count, to ensure it doesn't go
* backwards.
*/
if(auto result = mStream->getAvailableFrames())
mLastAvail = std::max(static_cast<uint>(result.value()), mLastAvail);
const auto frame_size = static_cast<uint32_t>(mStream->getBytesPerFrame());
return static_cast<uint>(mSamples.size()/frame_size) + mLastAvail;
}
void OboeCapture::captureSamples(al::byte *buffer, uint samples)
{
const auto frame_size = static_cast<uint>(mStream->getBytesPerFrame());
if(const size_t storelen{mSamples.size()})
{
const auto instore = static_cast<uint>(storelen / frame_size);
const uint tocopy{std::min(samples, instore) * frame_size};
std::copy_n(mSamples.begin(), tocopy, buffer);
mSamples.erase(mSamples.begin(), mSamples.begin() + tocopy);
buffer += tocopy;
samples -= tocopy/frame_size;
if(!samples) return;
}
auto result = mStream->read(buffer, static_cast<int32_t>(samples), 0);
uint got{bool{result} ? static_cast<uint>(result.value()) : 0u};
if(got < samples)
std::fill_n(buffer + got*frame_size, (samples-got)*frame_size, al::byte{});
mLastAvail = std::max(mLastAvail, samples) - samples;
}
} // namespace
bool OboeBackendFactory::init() { return true; }
bool OboeBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string OboeBackendFactory::probe(BackendType type)
{
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
return std::string{device_name, sizeof(device_name)};
}
return std::string{};
}
BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OboePlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new OboeCapture{device}};
return BackendPtr{};
}
BackendFactory &OboeBackendFactory::getFactory()
{
static OboeBackendFactory factory{};
return factory;
}

+ 19
- 0
modules/openal-soft/Alc/backends/oboe.h View File

@ -0,0 +1,19 @@
#ifndef BACKENDS_OBOE_H
#define BACKENDS_OBOE_H
#include "base.h"
struct OboeBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_OBOE_H */

+ 299
- 256
modules/openal-soft/Alc/backends/opensl.cpp View File

@ -21,20 +21,25 @@
#include "config.h"
#include "backends/opensl.h"
#include "opensl.h"
#include <stdlib.h>
#include <jni.h>
#include <new>
#include <array>
#include <cstring>
#include <thread>
#include <functional>
#include "alMain.h"
#include "alu.h"
#include "albit.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "opthelpers.h"
#include "ringbuffer.h"
#include "threads.h"
#include "compat.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
@ -49,109 +54,112 @@ namespace {
#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
constexpr ALCchar opensl_device[] = "OpenSL";
constexpr char opensl_device[] = "OpenSL";
SLuint32 GetChannelMask(DevFmtChannels chans)
constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
{
switch(chans)
{
case DevFmtMono: return SL_SPEAKER_FRONT_CENTER;
case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT;
case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT;
case DevFmtX51: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY|
SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT;
case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY|
SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT;
case DevFmtX61: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY|
SL_SPEAKER_BACK_CENTER|
SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT;
case DevFmtX71: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY|
SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT|
SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT;
case DevFmtAmbi3D:
break;
case DevFmtMono: return SL_SPEAKER_FRONT_CENTER;
case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT;
case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT |
SL_SPEAKER_SIDE_RIGHT;
case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER |
SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
case DevFmtAmbi3D:
break;
}
return 0;
}
#ifdef SL_DATAFORMAT_PCM_EX
SLuint32 GetTypeRepresentation(DevFmtType type)
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
constexpr SLuint32 GetTypeRepresentation(DevFmtType type) noexcept
{
switch(type)
{
case DevFmtUByte:
case DevFmtUShort:
case DevFmtUInt:
return SL_PCM_REPRESENTATION_UNSIGNED_INT;
case DevFmtByte:
case DevFmtShort:
case DevFmtInt:
return SL_PCM_REPRESENTATION_SIGNED_INT;
case DevFmtFloat:
return SL_PCM_REPRESENTATION_FLOAT;
case DevFmtUByte:
case DevFmtUShort:
case DevFmtUInt:
return SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT;
case DevFmtByte:
case DevFmtShort:
case DevFmtInt:
return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
case DevFmtFloat:
return SL_ANDROID_PCM_REPRESENTATION_FLOAT;
}
return 0;
}
#endif
const char *res_str(SLresult result)
constexpr SLuint32 GetByteOrderEndianness() noexcept
{
if(al::endian::native == al::endian::little)
return SL_BYTEORDER_LITTLEENDIAN;
return SL_BYTEORDER_BIGENDIAN;
}
const char *res_str(SLresult result) noexcept
{
switch(result)
{
case SL_RESULT_SUCCESS: return "Success";
case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated";
case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid";
case SL_RESULT_MEMORY_FAILURE: return "Memory failure";
case SL_RESULT_RESOURCE_ERROR: return "Resource error";
case SL_RESULT_RESOURCE_LOST: return "Resource lost";
case SL_RESULT_IO_ERROR: return "I/O error";
case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient";
case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted";
case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported";
case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found";
case SL_RESULT_PERMISSION_DENIED: return "Permission denied";
case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported";
case SL_RESULT_INTERNAL_ERROR: return "Internal error";
case SL_RESULT_UNKNOWN_ERROR: return "Unknown error";
case SL_RESULT_OPERATION_ABORTED: return "Operation aborted";
case SL_RESULT_CONTROL_LOST: return "Control lost";
case SL_RESULT_SUCCESS: return "Success";
case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated";
case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid";
case SL_RESULT_MEMORY_FAILURE: return "Memory failure";
case SL_RESULT_RESOURCE_ERROR: return "Resource error";
case SL_RESULT_RESOURCE_LOST: return "Resource lost";
case SL_RESULT_IO_ERROR: return "I/O error";
case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient";
case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted";
case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported";
case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found";
case SL_RESULT_PERMISSION_DENIED: return "Permission denied";
case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported";
case SL_RESULT_INTERNAL_ERROR: return "Internal error";
case SL_RESULT_UNKNOWN_ERROR: return "Unknown error";
case SL_RESULT_OPERATION_ABORTED: return "Operation aborted";
case SL_RESULT_CONTROL_LOST: return "Control lost";
#ifdef SL_RESULT_READONLY
case SL_RESULT_READONLY: return "ReadOnly";
case SL_RESULT_READONLY: return "ReadOnly";
#endif
#ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED
case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported";
case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported";
#endif
#ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE
case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible";
case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible";
#endif
}
return "Unknown error code";
}
#define PRINTERR(x, s) do { \
if(UNLIKELY((x) != SL_RESULT_SUCCESS)) \
if UNLIKELY((x) != SL_RESULT_SUCCESS) \
ERR("%s: %s\n", (s), res_str((x))); \
} while(0)
struct OpenSLPlayback final : public BackendBase {
OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OpenSLPlayback() override;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context);
void process(SLAndroidSimpleBufferQueueItf bq);
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLPlayback*>(context)->process(bq); }
int mixerProc();
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
ClockLatency getClockLatency() override;
@ -168,12 +176,13 @@ struct OpenSLPlayback final : public BackendBase {
RingBufferPtr mRing{nullptr};
al::semaphore mSem;
ALsizei mFrameSize{0};
std::mutex mMutex;
uint mFrameSize{0};
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "OpenSLPlayback::"; }
DEF_NEWDEL(OpenSLPlayback)
};
@ -195,10 +204,7 @@ OpenSLPlayback::~OpenSLPlayback()
/* this callback handler is called every time a buffer finishes playing */
void OpenSLPlayback::processC(SLAndroidSimpleBufferQueueItf bq, void *context)
{ static_cast<OpenSLPlayback*>(context)->process(bq); }
void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf UNUSED(bq))
void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept
{
/* A note on the ringbuffer usage: The buffer queue seems to hold on to the
* pointer passed to the Enqueue method, rather than copying the audio.
@ -229,12 +235,13 @@ int OpenSLPlayback::mixerProc()
PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
}
lock();
const size_t frame_step{mDevice->channelsFromFmt()};
if(SL_RESULT_SUCCESS != result)
aluHandleDisconnect(mDevice, "Failed to get playback buffer: 0x%08x", result);
mDevice->handleDisconnect("Failed to get playback buffer: 0x%08x", result);
while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() == 0)
{
@ -249,26 +256,28 @@ int OpenSLPlayback::mixerProc()
}
if(SL_RESULT_SUCCESS != result)
{
aluHandleDisconnect(mDevice, "Failed to start platback: 0x%08x", result);
mDevice->handleDisconnect("Failed to start playback: 0x%08x", result);
break;
}
if(mRing->writeSpace() == 0)
{
unlock();
mSem.wait();
lock();
continue;
}
}
std::unique_lock<std::mutex> dlock{mMutex};
auto data = mRing->getWriteVector();
aluMixData(mDevice, data.first.buf, data.first.len*mDevice->UpdateSize);
mDevice->renderSamples(data.first.buf,
static_cast<uint>(data.first.len)*mDevice->UpdateSize, frame_step);
if(data.second.len > 0)
aluMixData(mDevice, data.second.buf, data.second.len*mDevice->UpdateSize);
mDevice->renderSamples(data.second.buf,
static_cast<uint>(data.second.len)*mDevice->UpdateSize, frame_step);
size_t todo{data.first.len + data.second.len};
mRing->writeAdvance(todo);
dlock.unlock();
for(size_t i{0};i < todo;i++)
{
@ -283,7 +292,7 @@ int OpenSLPlayback::mixerProc()
PRINTERR(result, "bufferQueue->Enqueue");
if(SL_RESULT_SUCCESS != result)
{
aluHandleDisconnect(mDevice, "Failed to queue audio: 0x%08x", result);
mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result);
break;
}
@ -291,18 +300,21 @@ int OpenSLPlayback::mixerProc()
data.first.buf += mDevice->UpdateSize*mFrameSize;
}
}
unlock();
return 0;
}
ALCenum OpenSLPlayback::open(const ALCchar *name)
void OpenSLPlayback::open(const char *name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
/* There's only one device, so if it's already open, there's nothing to do. */
if(mEngineObj) return;
// create engine
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
@ -339,21 +351,15 @@ ALCenum OpenSLPlayback::open(const ALCchar *name)
mEngineObj = nullptr;
mEngine = nullptr;
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to initialize OpenSL device: 0x%08x", result};
}
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean OpenSLPlayback::reset()
bool OpenSLPlayback::reset()
{
SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
SLDataLocator_OutputMix loc_outmix;
SLDataSource audioSrc;
SLDataSink audioSnk;
SLInterfaceID ids[2];
SLboolean reqs[2];
SLresult result;
if(mBufferQueueObj)
@ -363,7 +369,7 @@ ALCboolean OpenSLPlayback::reset()
mRing = nullptr;
#if 0
if(!(mDevice->Flags&DEVICE_FREQUENCY_REQUEST))
if(!mDevice->Flags.get<FrequencyRequest>())
{
/* FIXME: Disabled until I figure out how to get the Context needed for
* the getSystemService call.
@ -433,53 +439,74 @@ ALCboolean OpenSLPlayback::reset()
mDevice->FmtChans = DevFmtStereo;
mDevice->FmtType = DevFmtShort;
SetDefaultWFXChannelOrder(mDevice);
setDefaultWFXChannelOrder();
mFrameSize = mDevice->frameSizeFromFmt();
const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
SLDataLocator_OutputMix loc_outmix{};
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
loc_outmix.outputMix = mOutputMix;
SLDataSink audioSnk{};
audioSnk.pLocator = &loc_outmix;
audioSnk.pFormat = nullptr;
SLDataLocator_AndroidSimpleBufferQueue loc_bufq{};
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
#ifdef SL_DATAFORMAT_PCM_EX
SLDataFormat_PCM_EX format_pcm;
format_pcm.formatType = SL_DATAFORMAT_PCM_EX;
format_pcm.numChannels = mDevice->channelsFromFmt();
format_pcm.sampleRate = mDevice->Frequency * 1000;
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
SL_BYTEORDER_BIGENDIAN;
format_pcm.representation = GetTypeRepresentation(mDevice->FmtType);
#else
SLDataFormat_PCM format_pcm;
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = mDevice->channelsFromFmt();
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
SL_BYTEORDER_BIGENDIAN;
#endif
SLDataSource audioSrc{};
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
SLAndroidDataFormat_PCM_EX format_pcm_ex{};
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
format_pcm_ex.numChannels = mDevice->channelsFromFmt();
format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm_ex.endianness = GetByteOrderEndianness();
format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
audioSrc.pLocator = &loc_bufq;
audioSrc.pFormat = &format_pcm;
audioSrc.pFormat = &format_pcm_ex;
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
loc_outmix.outputMix = mOutputMix;
audioSnk.pLocator = &loc_outmix;
audioSnk.pFormat = nullptr;
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
ids.data(), reqs.data());
if(SL_RESULT_SUCCESS != result)
#endif
{
/* Alter sample type according to what SLDataFormat_PCM can support. */
switch(mDevice->FmtType)
{
case DevFmtByte: mDevice->FmtType = DevFmtUByte; break;
case DevFmtUInt: mDevice->FmtType = DevFmtInt; break;
case DevFmtFloat:
case DevFmtUShort: mDevice->FmtType = DevFmtShort; break;
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
break;
}
SLDataFormat_PCM format_pcm{};
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = mDevice->channelsFromFmt();
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm.endianness = GetByteOrderEndianness();
ids[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
reqs[0] = SL_BOOLEAN_TRUE;
ids[1] = SL_IID_ANDROIDCONFIGURATION;
reqs[1] = SL_BOOLEAN_FALSE;
audioSrc.pLocator = &loc_bufq;
audioSrc.pFormat = &format_pcm;
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, COUNTOF(ids),
ids, reqs);
PRINTERR(result, "engine->CreateAudioPlayer");
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
ids.data(), reqs.data());
PRINTERR(result, "engine->CreateAudioPlayer");
}
if(SL_RESULT_SUCCESS == result)
{
/* Set the stream type to "media" (games, music, etc), if possible. */
@ -504,15 +531,8 @@ ALCboolean OpenSLPlayback::reset()
}
if(SL_RESULT_SUCCESS == result)
{
const ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
try {
mRing = CreateRingBuffer(num_updates, mFrameSize*mDevice->UpdateSize, true);
}
catch(std::exception& e) {
ERR("Failed allocating ring buffer %ux%ux%u: %s\n", mDevice->UpdateSize,
num_updates, mFrameSize, e.what());
result = SL_RESULT_MEMORY_FAILURE;
}
const uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true);
}
if(SL_RESULT_SUCCESS != result)
@ -521,13 +541,13 @@ ALCboolean OpenSLPlayback::reset()
VCALL0(mBufferQueueObj,Destroy)();
mBufferQueueObj = nullptr;
return ALC_FALSE;
return false;
}
return ALC_TRUE;
return true;
}
ALCboolean OpenSLPlayback::start()
void OpenSLPlayback::start()
{
mRing->reset();
@ -535,24 +555,23 @@ ALCboolean OpenSLPlayback::start()
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
PRINTERR(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS != result)
return ALC_FALSE;
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
PRINTERR(result, "bufferQueue->RegisterCallback");
if(SL_RESULT_SUCCESS != result) return ALC_FALSE;
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to register callback: 0x%08x", result};
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this);
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
return ALC_FALSE;
}
void OpenSLPlayback::stop()
@ -600,28 +619,28 @@ ClockLatency OpenSLPlayback::getClockLatency()
{
ClockLatency ret;
lock();
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
unlock();
return ret;
}
struct OpenSLCapture final : public BackendBase {
OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { }
OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~OpenSLCapture() override;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context);
void process(SLAndroidSimpleBufferQueueItf bq);
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLCapture*>(context)->process(bq); }
ALCenum open(const ALCchar *name) override;
ALCboolean start() override;
void open(const char *name) override;
void start() override;
void stop() override;
ALCenum captureSamples(void *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
/* engine interfaces */
SLObjectItf mEngineObj{nullptr};
@ -631,11 +650,10 @@ struct OpenSLCapture final : public BackendBase {
SLObjectItf mRecordObj{nullptr};
RingBufferPtr mRing{nullptr};
ALCuint mSplOffset{0u};
uint mSplOffset{0u};
ALsizei mFrameSize{0};
uint mFrameSize{0};
static constexpr inline const char *CurrentPrefix() noexcept { return "OpenSLCapture::"; }
DEF_NEWDEL(OpenSLCapture)
};
@ -652,22 +670,20 @@ OpenSLCapture::~OpenSLCapture()
}
void OpenSLCapture::processC(SLAndroidSimpleBufferQueueItf bq, void *context)
{ static_cast<OpenSLCapture*>(context)->process(bq); }
void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf UNUSED(bq))
void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept
{
/* A new chunk has been written into the ring buffer, advance it. */
mRing->writeAdvance(1);
}
ALCenum OpenSLCapture::open(const ALCchar* name)
void OpenSLCapture::open(const char* name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PRINTERR(result, "slCreateEngine");
@ -685,27 +701,21 @@ ALCenum OpenSLCapture::open(const ALCchar* name)
{
mFrameSize = mDevice->frameSizeFromFmt();
/* Ensure the total length is at least 100ms */
ALsizei length{maxi(mDevice->BufferSize, mDevice->Frequency/10)};
uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
/* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
ALsizei update_len{clampi(mDevice->BufferSize/3, mDevice->Frequency/100,
uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
mDevice->Frequency/100*5)};
ALsizei num_updates{(length+update_len-1) / update_len};
uint num_updates{(length+update_len-1) / update_len};
try {
mRing = CreateRingBuffer(num_updates, update_len*mFrameSize, false);
mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
mDevice->UpdateSize = update_len;
mDevice->BufferSize = mRing->writeSpace() * update_len;
}
catch(std::exception& e) {
ERR("Failed to allocate ring buffer: %s\n", e.what());
result = SL_RESULT_MEMORY_FAILURE;
}
mDevice->UpdateSize = update_len;
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace() * update_len);
}
if(SL_RESULT_SUCCESS == result)
{
const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION };
const SLboolean reqs[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
SLDataLocator_IODevice loc_dev{};
loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
@ -721,34 +731,47 @@ ALCenum OpenSLCapture::open(const ALCchar* name)
loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
#ifdef SL_DATAFORMAT_PCM_EX
SLDataFormat_PCM_EX format_pcm{};
format_pcm.formatType = SL_DATAFORMAT_PCM_EX;
format_pcm.numChannels = mDevice->channelsFromFmt();
format_pcm.sampleRate = mDevice->Frequency * 1000;
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
format_pcm.representation = GetTypeRepresentation(mDevice->FmtType);
#else
SLDataFormat_PCM format_pcm{};
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = mDevice->channelsFromFmt();
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
#endif
SLDataSink audioSnk{};
audioSnk.pLocator = &loc_bq;
audioSnk.pFormat = &format_pcm;
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
SLAndroidDataFormat_PCM_EX format_pcm_ex{};
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
format_pcm_ex.numChannels = mDevice->channelsFromFmt();
format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm_ex.endianness = GetByteOrderEndianness();
format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
audioSnk.pLocator = &loc_bq;
audioSnk.pFormat = &format_pcm_ex;
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
COUNTOF(ids), ids, reqs);
PRINTERR(result, "engine->CreateAudioRecorder");
ids.size(), ids.data(), reqs.data());
if(SL_RESULT_SUCCESS != result)
#endif
{
/* Fallback to SLDataFormat_PCM only if it supports the desired
* sample type.
*/
if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtShort
|| mDevice->FmtType == DevFmtInt)
{
SLDataFormat_PCM format_pcm{};
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = mDevice->channelsFromFmt();
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm.endianness = GetByteOrderEndianness();
audioSnk.pLocator = &loc_bq;
audioSnk.pFormat = &format_pcm;
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
ids.size(), ids.data(), reqs.data());
}
PRINTERR(result, "engine->CreateAudioRecorder");
}
}
if(SL_RESULT_SUCCESS == result)
{
@ -786,9 +809,12 @@ ALCenum OpenSLCapture::open(const ALCchar* name)
}
if(SL_RESULT_SUCCESS == result)
{
const ALuint chunk_size{mDevice->UpdateSize * mFrameSize};
const uint chunk_size{mDevice->UpdateSize * mFrameSize};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
auto data = mRing->getWriteVector();
std::fill_n(data.first.buf, data.first.len*chunk_size, silence);
std::fill_n(data.second.buf, data.second.len*chunk_size, silence);
for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
@ -812,14 +838,14 @@ ALCenum OpenSLCapture::open(const ALCchar* name)
mEngineObj = nullptr;
mEngine = nullptr;
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to initialize OpenSL device: 0x%08x", result};
}
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean OpenSLCapture::start()
void OpenSLCapture::start()
{
SLRecordItf record;
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
@ -830,14 +856,9 @@ ALCboolean OpenSLCapture::start()
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
PRINTERR(result, "record->SetRecordState");
}
if(SL_RESULT_SUCCESS != result)
{
aluHandleDisconnect(mDevice, "Failed to start capture: 0x%08x", result);
return ALC_FALSE;
}
return ALC_TRUE;
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start capture: 0x%08x", result};
}
void OpenSLCapture::stop()
@ -853,58 +874,78 @@ void OpenSLCapture::stop()
}
}
ALCenum OpenSLCapture::captureSamples(void* buffer, ALCuint samples)
void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
{
ALsizei chunk_size = mDevice->UpdateSize * mFrameSize;
SLAndroidSimpleBufferQueueItf bufferQueue;
SLresult result;
ALCuint i;
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
PRINTERR(result, "recordObj->GetInterface");
const uint update_size{mDevice->UpdateSize};
const uint chunk_size{update_size * mFrameSize};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
/* Read the desired samples from the ring buffer then advance its read
* pointer.
*/
auto data = mRing->getReadVector();
for(i = 0;i < samples;)
size_t adv_count{0};
auto rdata = mRing->getReadVector();
for(uint i{0};i < samples;)
{
ALCuint rem{minu(samples - i, mDevice->UpdateSize - mSplOffset)};
memcpy((ALCbyte*)buffer + i*mFrameSize, data.first.buf + mSplOffset*mFrameSize,
rem * mFrameSize);
const uint rem{minu(samples - i, update_size - mSplOffset)};
std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
buffer + i*size_t{mFrameSize});
mSplOffset += rem;
if(mSplOffset == mDevice->UpdateSize)
if(mSplOffset == update_size)
{
/* Finished a chunk, reset the offset and advance the read pointer. */
mSplOffset = 0;
mRing->readAdvance(1);
result = VCALL(bufferQueue,Enqueue)(data.first.buf, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
if(SL_RESULT_SUCCESS != result) break;
data.first.len--;
if(!data.first.len)
data.first = data.second;
++adv_count;
rdata.first.len -= 1;
if(!rdata.first.len)
rdata.first = rdata.second;
else
data.first.buf += chunk_size;
rdata.first.buf += chunk_size;
}
i += rem;
}
mRing->readAdvance(adv_count);
if(SL_RESULT_SUCCESS != result)
SLAndroidSimpleBufferQueueItf bufferQueue{};
if LIKELY(mDevice->Connected.load(std::memory_order_acquire))
{
aluHandleDisconnect(mDevice, "Failed to update capture buffer: 0x%08x", result);
return ALC_INVALID_DEVICE;
const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "recordObj->GetInterface");
if UNLIKELY(SL_RESULT_SUCCESS != result)
{
mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result);
bufferQueue = nullptr;
}
}
return ALC_NO_ERROR;
if LIKELY(bufferQueue)
{
SLresult result{SL_RESULT_SUCCESS};
auto wdata = mRing->getWriteVector();
std::fill_n(wdata.first.buf, wdata.first.len*chunk_size, silence);
for(size_t i{0u};i < wdata.first.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(wdata.first.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
}
if(wdata.second.len > 0)
{
std::fill_n(wdata.second.buf, wdata.second.len*chunk_size, silence);
for(size_t i{0u};i < wdata.second.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
}
}
}
}
ALCuint OpenSLCapture::availableSamples()
{ return mRing->readSpace()*mDevice->UpdateSize - mSplOffset; }
uint OpenSLCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); }
} // namespace
@ -913,19 +954,21 @@ bool OSLBackendFactory::init() { return true; }
bool OSLBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void OSLBackendFactory::probe(DevProbe type, std::string *outnames)
std::string OSLBackendFactory::probe(BackendType type)
{
std::string outnames;
switch(type)
{
case DevProbe::Playback:
case DevProbe::Capture:
/* Includes null char. */
outnames->append(opensl_device, sizeof(opensl_device));
break;
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(opensl_device, sizeof(opensl_device));
break;
}
return outnames;
}
BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OpenSLPlayback{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/opensl.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_OSL_H
#define BACKENDS_OSL_H
#include "backends/base.h"
#include "base.h"
struct OSLBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 193
- 254
modules/openal-soft/Alc/backends/oss.cpp View File

@ -20,34 +20,38 @@
#include "config.h"
#include "backends/oss.h"
#include "oss.h"
#include <fcntl.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <memory.h>
#include <unistd.h>
#include <cerrno>
#include <poll.h>
#include <cmath>
#include <atomic>
#include <thread>
#include <vector>
#include <string>
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <exception>
#include <functional>
#include "alMain.h"
#include "alu.h"
#include "alconfig.h"
#include <memory>
#include <new>
#include <string>
#include <thread>
#include <utility>
#include "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "compat.h"
#include "threads.h"
#include "vector.h"
#include <sys/soundcard.h>
@ -80,27 +84,14 @@
namespace {
constexpr char DefaultName[] = "OSS Default";
const char *DefaultPlayback{"/dev/dsp"};
const char *DefaultCapture{"/dev/dsp"};
std::string DefaultPlayback{"/dev/dsp"};
std::string DefaultCapture{"/dev/dsp"};
struct DevMap {
std::string name;
std::string device_name;
template<typename StrT0, typename StrT1>
DevMap(StrT0&& name_, StrT1&& devname_)
: name{std::forward<StrT0>(name_)}, device_name{std::forward<StrT1>(devname_)}
{ }
};
bool checkName(const al::vector<DevMap> &list, const std::string &name)
{
return std::find_if(list.cbegin(), list.cend(),
[&name](const DevMap &entry) -> bool
{ return entry.name == name; }
) != list.cend();
}
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
@ -109,60 +100,59 @@ al::vector CaptureDevices;
#define DSP_CAP_OUTPUT 0x00020000
#define DSP_CAP_INPUT 0x00010000
void ALCossListPopulate(al::vector<DevMap> *devlist, int type)
void ALCossListPopulate(al::vector<DevMap> &devlist, int type)
{
devlist->emplace_back(DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback);
devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
}
#else
void ALCossListAppend(al::vector<DevMap> *list, const char *handle, size_t hlen, const char *path, size_t plen)
void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path)
{
#ifdef ALC_OSS_DEVNODE_TRUC
for(size_t i{0};i < plen;i++)
for(size_t i{0};i < path.size();++i)
{
if(path[i] == '.')
if(path[i] == '.' && handle.size() + i >= path.size())
{
if(strncmp(path + i, handle + hlen + i - plen, plen - i) == 0)
hlen = hlen + i - plen;
plen = i;
const size_t hoffset{handle.size() + i - path.size()};
if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0)
handle = handle.first(hoffset);
path = path.first(i);
}
}
#endif
if(handle[0] == '\0')
{
if(handle.empty())
handle = path;
hlen = plen;
}
std::string basename{handle, hlen};
basename.erase(std::find(basename.begin(), basename.end(), '\0'), basename.end());
std::string devname{path, plen};
devname.erase(std::find(devname.begin(), devname.end(), '\0'), devname.end());
std::string basename{handle.data(), handle.size()};
std::string devname{path.data(), path.size()};
auto iter = std::find_if(list->cbegin(), list->cend(),
[&devname](const DevMap &entry) -> bool
{ return entry.device_name == devname; }
);
if(iter != list->cend())
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())
return;
auto checkName = [&list](const std::string &name) -> bool
{
auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
};
int count{1};
std::string newname{basename};
while(checkName(PlaybackDevices, newname))
while(checkName(newname))
{
newname = basename;
newname += " #";
newname += std::to_string(++count);
}
list->emplace_back(std::move(newname), std::move(devname));
const DevMap &entry = list->back();
list.emplace_back(DevMap{std::move(newname), std::move(devname)});
const DevMap &entry = list.back();
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(al::vector<DevMap> &devlist, int type_flag)
{
int fd{open("/dev/mixer", O_RDONLY)};
if(fd < 0)
@ -190,21 +180,14 @@ void ALCossListPopulate(al::vector *devlist, int type_flag)
if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
continue;
const char *handle;
size_t len;
al::span<const char> handle;
if(ai.handle[0] != '\0')
{
len = strnlen(ai.handle, sizeof(ai.handle));
handle = ai.handle;
}
handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
else
{
len = strnlen(ai.name, sizeof(ai.name));
handle = ai.name;
}
handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
ALCossListAppend(devlist, handle, len, ai.devnode,
strnlen(ai.devnode, sizeof(ai.devnode)));
ALCossListAppend(devlist, handle, devnode);
}
done:
@ -212,28 +195,28 @@ done:
close(fd);
fd = -1;
const char *defdev{(type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback};
auto iter = std::find_if(devlist->cbegin(), devlist->cend(),
const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
[defdev](const DevMap &entry) -> bool
{ return entry.device_name == defdev; }
);
if(iter == devlist->cend())
devlist->insert(devlist->begin(), DevMap{DefaultName, defdev});
if(iter == devlist.cend())
devlist.insert(devlist.begin(), DevMap{DefaultName, defdev});
else
{
DevMap entry{std::move(*iter)};
devlist->erase(iter);
devlist->insert(devlist->begin(), std::move(entry));
devlist.erase(iter);
devlist.insert(devlist.begin(), std::move(entry));
}
devlist->shrink_to_fit();
devlist.shrink_to_fit();
}
#endif
int log2i(ALCuint x)
uint log2i(uint x)
{
int y = 0;
while (x > 1)
uint y{0};
while(x > 1)
{
x >>= 1;
y++;
@ -243,31 +226,30 @@ int log2i(ALCuint x)
struct OSSPlayback final : public BackendBase {
OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OSSPlayback() override;
int mixerProc();
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
int mFd{-1};
al::vector<ALubyte> mMixData;
al::vector<al::byte> mMixData;
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "OSSPlayback::"; }
DEF_NEWDEL(OSSPlayback)
};
OSSPlayback::~OSSPlayback()
{
if(mFd != -1)
close(mFd);
::close(mFd);
mFd = -1;
}
@ -277,25 +259,23 @@ int OSSPlayback::mixerProc()
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const int frame_size{mDevice->frameSizeFromFmt()};
const size_t frame_step{mDevice->channelsFromFmt()};
const size_t frame_size{mDevice->frameSizeFromFmt()};
lock();
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = mFd;
pollitem.events = POLLOUT;
unlock();
int pret{poll(&pollitem, 1, 1000)};
lock();
if(pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed waiting for playback buffer: %s", strerror(errno));
mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
break;
}
else if(pret == 0)
@ -304,9 +284,9 @@ int OSSPlayback::mixerProc()
continue;
}
ALubyte *write_ptr{mMixData.data()};
al::byte *write_ptr{mMixData.data()};
size_t to_write{mMixData.size()};
aluMixData(mDevice, write_ptr, to_write/frame_size);
mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
@ -315,63 +295,54 @@ int OSSPlayback::mixerProc()
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue;
ERR("write failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed writing playback samples: %s",
strerror(errno));
mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
break;
}
to_write -= wrote;
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
}
}
unlock();
return 0;
}
ALCenum OSSPlayback::open(const ALCchar *name)
void OSSPlayback::open(const char *name)
{
const char *devname{DefaultPlayback};
const char *devname{DefaultPlayback.c_str()};
if(!name)
name = DefaultName;
else
{
if(PlaybackDevices.empty())
ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT);
ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
[&name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
if(iter == PlaybackDevices.cend())
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
devname = iter->device_name.c_str();
}
mFd = ::open(devname, O_WRONLY);
if(mFd == -1)
{
ERR("Could not open %s: %s\n", devname, strerror(errno));
return ALC_INVALID_VALUE;
}
int fd{::open(devname, O_WRONLY)};
if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
if(mFd != -1)
::close(mFd);
mFd = fd;
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean OSSPlayback::reset()
bool OSSPlayback::reset()
{
int numFragmentsLogSize;
int log2FragmentSize;
unsigned int periods;
audio_buf_info info;
ALuint frameSize;
int numChannels;
int ossFormat;
int ossSpeed;
const char *err;
int ossFormat{};
switch(mDevice->FmtType)
{
case DevFmtByte:
@ -391,14 +362,16 @@ ALCboolean OSSPlayback::reset()
break;
}
periods = mDevice->BufferSize / mDevice->UpdateSize;
numChannels = mDevice->channelsFromFmt();
ossSpeed = mDevice->Frequency;
frameSize = numChannels * mDevice->bytesFromFmt();
uint periods{mDevice->BufferSize / mDevice->UpdateSize};
uint numChannels{mDevice->channelsFromFmt()};
uint ossSpeed{mDevice->Frequency};
uint frameSize{numChannels * mDevice->bytesFromFmt()};
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
log2FragmentSize = maxi(log2i(mDevice->UpdateSize*frameSize), 4);
numFragmentsLogSize = (periods << 16) | log2FragmentSize;
uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
const char *err;
#define CHECKERR(func) if((func) < 0) { \
err = #func; \
goto err; \
@ -414,7 +387,7 @@ ALCboolean OSSPlayback::reset()
{
err:
ERR("%s failed: %s\n", err, strerror(errno));
return ALC_FALSE;
return false;
}
#undef CHECKERR
@ -422,7 +395,7 @@ ALCboolean OSSPlayback::reset()
{
ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
numChannels);
return ALC_FALSE;
return false;
}
if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) ||
@ -431,33 +404,30 @@ ALCboolean OSSPlayback::reset()
{
ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType),
ossFormat);
return ALC_FALSE;
return false;
}
mDevice->Frequency = ossSpeed;
mDevice->UpdateSize = info.fragsize / frameSize;
mDevice->BufferSize = info.fragments * mDevice->UpdateSize;
mDevice->UpdateSize = static_cast<uint>(info.fragsize) / frameSize;
mDevice->BufferSize = static_cast<uint>(info.fragments) * mDevice->UpdateSize;
SetDefaultChannelOrder(mDevice);
setDefaultChannelOrder();
mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
return ALC_TRUE;
return true;
}
ALCboolean OSSPlayback::start()
void OSSPlayback::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
return ALC_FALSE;
}
void OSSPlayback::stop()
@ -472,16 +442,16 @@ void OSSPlayback::stop()
struct OSScapture final : public BackendBase {
OSScapture(ALCdevice *device) noexcept : BackendBase{device} { }
OSScapture(DeviceBase *device) noexcept : BackendBase{device} { }
~OSScapture() override;
int recordProc();
ALCenum open(const ALCchar *name) override;
ALCboolean start() override;
void open(const char *name) override;
void start() override;
void stop() override;
ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
int mFd{-1};
@ -490,7 +460,6 @@ struct OSScapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "OSScapture::"; }
DEF_NEWDEL(OSScapture)
};
@ -507,7 +476,7 @@ int OSScapture::recordProc()
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
const int frame_size{mDevice->frameSizeFromFmt()};
const size_t frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire))
{
pollfd pollitem{};
@ -520,7 +489,7 @@ int OSScapture::recordProc()
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed to check capture samples: %s", strerror(errno));
mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno));
break;
}
else if(sret == 0)
@ -536,11 +505,10 @@ int OSScapture::recordProc()
if(amt < 0)
{
ERR("read failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed reading capture samples: %s",
strerror(errno));
mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
break;
}
mRing->writeAdvance(amt/frame_size);
mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
}
}
@ -548,128 +516,98 @@ int OSScapture::recordProc()
}
ALCenum OSScapture::open(const ALCchar *name)
void OSScapture::open(const char *name)
{
const char *devname{DefaultCapture};
const char *devname{DefaultCapture.c_str()};
if(!name)
name = DefaultName;
else
{
if(CaptureDevices.empty())
ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT);
ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
[&name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
if(iter == CaptureDevices.cend())
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
devname = iter->device_name.c_str();
}
mFd = ::open(devname, O_RDONLY);
if(mFd == -1)
{
ERR("Could not open %s: %s\n", devname, strerror(errno));
return ALC_INVALID_VALUE;
}
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
int ossFormat{};
switch(mDevice->FmtType)
{
case DevFmtByte:
ossFormat = AFMT_S8;
break;
case DevFmtUByte:
ossFormat = AFMT_U8;
break;
case DevFmtShort:
ossFormat = AFMT_S16_NE;
break;
case DevFmtUShort:
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
return ALC_INVALID_VALUE;
case DevFmtByte:
ossFormat = AFMT_S8;
break;
case DevFmtUByte:
ossFormat = AFMT_U8;
break;
case DevFmtShort:
ossFormat = AFMT_S16_NE;
break;
case DevFmtUShort:
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
}
int periods{4};
int numChannels{mDevice->channelsFromFmt()};
int frameSize{numChannels * mDevice->bytesFromFmt()};
int ossSpeed{static_cast<int>(mDevice->Frequency)};
int log2FragmentSize{log2i(mDevice->BufferSize * frameSize / periods)};
uint periods{4};
uint numChannels{mDevice->channelsFromFmt()};
uint frameSize{numChannels * mDevice->bytesFromFmt()};
uint ossSpeed{mDevice->Frequency};
/* according to the OSS spec, 16 bytes are the minimum */
log2FragmentSize = std::max(log2FragmentSize, 4);
int numFragmentsLogSize{(periods << 16) | log2FragmentSize};
uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info;
const char *err;
audio_buf_info info{};
#define CHECKERR(func) if((func) < 0) { \
err = #func; \
goto err; \
throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \
strerror(errno)}; \
}
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info));
if(0)
{
err:
ERR("%s failed: %s\n", err, strerror(errno));
close(mFd);
mFd = -1;
return ALC_INVALID_VALUE;
}
#undef CHECKERR
if(mDevice->channelsFromFmt() != numChannels)
{
ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
numChannels);
close(mFd);
mFd = -1;
return ALC_INVALID_VALUE;
}
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans),
numChannels};
if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) ||
(ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) ||
(ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
{
ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType), ossFormat);
close(mFd);
mFd = -1;
return ALC_INVALID_VALUE;
}
if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte)
|| (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte)
|| (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType),
ossFormat};
mRing = CreateRingBuffer(mDevice->BufferSize, frameSize, false);
if(!mRing)
{
ERR("Ring buffer create failed\n");
close(mFd);
mFd = -1;
return ALC_OUT_OF_MEMORY;
}
mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean OSScapture::start()
void OSScapture::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Could not create record thread: %s\n", e.what());
}
catch(...) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording thread: %s", e.what()};
}
return ALC_FALSE;
}
void OSScapture::stop()
@ -682,14 +620,11 @@ void OSScapture::stop()
ERR("Error resetting device: %s\n", strerror(errno));
}
ALCenum OSScapture::captureSamples(ALCvoid *buffer, ALCuint samples)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
void OSScapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
ALCuint OSScapture::availableSamples()
{ return mRing->readSpace(); }
uint OSScapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
} // namespace
@ -702,8 +637,10 @@ BackendFactory &OSSBackendFactory::getFactory()
bool OSSBackendFactory::init()
{
ConfigValueStr(nullptr, "oss", "device", &DefaultPlayback);
ConfigValueStr(nullptr, "oss", "capture", &DefaultCapture);
if(auto devopt = ConfigValueStr(nullptr, "oss", "device"))
DefaultPlayback = std::move(*devopt);
if(auto capopt = ConfigValueStr(nullptr, "oss", "capture"))
DefaultCapture = std::move(*capopt);
return true;
}
@ -711,37 +648,39 @@ bool OSSBackendFactory::init()
bool OSSBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void OSSBackendFactory::probe(DevProbe type, std::string *outnames)
std::string OSSBackendFactory::probe(BackendType type)
{
auto add_device = [outnames](const DevMap &entry) -> void
std::string outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
#ifdef HAVE_STAT
struct stat buf;
if(stat(entry.device_name.c_str(), &buf) == 0)
#endif
{
/* Includes null char. */
outnames->append(entry.name.c_str(), entry.name.length()+1);
outnames.append(entry.name.c_str(), entry.name.length()+1);
}
};
switch(type)
{
case DevProbe::Playback:
PlaybackDevices.clear();
ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT);
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case DevProbe::Capture:
CaptureDevices.clear();
ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT);
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
case BackendType::Playback:
PlaybackDevices.clear();
ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices.clear();
ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
return outnames;
}
BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr OSSBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OSSPlayback{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/oss.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_OSS_H
#define BACKENDS_OSS_H
#include "backends/base.h"
#include "base.h"
struct OSSBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 2008
- 0
modules/openal-soft/Alc/backends/pipewire.cpp
File diff suppressed because it is too large
View File


+ 23
- 0
modules/openal-soft/Alc/backends/pipewire.h View File

@ -0,0 +1,23 @@
#ifndef BACKENDS_PIPEWIRE_H
#define BACKENDS_PIPEWIRE_H
#include <string>
#include "base.h"
struct DeviceBase;
struct PipeWireBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_PIPEWIRE_H */

+ 137
- 155
modules/openal-soft/Alc/backends/portaudio.cpp View File

@ -20,24 +20,25 @@
#include "config.h"
#include "backends/portaudio.h"
#include "portaudio.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "alMain.h"
#include "alu.h"
#include "alconfig.h"
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "compat.h"
#include <portaudio.h>
namespace {
constexpr ALCchar pa_device[] = "PortAudio Default";
constexpr char pa_device[] = "PortAudio Default";
#ifdef HAVE_DYNLOAD
@ -71,25 +72,28 @@ MAKE_FUNC(Pa_GetStreamInfo);
struct PortPlayback final : public BackendBase {
PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~PortPlayback() override;
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData);
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags);
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams{};
ALuint mUpdateSize{0u};
uint mUpdateSize{0u};
static constexpr inline const char *CurrentPrefix() noexcept { return "PortPlayback::"; }
DEF_NEWDEL(PortPlayback)
};
@ -102,88 +106,82 @@ PortPlayback::~PortPlayback()
}
int PortPlayback::writeCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData)
int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
{
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
int PortPlayback::writeCallback(const void* UNUSED(inputBuffer), void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* UNUSED(timeInfo),
const PaStreamCallbackFlags UNUSED(statusFlags))
{
lock();
aluMixData(mDevice, outputBuffer, framesPerBuffer);
unlock();
mDevice->renderSamples(outputBuffer, static_cast<uint>(framesPerBuffer),
static_cast<uint>(mParams.channelCount));
return 0;
}
ALCenum PortPlayback::open(const ALCchar *name)
void PortPlayback::open(const char *name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
return ALC_INVALID_VALUE;
mUpdateSize = mDevice->UpdateSize;
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
mParams.device = -1;
if(!ConfigValueInt(nullptr, "port", "device", &mParams.device) || mParams.device < 0)
mParams.device = Pa_GetDefaultOutputDevice();
mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
mParams.hostApiSpecificStreamInfo = nullptr;
PaStreamParameters params{};
auto devidopt = ConfigValueInt(nullptr, "port", "device");
if(devidopt && *devidopt >= 0) params.device = *devidopt;
else params.device = Pa_GetDefaultOutputDevice();
params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
params.hostApiSpecificStreamInfo = nullptr;
mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
switch(mDevice->FmtType)
{
case DevFmtByte:
mParams.sampleFormat = paInt8;
break;
case DevFmtUByte:
mParams.sampleFormat = paUInt8;
break;
case DevFmtUShort:
/* fall-through */
case DevFmtShort:
mParams.sampleFormat = paInt16;
break;
case DevFmtUInt:
/* fall-through */
case DevFmtInt:
mParams.sampleFormat = paInt32;
break;
case DevFmtFloat:
mParams.sampleFormat = paFloat32;
break;
case DevFmtByte:
params.sampleFormat = paInt8;
break;
case DevFmtUByte:
params.sampleFormat = paUInt8;
break;
case DevFmtUShort:
/* fall-through */
case DevFmtShort:
params.sampleFormat = paInt16;
break;
case DevFmtUInt:
/* fall-through */
case DevFmtInt:
params.sampleFormat = paInt32;
break;
case DevFmtFloat:
params.sampleFormat = paFloat32;
break;
}
retry_open:
PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize,
PaStream *stream{};
PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency, mDevice->UpdateSize,
paNoFlag, &PortPlayback::writeCallbackC, this)};
if(err != paNoError)
{
if(mParams.sampleFormat == paFloat32)
if(params.sampleFormat == paFloat32)
{
mParams.sampleFormat = paInt16;
params.sampleFormat = paInt16;
goto retry_open;
}
ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err));
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
}
mDevice->DeviceName = name;
return ALC_NO_ERROR;
Pa_CloseStream(mStream);
mStream = stream;
mParams = params;
mUpdateSize = mDevice->UpdateSize;
mDevice->DeviceName = name;
}
ALCboolean PortPlayback::reset()
bool PortPlayback::reset()
{
const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
mDevice->Frequency = streamInfo->sampleRate;
mDevice->Frequency = static_cast<uint>(streamInfo->sampleRate);
mDevice->UpdateSize = mUpdateSize;
if(mParams.sampleFormat == paInt8)
@ -199,32 +197,29 @@ ALCboolean PortPlayback::reset()
else
{
ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
return ALC_FALSE;
return false;
}
if(mParams.channelCount == 2)
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 ALC_FALSE;
return false;
}
SetDefaultChannelOrder(mDevice);
setDefaultChannelOrder();
return ALC_TRUE;
return true;
}
ALCboolean PortPlayback::start()
void PortPlayback::start()
{
PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
{
ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err));
return ALC_FALSE;
}
return ALC_TRUE;
const PaError err{Pa_StartStream(mStream)};
if(err == paNoError)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
Pa_GetErrorText(err)};
}
void PortPlayback::stop()
@ -236,27 +231,30 @@ void PortPlayback::stop()
struct PortCapture final : public BackendBase {
PortCapture(ALCdevice *device) noexcept : BackendBase{device} { }
PortCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~PortCapture() override;
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
static int readCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData);
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags);
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
ALCenum open(const ALCchar *name) override;
ALCboolean start() override;
void open(const char *name) override;
void start() override;
void stop() override;
ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams;
RingBufferPtr mRing{nullptr};
static constexpr inline const char *CurrentPrefix() noexcept { return "PortCapture::"; }
DEF_NEWDEL(PortCapture)
};
@ -269,89 +267,74 @@ PortCapture::~PortCapture()
}
int PortCapture::readCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void* userData)
{
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
int PortCapture::readCallback(const void *inputBuffer, void *UNUSED(outputBuffer),
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *UNUSED(timeInfo),
const PaStreamCallbackFlags UNUSED(statusFlags))
int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
{
mRing->write(inputBuffer, framesPerBuffer);
return 0;
}
ALCenum PortCapture::open(const ALCchar *name)
void PortCapture::open(const char *name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
ALuint samples{mDevice->BufferSize};
uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
ALsizei frame_size{mDevice->frameSizeFromFmt()};
uint frame_size{mDevice->frameSizeFromFmt()};
mRing = CreateRingBuffer(samples, frame_size, false);
if(!mRing) return ALC_INVALID_VALUE;
mRing = RingBuffer::Create(samples, frame_size, false);
mParams.device = -1;
if(!ConfigValueInt(nullptr, "port", "capture", &mParams.device) || mParams.device < 0)
mParams.device = Pa_GetDefaultInputDevice();
auto devidopt = ConfigValueInt(nullptr, "port", "capture");
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
else mParams.device = Pa_GetDefaultOutputDevice();
mParams.suggestedLatency = 0.0f;
mParams.hostApiSpecificStreamInfo = nullptr;
switch(mDevice->FmtType)
{
case DevFmtByte:
mParams.sampleFormat = paInt8;
break;
case DevFmtUByte:
mParams.sampleFormat = paUInt8;
break;
case DevFmtShort:
mParams.sampleFormat = paInt16;
break;
case DevFmtInt:
mParams.sampleFormat = paInt32;
break;
case DevFmtFloat:
mParams.sampleFormat = paFloat32;
break;
case DevFmtUInt:
case DevFmtUShort:
ERR("%s samples not supported\n", DevFmtTypeString(mDevice->FmtType));
return ALC_INVALID_VALUE;
case DevFmtByte:
mParams.sampleFormat = paInt8;
break;
case DevFmtUByte:
mParams.sampleFormat = paUInt8;
break;
case DevFmtShort:
mParams.sampleFormat = paInt16;
break;
case DevFmtInt:
mParams.sampleFormat = paInt32;
break;
case DevFmtFloat:
mParams.sampleFormat = paFloat32;
break;
case DevFmtUInt:
case DevFmtUShort:
throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
DevFmtTypeString(mDevice->FmtType)};
}
mParams.channelCount = mDevice->channelsFromFmt();
mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
if(err != paNoError)
{
ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err));
return ALC_INVALID_VALUE;
}
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean PortCapture::start()
void PortCapture::start()
{
PaError err{Pa_StartStream(mStream)};
const PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
{
ERR("Error starting stream: %s\n", Pa_GetErrorText(err));
return ALC_FALSE;
}
return ALC_TRUE;
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording: %s", Pa_GetErrorText(err)};
}
void PortCapture::stop()
@ -362,14 +345,11 @@ void PortCapture::stop()
}
ALCuint PortCapture::availableSamples()
{ return mRing->readSpace(); }
uint PortCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
ALCenum PortCapture::captureSamples(ALCvoid *buffer, ALCuint samples)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
void PortCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
} // namespace
@ -437,19 +417,21 @@ bool PortBackendFactory::init()
bool PortBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void PortBackendFactory::probe(DevProbe type, std::string *outnames)
std::string PortBackendFactory::probe(BackendType type)
{
std::string outnames;
switch(type)
{
case DevProbe::Playback:
case DevProbe::Capture:
/* Includes null char. */
outnames->append(pa_device, sizeof(pa_device));
break;
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(pa_device, sizeof(pa_device));
break;
}
return outnames;
}
BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new PortPlayback{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/portaudio.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_PORTAUDIO_H
#define BACKENDS_PORTAUDIO_H
#include "backends/base.h"
#include "base.h"
struct PortBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 662
- 722
modules/openal-soft/Alc/backends/pulseaudio.cpp
File diff suppressed because it is too large
View File


+ 3
- 3
modules/openal-soft/Alc/backends/pulseaudio.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_PULSEAUDIO_H
#define BACKENDS_PULSEAUDIO_H
#include "backends/base.h"
#include "base.h"
class PulseBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 0
- 955
modules/openal-soft/Alc/backends/qsa.cpp View File

@ -1,955 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2011-2013 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/qsa.h"
#include <stdlib.h>
#include <stdio.h>
#include <sched.h>
#include <errno.h>
#include <memory.h>
#include <poll.h>
#include <thread>
#include <memory>
#include <algorithm>
#include "alMain.h"
#include "alu.h"
#include "threads.h"
#include <sys/asoundlib.h>
#include <sys/neutrino.h>
namespace {
struct qsa_data {
snd_pcm_t* pcmHandle{nullptr};
int audio_fd{-1};
snd_pcm_channel_setup_t csetup{};
snd_pcm_channel_params_t cparams{};
ALvoid* buffer{nullptr};
ALsizei size{0};
std::atomic<ALenum> mKillNow{AL_TRUE};
std::thread mThread;
};
struct DevMap {
ALCchar* name;
int card;
int dev;
};
al::vector<DevMap> DeviceNameMap;
al::vector<DevMap> CaptureNameMap;
constexpr ALCchar qsaDevice[] = "QSA Default";
constexpr struct {
int32_t format;
} formatlist[] = {
{SND_PCM_SFMT_FLOAT_LE},
{SND_PCM_SFMT_S32_LE},
{SND_PCM_SFMT_U32_LE},
{SND_PCM_SFMT_S16_LE},
{SND_PCM_SFMT_U16_LE},
{SND_PCM_SFMT_S8},
{SND_PCM_SFMT_U8},
{0},
};
constexpr struct {
int32_t rate;
} ratelist[] = {
{192000},
{176400},
{96000},
{88200},
{48000},
{44100},
{32000},
{24000},
{22050},
{16000},
{12000},
{11025},
{8000},
{0},
};
constexpr struct {
int32_t channels;
} channellist[] = {
{8},
{7},
{6},
{4},
{2},
{1},
{0},
};
void deviceList(int type, al::vector<DevMap> *devmap)
{
snd_ctl_t* handle;
snd_pcm_info_t pcminfo;
int max_cards, card, err, dev;
DevMap entry;
char name[1024];
snd_ctl_hw_info info;
max_cards = snd_cards();
if(max_cards < 0)
return;
std::for_each(devmap->begin(), devmap->end(),
[](const DevMap &entry) -> void
{ free(entry.name); }
);
devmap->clear();
entry.name = strdup(qsaDevice);
entry.card = 0;
entry.dev = 0;
devmap->push_back(entry);
for(card = 0;card < max_cards;card++)
{
if((err=snd_ctl_open(&handle, card)) < 0)
continue;
if((err=snd_ctl_hw_info(handle, &info)) < 0)
{
snd_ctl_close(handle);
continue;
}
for(dev = 0;dev < (int)info.pcmdevs;dev++)
{
if((err=snd_ctl_pcm_info(handle, dev, &pcminfo)) < 0)
continue;
if((type==SND_PCM_CHANNEL_PLAYBACK && (pcminfo.flags&SND_PCM_INFO_PLAYBACK)) ||
(type==SND_PCM_CHANNEL_CAPTURE && (pcminfo.flags&SND_PCM_INFO_CAPTURE)))
{
snprintf(name, sizeof(name), "%s [%s] (hw:%d,%d)", info.name, pcminfo.name, card, dev);
entry.name = strdup(name);
entry.card = card;
entry.dev = dev;
devmap->push_back(entry);
TRACE("Got device \"%s\", card %d, dev %d\n", name, card, dev);
}
}
snd_ctl_close(handle);
}
}
/* Wrappers to use an old-style backend with the new interface. */
struct PlaybackWrapper final : public BackendBase {
PlaybackWrapper(ALCdevice *device) noexcept : BackendBase{device} { }
~PlaybackWrapper() override;
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void stop() override;
std::unique_ptr<qsa_data> mExtraData;
static constexpr inline const char *CurrentPrefix() noexcept { return "PlaybackWrapper::"; }
DEF_NEWDEL(PlaybackWrapper)
};
FORCE_ALIGN static int qsa_proc_playback(void *ptr)
{
PlaybackWrapper *self = static_cast<PlaybackWrapper*>(ptr);
ALCdevice *device = self->mDevice;
qsa_data *data = self->mExtraData.get();
snd_pcm_channel_status_t status;
sched_param param;
char* write_ptr;
ALint len;
int sret;
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
/* Increase default 10 priority to 11 to avoid jerky sound */
SchedGet(0, 0, &param);
param.sched_priority=param.sched_curpriority+1;
SchedSet(0, 0, SCHED_NOCHANGE, &param);
const ALint frame_size = device->frameSizeFromFmt();
self->lock();
while(!data->mKillNow.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = data->audio_fd;
pollitem.events = POLLOUT;
/* Select also works like time slice to OS */
self->unlock();
sret = poll(&pollitem, 1, 2000);
self->lock();
if(sret == -1)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll error: %s\n", strerror(errno));
aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno));
break;
}
if(sret == 0)
{
ERR("poll timeout\n");
continue;
}
len = data->size;
write_ptr = static_cast<char*>(data->buffer);
aluMixData(device, write_ptr, len/frame_size);
while(len>0 && !data->mKillNow.load(std::memory_order_acquire))
{
int wrote = snd_pcm_plugin_write(data->pcmHandle, write_ptr, len);
if(wrote <= 0)
{
if(errno==EAGAIN || errno==EWOULDBLOCK)
continue;
memset(&status, 0, sizeof(status));
status.channel = SND_PCM_CHANNEL_PLAYBACK;
snd_pcm_plugin_status(data->pcmHandle, &status);
/* we need to reinitialize the sound channel if we've underrun the buffer */
if(status.status == SND_PCM_STATUS_UNDERRUN ||
status.status == SND_PCM_STATUS_READY)
{
if(snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK) < 0)
{
aluHandleDisconnect(device, "Playback recovery failed");
break;
}
}
}
else
{
write_ptr += wrote;
len -= wrote;
}
}
}
self->unlock();
return 0;
}
/************/
/* Playback */
/************/
static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceName)
{
ALCdevice *device = self->mDevice;
int card, dev;
int status;
std::unique_ptr<qsa_data> data{new qsa_data{}};
data->mKillNow.store(AL_TRUE, std::memory_order_relaxed);
if(!deviceName)
deviceName = qsaDevice;
if(strcmp(deviceName, qsaDevice) == 0)
status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_PLAYBACK);
else
{
if(DeviceNameMap.empty())
deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
auto iter = std::find_if(DeviceNameMap.begin(), DeviceNameMap.end(),
[deviceName](const DevMap &entry) -> bool
{ return entry.name && strcmp(deviceName, entry.name) == 0; }
);
if(iter == DeviceNameMap.cend())
return ALC_INVALID_DEVICE;
status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_PLAYBACK);
}
if(status < 0)
return ALC_INVALID_DEVICE;
data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK);
if(data->audio_fd < 0)
{
snd_pcm_close(data->pcmHandle);
return ALC_INVALID_DEVICE;
}
device->DeviceName = deviceName;
self->mExtraData = std::move(data);
return ALC_NO_ERROR;
}
static void qsa_close_playback(PlaybackWrapper *self)
{
qsa_data *data = self->mExtraData.get();
if (data->buffer!=NULL)
{
free(data->buffer);
data->buffer=NULL;
}
snd_pcm_close(data->pcmHandle);
self->mExtraData = nullptr;
}
static ALCboolean qsa_reset_playback(PlaybackWrapper *self)
{
ALCdevice *device = self->mDevice;
qsa_data *data = self->mExtraData.get();
int32_t format=-1;
switch(device->FmtType)
{
case DevFmtByte:
format=SND_PCM_SFMT_S8;
break;
case DevFmtUByte:
format=SND_PCM_SFMT_U8;
break;
case DevFmtShort:
format=SND_PCM_SFMT_S16_LE;
break;
case DevFmtUShort:
format=SND_PCM_SFMT_U16_LE;
break;
case DevFmtInt:
format=SND_PCM_SFMT_S32_LE;
break;
case DevFmtUInt:
format=SND_PCM_SFMT_U32_LE;
break;
case DevFmtFloat:
format=SND_PCM_SFMT_FLOAT_LE;
break;
}
/* we actually don't want to block on writes */
snd_pcm_nonblock_mode(data->pcmHandle, 1);
/* Disable mmap to control data transfer to the audio device */
snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP);
snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_BUFFER_PARTIAL_BLOCKS);
// configure a sound channel
memset(&data->cparams, 0, sizeof(data->cparams));
data->cparams.channel=SND_PCM_CHANNEL_PLAYBACK;
data->cparams.mode=SND_PCM_MODE_BLOCK;
data->cparams.start_mode=SND_PCM_START_FULL;
data->cparams.stop_mode=SND_PCM_STOP_STOP;
data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt();
data->cparams.buf.block.frags_max=device->BufferSize / device->UpdateSize;
data->cparams.buf.block.frags_min=data->cparams.buf.block.frags_max;
data->cparams.format.interleave=1;
data->cparams.format.rate=device->Frequency;
data->cparams.format.voices=device->channelsFromFmt();
data->cparams.format.format=format;
if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
{
int original_rate=data->cparams.format.rate;
int original_voices=data->cparams.format.voices;
int original_format=data->cparams.format.format;
int it;
int jt;
for (it=0; it<1; it++)
{
/* Check for second pass */
if (it==1)
{
original_rate=ratelist[0].rate;
original_voices=channellist[0].channels;
original_format=formatlist[0].format;
}
do {
/* At first downgrade sample format */
jt=0;
do {
if (formatlist[jt].format==data->cparams.format.format)
{
data->cparams.format.format=formatlist[jt+1].format;
break;
}
if (formatlist[jt].format==0)
{
data->cparams.format.format=0;
break;
}
jt++;
} while(1);
if (data->cparams.format.format==0)
{
data->cparams.format.format=original_format;
/* At secod downgrade sample rate */
jt=0;
do {
if (ratelist[jt].rate==data->cparams.format.rate)
{
data->cparams.format.rate=ratelist[jt+1].rate;
break;
}
if (ratelist[jt].rate==0)
{
data->cparams.format.rate=0;
break;
}
jt++;
} while(1);
if (data->cparams.format.rate==0)
{
data->cparams.format.rate=original_rate;
data->cparams.format.format=original_format;
/* At third downgrade channels number */
jt=0;
do {
if(channellist[jt].channels==data->cparams.format.voices)
{
data->cparams.format.voices=channellist[jt+1].channels;
break;
}
if (channellist[jt].channels==0)
{
data->cparams.format.voices=0;
break;
}
jt++;
} while(1);
}
if (data->cparams.format.voices==0)
{
break;
}
}
data->cparams.buf.block.frag_size=device->UpdateSize*
data->cparams.format.voices*
snd_pcm_format_width(data->cparams.format.format)/8;
data->cparams.buf.block.frags_max=device->NumUpdates;
data->cparams.buf.block.frags_min=device->NumUpdates;
if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
{
continue;
}
else
{
break;
}
} while(1);
if (data->cparams.format.voices!=0)
{
break;
}
}
if (data->cparams.format.voices==0)
{
return ALC_FALSE;
}
}
if ((snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK))<0)
{
return ALC_FALSE;
}
memset(&data->csetup, 0, sizeof(data->csetup));
data->csetup.channel=SND_PCM_CHANNEL_PLAYBACK;
if (snd_pcm_plugin_setup(data->pcmHandle, &data->csetup)<0)
{
return ALC_FALSE;
}
/* now fill back to the our AL device */
device->Frequency=data->cparams.format.rate;
switch (data->cparams.format.voices)
{
case 1:
device->FmtChans=DevFmtMono;
break;
case 2:
device->FmtChans=DevFmtStereo;
break;
case 4:
device->FmtChans=DevFmtQuad;
break;
case 6:
device->FmtChans=DevFmtX51;
break;
case 7:
device->FmtChans=DevFmtX61;
break;
case 8:
device->FmtChans=DevFmtX71;
break;
default:
device->FmtChans=DevFmtMono;
break;
}
switch (data->cparams.format.format)
{
case SND_PCM_SFMT_S8:
device->FmtType=DevFmtByte;
break;
case SND_PCM_SFMT_U8:
device->FmtType=DevFmtUByte;
break;
case SND_PCM_SFMT_S16_LE:
device->FmtType=DevFmtShort;
break;
case SND_PCM_SFMT_U16_LE:
device->FmtType=DevFmtUShort;
break;
case SND_PCM_SFMT_S32_LE:
device->FmtType=DevFmtInt;
break;
case SND_PCM_SFMT_U32_LE:
device->FmtType=DevFmtUInt;
break;
case SND_PCM_SFMT_FLOAT_LE:
device->FmtType=DevFmtFloat;
break;
default:
device->FmtType=DevFmtShort;
break;
}
SetDefaultChannelOrder(device);
device->UpdateSize=data->csetup.buf.block.frag_size / device->frameSizeFromFmt();
device->NumUpdates=data->csetup.buf.block.frags;
data->size=data->csetup.buf.block.frag_size;
data->buffer=malloc(data->size);
if (!data->buffer)
{
return ALC_FALSE;
}
return ALC_TRUE;
}
static ALCboolean qsa_start_playback(PlaybackWrapper *self)
{
qsa_data *data = self->mExtraData.get();
try {
data->mKillNow.store(AL_FALSE, std::memory_order_release);
data->mThread = std::thread(qsa_proc_playback, self);
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
}
return ALC_FALSE;
}
static void qsa_stop_playback(PlaybackWrapper *self)
{
qsa_data *data = self->mExtraData.get();
if(data->mKillNow.exchange(AL_TRUE, std::memory_order_acq_rel) || !data->mThread.joinable())
return;
data->mThread.join();
}
PlaybackWrapper::~PlaybackWrapper()
{
if(mExtraData)
qsa_close_playback(this);
}
ALCenum PlaybackWrapper::open(const ALCchar *name)
{ return qsa_open_playback(this, name); }
ALCboolean PlaybackWrapper::reset()
{ return qsa_reset_playback(this); }
ALCboolean PlaybackWrapper::start()
{ return qsa_start_playback(this); }
void PlaybackWrapper::stop()
{ qsa_stop_playback(this); }
/***********/
/* Capture */
/***********/
struct CaptureWrapper final : public BackendBase {
CaptureWrapper(ALCdevice *device) noexcept : BackendBase{device} { }
~CaptureWrapper() override;
ALCenum open(const ALCchar *name) override;
ALCboolean start() override;
void stop() override;
ALCenum captureSamples(void *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
std::unique_ptr<qsa_data> mExtraData;
static constexpr inline const char *CurrentPrefix() noexcept { return "CaptureWrapper::"; }
DEF_NEWDEL(CaptureWrapper)
};
static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName)
{
ALCdevice *device = self->mDevice;
int card, dev;
int format=-1;
int status;
std::unique_ptr<qsa_data> data{new qsa_data{}};
if(!deviceName)
deviceName = qsaDevice;
if(strcmp(deviceName, qsaDevice) == 0)
status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_CAPTURE);
else
{
if(CaptureNameMap.empty())
deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
auto iter = std::find_if(CaptureNameMap.cbegin(), CaptureNameMap.cend(),
[deviceName](const DevMap &entry) -> bool
{ return entry.name && strcmp(deviceName, entry.name) == 0; }
);
if(iter == CaptureNameMap.cend())
return ALC_INVALID_DEVICE;
status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_CAPTURE);
}
if(status < 0)
return ALC_INVALID_DEVICE;
data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE);
if(data->audio_fd < 0)
{
snd_pcm_close(data->pcmHandle);
return ALC_INVALID_DEVICE;
}
device->DeviceName = deviceName;
switch (device->FmtType)
{
case DevFmtByte:
format=SND_PCM_SFMT_S8;
break;
case DevFmtUByte:
format=SND_PCM_SFMT_U8;
break;
case DevFmtShort:
format=SND_PCM_SFMT_S16_LE;
break;
case DevFmtUShort:
format=SND_PCM_SFMT_U16_LE;
break;
case DevFmtInt:
format=SND_PCM_SFMT_S32_LE;
break;
case DevFmtUInt:
format=SND_PCM_SFMT_U32_LE;
break;
case DevFmtFloat:
format=SND_PCM_SFMT_FLOAT_LE;
break;
}
/* we actually don't want to block on reads */
snd_pcm_nonblock_mode(data->pcmHandle, 1);
/* Disable mmap to control data transfer to the audio device */
snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP);
/* configure a sound channel */
memset(&data->cparams, 0, sizeof(data->cparams));
data->cparams.mode=SND_PCM_MODE_BLOCK;
data->cparams.channel=SND_PCM_CHANNEL_CAPTURE;
data->cparams.start_mode=SND_PCM_START_GO;
data->cparams.stop_mode=SND_PCM_STOP_STOP;
data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt();
data->cparams.buf.block.frags_max=device->NumUpdates;
data->cparams.buf.block.frags_min=device->NumUpdates;
data->cparams.format.interleave=1;
data->cparams.format.rate=device->Frequency;
data->cparams.format.voices=device->channelsFromFmt();
data->cparams.format.format=format;
if(snd_pcm_plugin_params(data->pcmHandle, &data->cparams) < 0)
{
snd_pcm_close(data->pcmHandle);
return ALC_INVALID_VALUE;
}
self->mExtraData = std::move(data);
return ALC_NO_ERROR;
}
static void qsa_close_capture(CaptureWrapper *self)
{
qsa_data *data = self->mExtraData.get();
if (data->pcmHandle!=nullptr)
snd_pcm_close(data->pcmHandle);
data->pcmHandle = nullptr;
self->mExtraData = nullptr;
}
static void qsa_start_capture(CaptureWrapper *self)
{
qsa_data *data = self->mExtraData.get();
int rstatus;
if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
{
ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
return;
}
memset(&data->csetup, 0, sizeof(data->csetup));
data->csetup.channel=SND_PCM_CHANNEL_CAPTURE;
if ((rstatus=snd_pcm_plugin_setup(data->pcmHandle, &data->csetup))<0)
{
ERR("capture setup failed: %s\n", snd_strerror(rstatus));
return;
}
snd_pcm_capture_go(data->pcmHandle);
}
static void qsa_stop_capture(CaptureWrapper *self)
{
qsa_data *data = self->mExtraData.get();
snd_pcm_capture_flush(data->pcmHandle);
}
static ALCuint qsa_available_samples(CaptureWrapper *self)
{
ALCdevice *device = self->mDevice;
qsa_data *data = self->mExtraData.get();
snd_pcm_channel_status_t status;
ALint frame_size = device->frameSizeFromFmt();
ALint free_size;
int rstatus;
memset(&status, 0, sizeof (status));
status.channel=SND_PCM_CHANNEL_CAPTURE;
snd_pcm_plugin_status(data->pcmHandle, &status);
if ((status.status==SND_PCM_STATUS_OVERRUN) ||
(status.status==SND_PCM_STATUS_READY))
{
if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
{
ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
aluHandleDisconnect(device, "Failed capture recovery: %s", snd_strerror(rstatus));
return 0;
}
snd_pcm_capture_go(data->pcmHandle);
return 0;
}
free_size=data->csetup.buf.block.frag_size*data->csetup.buf.block.frags;
free_size-=status.free;
return free_size/frame_size;
}
static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuint samples)
{
ALCdevice *device = self->mDevice;
qsa_data *data = self->mExtraData.get();
char* read_ptr;
snd_pcm_channel_status_t status;
int selectret;
int bytes_read;
ALint frame_size=device->frameSizeFromFmt();
ALint len=samples*frame_size;
int rstatus;
read_ptr = static_cast<char*>(buffer);
while (len>0)
{
pollfd pollitem{};
pollitem.fd = data->audio_fd;
pollitem.events = POLLOUT;
/* Select also works like time slice to OS */
bytes_read=0;
selectret = poll(&pollitem, 1, 2000);
switch (selectret)
{
case -1:
aluHandleDisconnect(device, "Failed to check capture samples");
return ALC_INVALID_DEVICE;
case 0:
break;
default:
bytes_read=snd_pcm_plugin_read(data->pcmHandle, read_ptr, len);
break;
}
if (bytes_read<=0)
{
if ((errno==EAGAIN) || (errno==EWOULDBLOCK))
{
continue;
}
memset(&status, 0, sizeof (status));
status.channel=SND_PCM_CHANNEL_CAPTURE;
snd_pcm_plugin_status(data->pcmHandle, &status);
/* we need to reinitialize the sound channel if we've overrun the buffer */
if ((status.status==SND_PCM_STATUS_OVERRUN) ||
(status.status==SND_PCM_STATUS_READY))
{
if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
{
ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
aluHandleDisconnect(device, "Failed capture recovery: %s",
snd_strerror(rstatus));
return ALC_INVALID_DEVICE;
}
snd_pcm_capture_go(data->pcmHandle);
}
}
else
{
read_ptr+=bytes_read;
len-=bytes_read;
}
}
return ALC_NO_ERROR;
}
CaptureWrapper::~CaptureWrapper()
{
if(mExtraData)
qsa_close_capture(this);
}
ALCenum CaptureWrapper::open(const ALCchar *name)
{ return qsa_open_capture(this, name); }
ALCboolean CaptureWrapper::start()
{ qsa_start_capture(this); return ALC_TRUE; }
void CaptureWrapper::stop()
{ qsa_stop_capture(this); }
ALCenum CaptureWrapper::captureSamples(void *buffer, ALCuint samples)
{ return qsa_capture_samples(this, buffer, samples); }
ALCuint CaptureWrapper::availableSamples()
{ return qsa_available_samples(this); }
} // namespace
bool QSABackendFactory::init()
{ return true; }
bool QSABackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void QSABackendFactory::probe(DevProbe type, std::string *outnames)
{
auto add_device = [outnames](const DevMap &entry) -> void
{
const char *n = entry.name;
if(n && n[0])
outnames->append(n, strlen(n)+1);
};
switch (type)
{
case DevProbe::Playback:
deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
std::for_each(DeviceNameMap.cbegin(), DeviceNameMap.cend(), add_device);
break;
case DevProbe::Capture:
deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
std::for_each(CaptureNameMap.cbegin(), CaptureNameMap.cend(), add_device);
break;
}
}
BackendPtr QSABackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new PlaybackWrapper{device}};
if(type == BackendType::Capture)
return BackendPtr{new CaptureWrapper{device}};
return nullptr;
}
BackendFactory &QSABackendFactory::getFactory()
{
static QSABackendFactory factory{};
return factory;
}

+ 0
- 19
modules/openal-soft/Alc/backends/qsa.h View File

@ -1,19 +0,0 @@
#ifndef BACKENDS_QSA_H
#define BACKENDS_QSA_H
#include "backends/base.h"
struct QSABackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_QSA_H */

+ 88
- 91
modules/openal-soft/Alc/backends/sdl2.cpp View File

@ -20,17 +20,19 @@
#include "config.h"
#include "backends/sdl2.h"
#include <stdlib.h>
#include <SDL2/SDL.h>
#include "sdl2.h"
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <string>
#include "alMain.h"
#include "alu.h"
#include "threads.h"
#include "compat.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/logging.h"
#include <SDL2/SDL.h>
namespace {
@ -41,31 +43,29 @@ namespace {
#define DEVNAME_PREFIX ""
#endif
constexpr ALCchar defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
struct Sdl2Backend final : public BackendBase {
Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { }
Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
~Sdl2Backend() override;
static void audioCallbackC(void *ptr, Uint8 *stream, int len);
void audioCallback(Uint8 *stream, int len);
void audioCallback(Uint8 *stream, int len) noexcept;
static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept
{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
void lock() override;
void unlock() override;
SDL_AudioDeviceID mDeviceID{0u};
ALsizei mFrameSize{0};
uint mFrameSize{0};
ALuint mFrequency{0u};
uint mFrequency{0u};
DevFmtChannels mFmtChans{};
DevFmtType mFmtType{};
ALuint mUpdateSize{0u};
uint mUpdateSize{0u};
static constexpr inline const char *CurrentPrefix() noexcept { return "ALCsdl2Playback::"; }
DEF_NEWDEL(Sdl2Backend)
};
@ -76,114 +76,108 @@ Sdl2Backend::~Sdl2Backend()
mDeviceID = 0;
}
void Sdl2Backend::audioCallbackC(void *ptr, Uint8 *stream, int len)
{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
void Sdl2Backend::audioCallback(Uint8 *stream, int len)
void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept
{
assert((len % mFrameSize) == 0);
aluMixData(mDevice, stream, len / mFrameSize);
const auto ulen = static_cast<unsigned int>(len);
assert((ulen % mFrameSize) == 0);
mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt());
}
ALCenum Sdl2Backend::open(const ALCchar *name)
void Sdl2Backend::open(const char *name)
{
SDL_AudioSpec want{}, have{};
want.freq = mDevice->Frequency;
want.freq = static_cast<int>(mDevice->Frequency);
switch(mDevice->FmtType)
{
case DevFmtUByte: want.format = AUDIO_U8; break;
case DevFmtByte: want.format = AUDIO_S8; break;
case DevFmtUShort: want.format = AUDIO_U16SYS; break;
case DevFmtShort: want.format = AUDIO_S16SYS; break;
case DevFmtUInt: /* fall-through */
case DevFmtInt: want.format = AUDIO_S32SYS; break;
case DevFmtFloat: want.format = AUDIO_F32; break;
case DevFmtUByte: want.format = AUDIO_U8; break;
case DevFmtByte: want.format = AUDIO_S8; break;
case DevFmtUShort: want.format = AUDIO_U16SYS; break;
case DevFmtShort: want.format = AUDIO_S16SYS; break;
case DevFmtUInt: /* fall-through */
case DevFmtInt: want.format = AUDIO_S32SYS; break;
case DevFmtFloat: want.format = AUDIO_F32; break;
}
want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
want.samples = mDevice->UpdateSize;
want.samples = static_cast<Uint16>(minu(mDevice->UpdateSize, 8192));
want.callback = &Sdl2Backend::audioCallbackC;
want.userdata = this;
/* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
* necessarily the first in the list.
*/
SDL_AudioDeviceID devid;
if(!name || strcmp(name, defaultDeviceName) == 0)
mDeviceID = 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
{
const size_t prefix_len = strlen(DEVNAME_PREFIX);
if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0)
mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
else
mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
}
if(mDeviceID == 0)
return ALC_INVALID_VALUE;
mDevice->Frequency = have.freq;
if(have.channels == 1)
mDevice->FmtChans = DevFmtMono;
else if(have.channels == 2)
mDevice->FmtChans = DevFmtStereo;
if(!devid)
throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()};
DevFmtChannels devchans{};
if(have.channels >= 2)
devchans = DevFmtStereo;
else if(have.channels == 1)
devchans = DevFmtMono;
else
{
ERR("Got unhandled SDL channel count: %d\n", (int)have.channels);
return ALC_INVALID_VALUE;
SDL_CloseAudioDevice(devid);
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled SDL channel count: %d", int{have.channels}};
}
DevFmtType devtype{};
switch(have.format)
{
case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break;
case AUDIO_S8: mDevice->FmtType = DevFmtByte; break;
case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break;
case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break;
case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break;
case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break;
default:
ERR("Got unsupported SDL format: 0x%04x\n", have.format);
return ALC_INVALID_VALUE;
case AUDIO_U8: devtype = DevFmtUByte; break;
case AUDIO_S8: devtype = DevFmtByte; break;
case AUDIO_U16SYS: devtype = DevFmtUShort; break;
case AUDIO_S16SYS: devtype = DevFmtShort; break;
case AUDIO_S32SYS: devtype = DevFmtInt; break;
case AUDIO_F32SYS: devtype = DevFmtFloat; break;
default:
SDL_CloseAudioDevice(devid);
throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x",
have.format};
}
mDevice->UpdateSize = have.samples;
mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */
mFrameSize = mDevice->frameSizeFromFmt();
mFrequency = mDevice->Frequency;
mFmtChans = mDevice->FmtChans;
mFmtType = mDevice->FmtType;
mUpdateSize = mDevice->UpdateSize;
if(mDeviceID)
SDL_CloseAudioDevice(mDeviceID);
mDeviceID = devid;
mFrameSize = BytesFromDevFmt(devtype) * have.channels;
mFrequency = static_cast<uint>(have.freq);
mFmtChans = devchans;
mFmtType = devtype;
mUpdateSize = have.samples;
mDevice->DeviceName = name ? name : defaultDeviceName;
return ALC_NO_ERROR;
}
ALCboolean Sdl2Backend::reset()
bool Sdl2Backend::reset()
{
mDevice->Frequency = mFrequency;
mDevice->FmtChans = mFmtChans;
mDevice->FmtType = mFmtType;
mDevice->UpdateSize = mUpdateSize;
mDevice->BufferSize = mUpdateSize * 2;
SetDefaultWFXChannelOrder(mDevice);
return ALC_TRUE;
mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */
setDefaultWFXChannelOrder();
return true;
}
ALCboolean Sdl2Backend::start()
{
SDL_PauseAudioDevice(mDeviceID, 0);
return ALC_TRUE;
}
void Sdl2Backend::start()
{ SDL_PauseAudioDevice(mDeviceID, 0); }
void Sdl2Backend::stop()
{ SDL_PauseAudioDevice(mDeviceID, 1); }
void Sdl2Backend::lock()
{ SDL_LockAudioDevice(mDeviceID); }
void Sdl2Backend::unlock()
{ SDL_UnlockAudioDevice(mDeviceID); }
} // namespace
BackendFactory &SDL2BackendFactory::getFactory()
@ -198,25 +192,28 @@ bool SDL2BackendFactory::init()
bool SDL2BackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
void SDL2BackendFactory::probe(DevProbe type, std::string *outnames)
std::string SDL2BackendFactory::probe(BackendType type)
{
if(type != DevProbe::Playback)
return;
std::string outnames;
if(type != BackendType::Playback)
return outnames;
int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)};
/* Includes null char. */
outnames->append(defaultDeviceName, sizeof(defaultDeviceName));
outnames.append(defaultDeviceName, sizeof(defaultDeviceName));
for(int i{0};i < num_devices;++i)
{
std::string name{DEVNAME_PREFIX};
name += SDL_GetAudioDeviceName(i, SDL_FALSE);
if(!name.empty())
outnames->append(name.c_str(), name.length()+1);
outnames.append(name.c_str(), name.length()+1);
}
return outnames;
}
BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr SDL2BackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new Sdl2Backend{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/sdl2.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_SDL2_H
#define BACKENDS_SDL2_H
#include "backends/base.h"
#include "base.h"
struct SDL2BackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 273
- 237
modules/openal-soft/Alc/backends/sndio.cpp View File

@ -20,8 +20,9 @@
#include "config.h"
#include "backends/sndio.h"
#include "sndio.h"
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -29,39 +30,46 @@
#include <thread>
#include <functional>
#include "alMain.h"
#include "alu.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include "ringbuffer.h"
#include <sndio.h>
namespace {
static const ALCchar sndio_device[] = "SndIO Default";
static const char sndio_device[] = "SndIO Default";
struct SioPar : public sio_par {
SioPar() { sio_initpar(this); }
void clear() { sio_initpar(this); }
};
struct SndioPlayback final : public BackendBase {
SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioPlayback() override;
int mixerProc();
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
sio_hdl *mSndHandle{nullptr};
uint mFrameStep{};
al::vector<ALubyte> mBuffer;
al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "SndioPlayback::"; }
DEF_NEWDEL(SndioPlayback)
};
@ -74,32 +82,29 @@ SndioPlayback::~SndioPlayback()
int SndioPlayback::mixerProc()
{
const size_t frameStep{mFrameStep};
const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const ALsizei frameSize{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
auto WritePtr = static_cast<ALubyte*>(mBuffer.data());
size_t len{mBuffer.size()};
al::span<al::byte> buffer{mBuffer};
lock();
aluMixData(mDevice, WritePtr, len/frameSize);
unlock();
while(len > 0 && !mKillNow.load(std::memory_order_acquire))
mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize),
frameStep);
while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
{
size_t wrote{sio_write(mSndHandle, WritePtr, len)};
size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
if(wrote == 0)
{
ERR("sio_write failed\n");
aluHandleDisconnect(mDevice, "Failed to write playback samples");
mDevice->handleDisconnect("Failed to write playback samples");
break;
}
len -= wrote;
WritePtr += wrote;
buffer = buffer.subspan(wrote);
}
}
@ -107,130 +112,150 @@ int SndioPlayback::mixerProc()
}
ALCenum SndioPlayback::open(const ALCchar *name)
void SndioPlayback::open(const char *name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
mSndHandle = sio_open(nullptr, SIO_PLAY, 0);
if(mSndHandle == nullptr)
{
ERR("Could not open device\n");
return ALC_INVALID_VALUE;
}
sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
if(!sndHandle)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
if(mSndHandle)
sio_close(mSndHandle);
mSndHandle = sndHandle;
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean SndioPlayback::reset()
bool SndioPlayback::reset()
{
sio_par par;
sio_initpar(&par);
SioPar par;
par.rate = mDevice->Frequency;
par.pchan = ((mDevice->FmtChans != DevFmtMono) ? 2 : 1);
switch(mDevice->FmtType)
auto tryfmt = mDevice->FmtType;
retry_params:
switch(tryfmt)
{
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
}
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = 1;
par.rate = mDevice->Frequency;
par.pchan = mDevice->channelsFromFmt();
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
{
ERR("Failed to set device parameters\n");
return ALC_FALSE;
try {
if(!sio_setpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device parameters"};
par.clear();
if(!sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get device parameters"};
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
if(par.pchan < 1)
throw al::backend_exception{al::backend_error::DeviceError,
"No playback channels on device"};
}
if(par.bits != par.bps*8)
{
ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
return ALC_FALSE;
catch(al::backend_exception &e) {
if(tryfmt == DevFmtShort)
throw;
par.clear();
tryfmt = DevFmtShort;
goto retry_params;
}
mDevice->Frequency = par.rate;
mDevice->FmtChans = ((par.pchan==1) ? DevFmtMono : DevFmtStereo);
if(par.bits == 8 && par.sig == 1)
mDevice->FmtType = DevFmtByte;
else if(par.bits == 8 && par.sig == 0)
mDevice->FmtType = DevFmtUByte;
else if(par.bits == 16 && par.sig == 1)
mDevice->FmtType = DevFmtShort;
else if(par.bits == 16 && par.sig == 0)
mDevice->FmtType = DevFmtUShort;
else if(par.bits == 32 && par.sig == 1)
mDevice->FmtType = DevFmtInt;
else if(par.bits == 32 && par.sig == 0)
mDevice->FmtType = DevFmtUInt;
if(par.bps == 1)
mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte;
else if(par.bps == 2)
mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort;
else if(par.bps == 4)
mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt;
else
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8};
mFrameStep = par.pchan;
if(par.pchan != mDevice->channelsFromFmt())
{
ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits);
return ALC_FALSE;
WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s",
DevFmtChannelsString(mDevice->FmtChans));
if(par.pchan < 2) mDevice->FmtChans = DevFmtMono;
else mDevice->FmtChans = DevFmtStereo;
}
mDevice->Frequency = par.rate;
SetDefaultChannelOrder(mDevice);
setDefaultChannelOrder();
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.bufsz + par.round;
mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
std::fill(mBuffer.begin(), mBuffer.end(), 0);
return ALC_TRUE;
mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps);
if(par.sig == 1)
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
else if(par.bits == 8)
std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80));
else if(par.bits == 16)
std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000);
else if(par.bits == 32)
std::fill_n(reinterpret_cast<uint32_t*>(mBuffer.data()), mBuffer.size()/4, 0x80000000u);
return true;
}
ALCboolean SndioPlayback::start()
void SndioPlayback::start()
{
if(!sio_start(mSndHandle))
{
ERR("Error starting playback\n");
return ALC_FALSE;
}
throw al::backend_exception{al::backend_error::DeviceError, "Error starting playback"};
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
sio_stop(mSndHandle);
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
catch(...) {
}
sio_stop(mSndHandle);
return ALC_FALSE;
}
void SndioPlayback::stop()
@ -244,17 +269,22 @@ void SndioPlayback::stop()
}
/* TODO: This could be improved by avoiding the ring buffer and record thread,
* counting the available samples with the sio_onmove callback and reading
* directly from the device. However, this depends on reasonable support for
* capture buffer sizes apps may request.
*/
struct SndioCapture final : public BackendBase {
SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioCapture() override;
int recordProc();
ALCenum open(const ALCchar *name) override;
ALCboolean start() override;
void open(const char *name) override;
void start() override;
void stop() override;
ALCenum captureSamples(void *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
sio_hdl *mSndHandle{nullptr};
@ -263,7 +293,6 @@ struct SndioCapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "SndioCapture::"; }
DEF_NEWDEL(SndioCapture)
};
@ -279,167 +308,175 @@ int SndioCapture::recordProc()
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
const ALsizei frameSize{mDevice->frameSizeFromFmt()};
const uint frameSize{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
int nfds_pre{sio_nfds(mSndHandle)};
if(nfds_pre <= 0)
{
auto data = mRing->getWriteVector();
size_t todo{data.first.len + data.second.len};
if(todo == 0)
mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre);
return 1;
}
auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre));
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
/* Wait until there's some samples to read. */
const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
if(nfds <= 0)
{
static char junk[4096];
sio_read(mSndHandle, junk,
minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize);
mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
break;
}
int pollres{::poll(fds.get(), static_cast<uint>(nfds), 2000)};
if(pollres < 0)
{
if(errno == EINTR) continue;
mDevice->handleDisconnect("Poll error: %s", strerror(errno));
break;
}
if(pollres == 0)
continue;
const int revents{sio_revents(mSndHandle, fds.get())};
if((revents&POLLHUP))
{
mDevice->handleDisconnect("Got POLLHUP from poll events");
break;
}
if(!(revents&POLLIN))
continue;
size_t total{0u};
data.first.len *= frameSize;
data.second.len *= frameSize;
todo = minz(todo, mDevice->UpdateSize) * frameSize;
while(total < todo)
auto data = mRing->getWriteVector();
al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
while(!buffer.empty())
{
if(!data.first.len)
data.first = data.second;
size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
if(got == 0) break;
size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))};
if(!got)
mRing->writeAdvance(got / frameSize);
buffer = buffer.subspan(got);
if(buffer.empty())
{
aluHandleDisconnect(mDevice, "Failed to read capture samples");
break;
data = mRing->getWriteVector();
buffer = {data.first.buf, data.first.len*frameSize};
}
data.first.buf += got;
data.first.len -= got;
total += got;
}
mRing->writeAdvance(total / frameSize);
if(buffer.empty())
{
/* Got samples to read, but no place to store it. Drop it. */
static char junk[4096];
sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize));
}
}
return 0;
}
ALCenum SndioCapture::open(const ALCchar *name)
void SndioCapture::open(const char *name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
mSndHandle = sio_open(nullptr, SIO_REC, 0);
mSndHandle = sio_open(nullptr, SIO_REC, true);
if(mSndHandle == nullptr)
{
ERR("Could not open device\n");
return ALC_INVALID_VALUE;
}
sio_par par;
sio_initpar(&par);
throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
SioPar par;
switch(mDevice->FmtType)
{
case DevFmtByte:
par.bps = 1;
par.sig = 1;
break;
case DevFmtUByte:
par.bps = 1;
par.sig = 0;
break;
case DevFmtShort:
par.bps = 2;
par.sig = 1;
break;
case DevFmtUShort:
par.bps = 2;
par.sig = 0;
break;
case DevFmtInt:
par.bps = 4;
par.sig = 1;
break;
case DevFmtUInt:
par.bps = 4;
par.sig = 0;
break;
case DevFmtFloat:
ERR("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
return ALC_INVALID_VALUE;
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
case DevFmtFloat:
throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
}
par.bits = par.bps * 8;
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = SIO_LE_NATIVE ? 0 : 1;
par.msb = 1;
par.rchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
par.round = minu(par.appbufsz, mDevice->Frequency/40);
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.appbufsz;
par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
{
ERR("Failed to set device parameters\n");
return ALC_INVALID_VALUE;
}
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device praameters"};
if(par.bits != par.bps*8)
{
ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
return ALC_INVALID_VALUE;
}
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0) ||
(mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0) ||
(mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0) ||
(mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0) ||
(mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0) ||
(mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0)) ||
mDevice->channelsFromFmt() != (ALsizei)par.rchan ||
mDevice->Frequency != par.rate)
auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool
{
ERR("Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead\n",
return (fmttype == DevFmtByte && p.bps == 1 && p.sig != 0)
|| (fmttype == DevFmtUByte && p.bps == 1 && p.sig == 0)
|| (fmttype == DevFmtShort && p.bps == 2 && p.sig != 0)
|| (fmttype == DevFmtUShort && p.bps == 2 && p.sig == 0)
|| (fmttype == DevFmtInt && p.bps == 4 && p.sig != 0)
|| (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0);
};
if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan
|| mDevice->Frequency != par.rate)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead",
DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
mDevice->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate);
return ALC_INVALID_VALUE;
}
mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
mRing = CreateRingBuffer(mDevice->BufferSize, par.bps*par.rchan, false);
if(!mRing)
{
ERR("Failed to allocate %u-byte ringbuffer\n", mDevice->BufferSize*par.bps*par.rchan);
return ALC_OUT_OF_MEMORY;
}
mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
mDevice->UpdateSize = par.round;
SetDefaultChannelOrder(mDevice);
setDefaultChannelOrder();
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean SndioCapture::start()
void SndioCapture::start()
{
if(!sio_start(mSndHandle))
{
ERR("Error starting playback\n");
return ALC_FALSE;
}
throw al::backend_exception{al::backend_error::DeviceError, "Error starting capture"};
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Could not create record thread: %s\n", e.what());
}
catch(...) {
sio_stop(mSndHandle);
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start capture thread: %s", e.what()};
}
sio_stop(mSndHandle);
return ALC_FALSE;
}
void SndioCapture::stop()
@ -452,14 +489,11 @@ void SndioCapture::stop()
ERR("Error stopping device\n");
}
ALCenum SndioCapture::captureSamples(void *buffer, ALCuint samples)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
void SndioCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
ALCuint SndioCapture::availableSamples()
{ return mRing->readSpace(); }
uint SndioCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
} // namespace
@ -475,19 +509,21 @@ bool SndIOBackendFactory::init()
bool SndIOBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void SndIOBackendFactory::probe(DevProbe type, std::string *outnames)
std::string SndIOBackendFactory::probe(BackendType type)
{
std::string outnames;
switch(type)
{
case DevProbe::Playback:
case DevProbe::Capture:
/* Includes null char. */
outnames->append(sndio_device, sizeof(sndio_device));
break;
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(sndio_device, sizeof(sndio_device));
break;
}
return outnames;
}
BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new SndioPlayback{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/sndio.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_SNDIO_H
#define BACKENDS_SNDIO_H
#include "backends/base.h"
#include "base.h"
struct SndIOBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 102
- 101
modules/openal-soft/Alc/backends/solaris.cpp View File

@ -20,7 +20,7 @@
#include "config.h"
#include "backends/solaris.h"
#include "solaris.h"
#include <sys/ioctl.h>
#include <sys/types.h>
@ -34,46 +34,48 @@
#include <errno.h>
#include <poll.h>
#include <math.h>
#include <string.h>
#include <thread>
#include <functional>
#include "alMain.h"
#include "alu.h"
#include "alconfig.h"
#include "albyte.h"
#include "alc/alconfig.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "threads.h"
#include "vector.h"
#include "compat.h"
#include <sys/audioio.h>
namespace {
constexpr ALCchar solaris_device[] = "Solaris Default";
constexpr char solaris_device[] = "Solaris Default";
const char *solaris_driver = "/dev/audio";
std::string solaris_driver{"/dev/audio"};
struct SolarisBackend final : public BackendBase {
SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { }
SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { }
~SolarisBackend() override;
int mixerProc();
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
int mFd{-1};
al::vector<ALubyte> mBuffer;
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "SolarisBackend::"; }
DEF_NEWDEL(SolarisBackend)
};
@ -89,26 +91,23 @@ int SolarisBackend::mixerProc()
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const int frame_size{mDevice->frameSizeFromFmt()};
const size_t frame_step{mDevice->channelsFromFmt()};
const uint frame_size{mDevice->frameSizeFromFmt()};
lock();
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = mFd;
pollitem.events = POLLOUT;
unlock();
int pret{poll(&pollitem, 1, 1000)};
lock();
if(pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed to wait for playback buffer: %s",
strerror(errno));
mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno));
break;
}
else if(pret == 0)
@ -117,9 +116,9 @@ int SolarisBackend::mixerProc()
continue;
}
ALubyte *write_ptr{mBuffer.data()};
al::byte *write_ptr{mBuffer.data()};
size_t to_write{mBuffer.size()};
aluMixData(mDevice, write_ptr, to_write/frame_size);
mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
@ -128,124 +127,125 @@ int SolarisBackend::mixerProc()
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue;
ERR("write failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed to write playback samples: %s",
strerror(errno));
mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno));
break;
}
to_write -= wrote;
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
}
}
unlock();
return 0;
}
ALCenum SolarisBackend::open(const ALCchar *name)
void SolarisBackend::open(const char *name)
{
if(!name)
name = solaris_device;
else if(strcmp(name, solaris_device) != 0)
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
mFd = ::open(solaris_driver, O_WRONLY);
if(mFd == -1)
{
ERR("Could not open %s: %s\n", solaris_driver, strerror(errno));
return ALC_INVALID_VALUE;
}
int fd{::open(solaris_driver.c_str(), O_WRONLY)};
if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s",
solaris_driver.c_str(), strerror(errno)};
if(mFd != -1)
::close(mFd);
mFd = fd;
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean SolarisBackend::reset()
bool SolarisBackend::reset()
{
audio_info_t info;
AUDIO_INITINFO(&info);
info.play.sample_rate = mDevice->Frequency;
if(mDevice->FmtChans != DevFmtMono)
mDevice->FmtChans = DevFmtStereo;
ALsizei numChannels{mDevice->channelsFromFmt()};
info.play.channels = numChannels;
info.play.channels = mDevice->channelsFromFmt();
switch(mDevice->FmtType)
{
case DevFmtByte:
info.play.precision = 8;
info.play.encoding = AUDIO_ENCODING_LINEAR;
break;
case DevFmtUByte:
info.play.precision = 8;
info.play.encoding = AUDIO_ENCODING_LINEAR8;
break;
case DevFmtUShort:
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
mDevice->FmtType = DevFmtShort;
/* fall-through */
case DevFmtShort:
info.play.precision = 16;
info.play.encoding = AUDIO_ENCODING_LINEAR;
break;
case DevFmtByte:
info.play.precision = 8;
info.play.encoding = AUDIO_ENCODING_LINEAR;
break;
case DevFmtUByte:
info.play.precision = 8;
info.play.encoding = AUDIO_ENCODING_LINEAR8;
break;
case DevFmtUShort:
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
mDevice->FmtType = DevFmtShort;
/* fall-through */
case DevFmtShort:
info.play.precision = 16;
info.play.encoding = AUDIO_ENCODING_LINEAR;
break;
}
ALsizei frameSize{numChannels * mDevice->bytesFromFmt()};
info.play.buffer_size = mDevice->BufferSize * frameSize;
info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt();
if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
{
ERR("ioctl failed: %s\n", strerror(errno));
return ALC_FALSE;
return false;
}
if(mDevice->channelsFromFmt() != (ALsizei)info.play.channels)
if(mDevice->channelsFromFmt() != info.play.channels)
{
ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
info.play.channels);
return ALC_FALSE;
if(info.play.channels >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(info.play.channels == 1)
mDevice->FmtChans = DevFmtMono;
else
throw al::backend_exception{al::backend_error::DeviceError,
"Got %u device channels", info.play.channels};
}
if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) ||
(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) ||
(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) ||
(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt)))
if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8)
mDevice->FmtType = DevFmtUByte;
else if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR)
mDevice->FmtType = DevFmtByte;
else if(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR)
mDevice->FmtType = DevFmtShort;
else if(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR)
mDevice->FmtType = DevFmtInt;
else
{
ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType),
info.play.precision, info.play.encoding);
return ALC_FALSE;
ERR("Got unhandled sample type: %d (0x%x)\n", info.play.precision, info.play.encoding);
return false;
}
uint frame_size{mDevice->bytesFromFmt() * info.play.channels};
mFrameStep = info.play.channels;
mDevice->Frequency = info.play.sample_rate;
mDevice->BufferSize = info.play.buffer_size / frameSize;
mDevice->BufferSize = info.play.buffer_size / frame_size;
/* How to get the actual period size/count? */
mDevice->UpdateSize = mDevice->BufferSize / 2;
SetDefaultChannelOrder(mDevice);
setDefaultChannelOrder();
mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
std::fill(mBuffer.begin(), mBuffer.end(), 0);
mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
return ALC_TRUE;
return true;
}
ALCboolean SolarisBackend::start()
void SolarisBackend::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
catch(...) {
}
return ALC_FALSE;
}
void SolarisBackend::stop()
@ -268,33 +268,34 @@ BackendFactory &SolarisBackendFactory::getFactory()
bool SolarisBackendFactory::init()
{
ConfigValueStr(nullptr, "solaris", "device", &solaris_driver);
if(auto devopt = ConfigValueStr(nullptr, "solaris", "device"))
solaris_driver = std::move(*devopt);
return true;
}
bool SolarisBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
void SolarisBackendFactory::probe(DevProbe type, std::string *outnames)
std::string SolarisBackendFactory::probe(BackendType type)
{
std::string outnames;
switch(type)
{
case DevProbe::Playback:
{
#ifdef HAVE_STAT
struct stat buf;
if(stat(solaris_driver, &buf) == 0)
#endif
outnames->append(solaris_device, sizeof(solaris_device));
}
break;
case BackendType::Playback:
{
struct stat buf;
if(stat(solaris_driver.c_str(), &buf) == 0)
outnames.append(solaris_device, sizeof(solaris_device));
}
break;
case DevProbe::Capture:
break;
case BackendType::Capture:
break;
}
return outnames;
}
BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new SolarisBackend{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/solaris.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_SOLARIS_H
#define BACKENDS_SOLARIS_H
#include "backends/base.h"
#include "base.h"
struct SolarisBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 722
- 611
modules/openal-soft/Alc/backends/wasapi.cpp
File diff suppressed because it is too large
View File


+ 3
- 3
modules/openal-soft/Alc/backends/wasapi.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_WASAPI_H
#define BACKENDS_WASAPI_H
#include "backends/base.h"
#include "base.h"
struct WasapiBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 135
- 129
modules/openal-soft/Alc/backends/wave.cpp View File

@ -20,22 +20,31 @@
#include "config.h"
#include "backends/wave.h"
#include "wave.h"
#include <cstdlib>
#include <cstdio>
#include <memory.h>
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <chrono>
#include <thread>
#include <vector>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <exception>
#include <functional>
#include <thread>
#include "alMain.h"
#include "alu.h"
#include "alconfig.h"
#include "compat.h"
#include "albit.h"
#include "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "opthelpers.h"
#include "strutils.h"
#include "threads.h"
#include "vector.h"
namespace {
@ -44,61 +53,63 @@ using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
constexpr ALCchar waveDevice[] = "Wave File Writer";
using ubyte = unsigned char;
using ushort = unsigned short;
constexpr char waveDevice[] = "Wave File Writer";
constexpr ALubyte SUBTYPE_PCM[]{
constexpr ubyte SUBTYPE_PCM[]{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
constexpr ALubyte SUBTYPE_FLOAT[]{
constexpr ubyte SUBTYPE_FLOAT[]{
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
constexpr ALubyte SUBTYPE_BFORMAT_PCM[]{
constexpr ubyte SUBTYPE_BFORMAT_PCM[]{
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
constexpr ALubyte SUBTYPE_BFORMAT_FLOAT[]{
constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
void fwrite16le(ALushort val, FILE *f)
void fwrite16le(ushort val, FILE *f)
{
ALubyte data[2]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff) };
ubyte data[2]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff) };
fwrite(data, 1, 2, f);
}
void fwrite32le(ALuint val, FILE *f)
void fwrite32le(uint val, FILE *f)
{
ALubyte data[4]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff),
static_cast<ALubyte>((val>>16)&0xff), static_cast<ALubyte>((val>>24)&0xff) };
ubyte data[4]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff) };
fwrite(data, 1, 4, f);
}
struct WaveBackend final : public BackendBase {
WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { }
WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { }
~WaveBackend() override;
int mixerProc();
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
FILE *mFile{nullptr};
long mDataStart{-1};
al::vector<ALbyte> mBuffer;
al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "WaveBackend::"; }
DEF_NEWDEL(WaveBackend)
};
@ -115,12 +126,13 @@ int WaveBackend::mixerProc()
althrd_setname(MIXER_THREAD_NAME);
const ALsizei frameSize{mDevice->frameSizeFromFmt()};
const size_t frameStep{mDevice->channelsFromFmt()};
const size_t frameSize{mDevice->frameSizeFromFmt()};
int64_t done{0};
auto start = std::chrono::steady_clock::now();
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
auto now = std::chrono::steady_clock::now();
@ -134,45 +146,35 @@ int WaveBackend::mixerProc()
}
while(avail-done >= mDevice->UpdateSize)
{
lock();
aluMixData(mDevice, mBuffer.data(), mDevice->UpdateSize);
unlock();
mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep);
done += mDevice->UpdateSize;
if(!IS_LITTLE_ENDIAN)
if(al::endian::native != al::endian::little)
{
const ALsizei bytesize{mDevice->bytesFromFmt()};
ALsizei i;
const uint bytesize{mDevice->bytesFromFmt()};
if(bytesize == 2)
{
ALushort *samples = reinterpret_cast<ALushort*>(mBuffer.data());
const auto len = static_cast<ALsizei>(mBuffer.size() / 2);
for(i = 0;i < len;i++)
{
ALushort samp = samples[i];
samples[i] = (samp>>8) | (samp<<8);
}
const size_t len{mBuffer.size() & ~size_t{1}};
for(size_t i{0};i < len;i+=2)
std::swap(mBuffer[i], mBuffer[i+1]);
}
else if(bytesize == 4)
{
ALuint *samples = reinterpret_cast<ALuint*>(mBuffer.data());
const auto len = static_cast<ALsizei>(mBuffer.size() / 4);
for(i = 0;i < len;i++)
const size_t len{mBuffer.size() & ~size_t{3}};
for(size_t i{0};i < len;i+=4)
{
ALuint samp = samples[i];
samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
((samp<<8)&0x00ff0000) | (samp<<24);
std::swap(mBuffer[i ], mBuffer[i+3]);
std::swap(mBuffer[i+1], mBuffer[i+2]);
}
}
}
size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
(void)fs;
if(ferror(mFile))
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
if(fs < mDevice->UpdateSize || ferror(mFile))
{
ERR("Error writing to file\n");
aluHandleDisconnect(mDevice, "Failed to write playback samples");
mDevice->handleDisconnect("Failed to write playback samples");
break;
}
}
@ -185,47 +187,48 @@ int WaveBackend::mixerProc()
if(done >= mDevice->Frequency)
{
seconds s{done/mDevice->Frequency};
done %= mDevice->Frequency;
start += s;
done -= mDevice->Frequency*s.count();
}
}
return 0;
}
ALCenum WaveBackend::open(const ALCchar *name)
void WaveBackend::open(const char *name)
{
const char *fname{GetConfigValue(nullptr, "wave", "file", "")};
if(!fname[0]) return ALC_INVALID_VALUE;
auto fname = ConfigValueStr(nullptr, "wave", "file");
if(!fname) throw al::backend_exception{al::backend_error::NoDevice,
"No wave output filename"};
if(!name)
name = waveDevice;
else if(strcmp(name, waveDevice) != 0)
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
/* There's only one "device", so if it's already open, we're done. */
if(mFile) return;
#ifdef _WIN32
{
std::wstring wname = utf8_to_wstr(fname);
std::wstring wname{utf8_to_wstr(fname->c_str())};
mFile = _wfopen(wname.c_str(), L"wb");
}
#else
mFile = fopen(fname, "wb");
mFile = fopen(fname->c_str(), "wb");
#endif
if(!mFile)
{
ERR("Could not open file '%s': %s\n", fname, strerror(errno));
return ALC_INVALID_VALUE;
}
throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s",
fname->c_str(), strerror(errno)};
mDevice->DeviceName = name;
return ALC_NO_ERROR;
}
ALCboolean WaveBackend::reset()
bool WaveBackend::reset()
{
ALuint channels=0, bytes=0, chanmask=0;
int isbformat = 0;
uint channels{0}, bytes{0}, chanmask{0};
bool isbformat{false};
size_t val;
fseek(mFile, 0, SEEK_SET);
@ -239,38 +242,37 @@ ALCboolean WaveBackend::reset()
switch(mDevice->FmtType)
{
case DevFmtByte:
mDevice->FmtType = DevFmtUByte;
break;
case DevFmtUShort:
mDevice->FmtType = DevFmtShort;
break;
case DevFmtUInt:
mDevice->FmtType = DevFmtInt;
break;
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtFloat:
break;
case DevFmtByte:
mDevice->FmtType = DevFmtUByte;
break;
case DevFmtUShort:
mDevice->FmtType = DevFmtShort;
break;
case DevFmtUInt:
mDevice->FmtType = DevFmtInt;
break;
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtFloat:
break;
}
switch(mDevice->FmtChans)
{
case DevFmtMono: chanmask = 0x04; break;
case DevFmtStereo: chanmask = 0x01 | 0x02; break;
case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break;
case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; 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 DevFmtAmbi3D:
/* .amb output requires FuMa */
mDevice->mAmbiOrder = mini(mDevice->mAmbiOrder, 3);
mDevice->mAmbiLayout = AmbiLayout::FuMa;
mDevice->mAmbiScale = AmbiNorm::FuMa;
isbformat = 1;
chanmask = 0;
break;
case DevFmtMono: chanmask = 0x04; break;
case DevFmtStereo: chanmask = 0x01 | 0x02; break;
case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break;
case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break;
case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtAmbi3D:
/* .amb output requires FuMa */
mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
mDevice->mAmbiLayout = DevAmbiLayout::FuMa;
mDevice->mAmbiScale = DevAmbiScaling::FuMa;
isbformat = true;
chanmask = 0;
break;
}
bytes = mDevice->bytesFromFmt();
channels = mDevice->channelsFromFmt();
@ -288,25 +290,25 @@ ALCboolean WaveBackend::reset()
// 16-bit val, format type id (extensible: 0xFFFE)
fwrite16le(0xFFFE, mFile);
// 16-bit val, channel count
fwrite16le(channels, mFile);
fwrite16le(static_cast<ushort>(channels), mFile);
// 32-bit val, frequency
fwrite32le(mDevice->Frequency, mFile);
// 32-bit val, bytes per second
fwrite32le(mDevice->Frequency * channels * bytes, mFile);
// 16-bit val, frame size
fwrite16le(channels * bytes, mFile);
fwrite16le(static_cast<ushort>(channels * bytes), mFile);
// 16-bit val, bits per sample
fwrite16le(bytes * 8, mFile);
fwrite16le(static_cast<ushort>(bytes * 8), mFile);
// 16-bit val, extra byte count
fwrite16le(22, mFile);
// 16-bit val, valid bits per sample
fwrite16le(bytes * 8, mFile);
fwrite16le(static_cast<ushort>(bytes * 8), mFile);
// 32-bit val, channel mask
fwrite32le(chanmask, mFile);
// 16 byte GUID, sub-type format
val = fwrite((mDevice->FmtType == DevFmtFloat) ?
(isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
(isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
(isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
(isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
(void)val;
fputs("data", mFile);
@ -315,31 +317,30 @@ ALCboolean WaveBackend::reset()
if(ferror(mFile))
{
ERR("Error writing header: %s\n", strerror(errno));
return ALC_FALSE;
return false;
}
mDataStart = ftell(mFile);
SetDefaultWFXChannelOrder(mDevice);
setDefaultWFXChannelOrder();
const ALuint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
mBuffer.resize(bufsize);
return ALC_TRUE;
return true;
}
ALCboolean WaveBackend::start()
void WaveBackend::start()
{
if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0)
WARN("Failed to seek on output file\n");
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: %s\n", e.what());
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
catch(...) {
}
return ALC_FALSE;
}
void WaveBackend::stop()
@ -348,14 +349,17 @@ void WaveBackend::stop()
return;
mThread.join();
long size{ftell(mFile)};
if(size > 0)
if(mDataStart > 0)
{
long dataLen{size - mDataStart};
if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
fwrite32le(dataLen, mFile); // 'data' header len
if(fseek(mFile, 4, SEEK_SET) == 0)
fwrite32le(size-8, mFile); // 'WAVE' header len
long size{ftell(mFile)};
if(size > 0)
{
long dataLen{size - mDataStart};
if(fseek(mFile, 4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(size-8), mFile); // 'WAVE' header len
if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(dataLen), mFile); // 'data' header len
}
}
}
@ -368,20 +372,22 @@ bool WaveBackendFactory::init()
bool WaveBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
void WaveBackendFactory::probe(DevProbe type, std::string *outnames)
std::string WaveBackendFactory::probe(BackendType type)
{
std::string outnames;
switch(type)
{
case DevProbe::Playback:
/* Includes null char. */
outnames->append(waveDevice, sizeof(waveDevice));
break;
case DevProbe::Capture:
break;
case BackendType::Playback:
/* Includes null char. */
outnames.append(waveDevice, sizeof(waveDevice));
break;
case BackendType::Capture:
break;
}
return outnames;
}
BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new WaveBackend{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/wave.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_WAVE_H
#define BACKENDS_WAVE_H
#include "backends/base.h"
#include "base.h"
struct WaveBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 156
- 167
modules/openal-soft/Alc/backends/winmm.cpp View File

@ -20,7 +20,7 @@
#include "config.h"
#include "backends/winmm.h"
#include "winmm.h"
#include <stdlib.h>
#include <stdio.h>
@ -28,6 +28,7 @@
#include <windows.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <array>
#include <atomic>
@ -37,11 +38,13 @@
#include <algorithm>
#include <functional>
#include "alMain.h"
#include "alu.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "strutils.h"
#include "threads.h"
#include "compat.h"
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
@ -62,9 +65,9 @@ void ProbePlaybackDevices(void)
{
PlaybackDevices.clear();
ALuint numdevs{waveOutGetNumDevs()};
UINT numdevs{waveOutGetNumDevs()};
PlaybackDevices.reserve(numdevs);
for(ALuint i{0};i < numdevs;i++)
for(UINT i{0};i < numdevs;++i)
{
std::string dname;
@ -93,9 +96,9 @@ void ProbeCaptureDevices(void)
{
CaptureDevices.clear();
ALuint numdevs{waveInGetNumDevs()};
UINT numdevs{waveInGetNumDevs()};
CaptureDevices.reserve(numdevs);
for(ALuint i{0};i < numdevs;i++)
for(UINT i{0};i < numdevs;++i)
{
std::string dname;
@ -122,22 +125,23 @@ void ProbeCaptureDevices(void)
struct WinMMPlayback final : public BackendBase {
WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~WinMMPlayback() override;
static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2);
void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2);
void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
{ reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); }
int mixerProc();
ALCenum open(const ALCchar *name) override;
ALCboolean reset() override;
ALCboolean start() override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
std::atomic<ALuint> mWritable{0u};
std::atomic<uint> mWritable{0u};
al::semaphore mSem;
int mIdx{0};
uint mIdx{0u};
std::array<WAVEHDR,4> mWaveBuffer{};
HWAVEOUT mOutHdl{nullptr};
@ -147,7 +151,6 @@ struct WinMMPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "WinMMPlayback::"; }
DEF_NEWDEL(WinMMPlayback)
};
@ -161,17 +164,12 @@ WinMMPlayback::~WinMMPlayback()
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
}
void CALLBACK WinMMPlayback::waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2)
{ reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); }
/* WinMMPlayback::waveOutProc
*
* Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
* completed and returns to the application (for more data)
*/
void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT UNUSED(device), UINT msg,
DWORD_PTR UNUSED(param1), DWORD_PTR UNUSED(param2))
void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
{
if(msg != WOM_DONE) return;
mWritable.fetch_add(1, std::memory_order_acq_rel);
@ -183,37 +181,33 @@ FORCE_ALIGN int WinMMPlayback::mixerProc()
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
lock();
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
ALsizei todo = mWritable.load(std::memory_order_acquire);
uint todo{mWritable.load(std::memory_order_acquire)};
if(todo < 1)
{
unlock();
mSem.wait();
lock();
continue;
}
int widx{mIdx};
size_t widx{mIdx};
do {
WAVEHDR &waveHdr = mWaveBuffer[widx];
widx = (widx+1) % mWaveBuffer.size();
if(++widx == mWaveBuffer.size()) widx = 0;
aluMixData(mDevice, waveHdr.lpData, mDevice->UpdateSize);
mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels);
mWritable.fetch_sub(1, std::memory_order_acq_rel);
waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
} while(--todo);
mIdx = widx;
mIdx = static_cast<uint>(widx);
}
unlock();
return 0;
}
ALCenum WinMMPlayback::open(const ALCchar *name)
void WinMMPlayback::open(const char *name)
{
if(PlaybackDevices.empty())
ProbePlaybackDevices();
@ -222,52 +216,60 @@ ALCenum WinMMPlayback::open(const ALCchar *name)
auto iter = name ?
std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
PlaybackDevices.cbegin();
if(iter == PlaybackDevices.cend()) return ALC_INVALID_VALUE;
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
DevFmtType fmttype{mDevice->FmtType};
retry_open:
mFormat = WAVEFORMATEX{};
if(mDevice->FmtType == DevFmtFloat)
WAVEFORMATEX format{};
if(fmttype == DevFmtFloat)
{
mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
mFormat.wBitsPerSample = 32;
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = 32;
}
else
{
mFormat.wFormatTag = WAVE_FORMAT_PCM;
if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte)
mFormat.wBitsPerSample = 8;
format.wFormatTag = WAVE_FORMAT_PCM;
if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
format.wBitsPerSample = 8;
else
mFormat.wBitsPerSample = 16;
format.wBitsPerSample = 16;
}
mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
mFormat.nBlockAlign = mFormat.wBitsPerSample * mFormat.nChannels / 8;
mFormat.nSamplesPerSec = mDevice->Frequency;
mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
mFormat.cbSize = 0;
MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat, (DWORD_PTR)&WinMMPlayback::waveOutProcC,
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(mDevice->FmtType == DevFmtFloat)
if(fmttype == DevFmtFloat)
{
mDevice->FmtType = DevFmtShort;
fmttype = DevFmtShort;
goto retry_open;
}
ERR("waveOutOpen failed: %u\n", res);
return ALC_INVALID_VALUE;
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
}
if(mOutHdl)
waveOutClose(mOutHdl);
mOutHdl = outHandle;
mFormat = format;
mDevice->DeviceName = PlaybackDevices[DeviceID];
return ALC_NO_ERROR;
}
ALCboolean WinMMPlayback::reset()
bool WinMMPlayback::reset()
{
mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
mFormat.nSamplesPerSec / mDevice->Frequency);
mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3;
mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u;
mDevice->UpdateSize = mDevice->BufferSize / 4;
mDevice->Frequency = mFormat.nSamplesPerSec;
@ -278,7 +280,7 @@ ALCboolean WinMMPlayback::reset()
else
{
ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
return ALC_FALSE;
return false;
}
}
else if(mFormat.wFormatTag == WAVE_FORMAT_PCM)
@ -290,27 +292,34 @@ ALCboolean WinMMPlayback::reset()
else
{
ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
return ALC_FALSE;
return false;
}
}
else
{
ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
return ALC_FALSE;
return false;
}
if(mFormat.nChannels == 2)
uint chanmask{};
if(mFormat.nChannels >= 2)
{
mDevice->FmtChans = DevFmtStereo;
chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
}
else if(mFormat.nChannels == 1)
{
mDevice->FmtChans = DevFmtMono;
chanmask = SPEAKER_FRONT_CENTER;
}
else
{
ERR("Unhandled channel count: %d\n", mFormat.nChannels);
return ALC_FALSE;
return false;
}
SetDefaultWFXChannelOrder(mDevice);
setChannelOrderFromWFXMask(chanmask);
ALuint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()};
uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
al_free(mWaveBuffer[0].lpData);
mWaveBuffer[0] = WAVEHDR{};
@ -324,28 +333,23 @@ ALCboolean WinMMPlayback::reset()
}
mIdx = 0;
return ALC_TRUE;
return true;
}
ALCboolean WinMMPlayback::start()
void WinMMPlayback::start()
{
try {
std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
[this](WAVEHDR &waveHdr) -> void
{ waveOutPrepareHeader(mOutHdl, &waveHdr, static_cast<UINT>(sizeof(WAVEHDR))); }
);
mWritable.store(static_cast<ALuint>(mWaveBuffer.size()), std::memory_order_release);
for(auto &waveHdr : mWaveBuffer)
waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR));
mWritable.store(static_cast<uint>(mWaveBuffer.size()), std::memory_order_release);
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: %s\n", e.what());
}
catch(...) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
return ALC_FALSE;
}
void WinMMPlayback::stop()
@ -356,32 +360,31 @@ void WinMMPlayback::stop()
while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
mSem.wait();
std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
[this](WAVEHDR &waveHdr) -> void
{ waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); }
);
for(auto &waveHdr : mWaveBuffer)
waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR));
mWritable.store(0, std::memory_order_release);
}
struct WinMMCapture final : public BackendBase {
WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { }
WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~WinMMCapture() override;
static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2);
void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2);
void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
{ reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); }
int captureProc();
ALCenum open(const ALCchar *name) override;
ALCboolean start() override;
void open(const char *name) override;
void start() override;
void stop() override;
ALCenum captureSamples(void *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
std::atomic<ALuint> mReadable{0u};
std::atomic<uint> mReadable{0u};
al::semaphore mSem;
int mIdx{0};
uint mIdx{0};
std::array<WAVEHDR,4> mWaveBuffer{};
HWAVEIN mInHdl{nullptr};
@ -393,7 +396,6 @@ struct WinMMCapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
static constexpr inline const char *CurrentPrefix() noexcept { return "WinMMCapture::"; }
DEF_NEWDEL(WinMMCapture)
};
@ -408,16 +410,12 @@ WinMMCapture::~WinMMCapture()
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
}
void CALLBACK WinMMCapture::waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2)
{ reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); }
/* WinMMCapture::waveInProc
*
* Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
* completed and returns to the application (with more data).
*/
void CALLBACK WinMMCapture::waveInProc(HWAVEIN UNUSED(device), UINT msg,
DWORD_PTR UNUSED(param1), DWORD_PTR UNUSED(param2))
void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
{
if(msg != WIM_DATA) return;
mReadable.fetch_add(1, std::memory_order_acq_rel);
@ -428,20 +426,17 @@ int WinMMCapture::captureProc()
{
althrd_setname(RECORD_THREAD_NAME);
lock();
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
ALuint todo{mReadable.load(std::memory_order_acquire)};
uint todo{mReadable.load(std::memory_order_acquire)};
if(todo < 1)
{
unlock();
mSem.wait();
lock();
continue;
}
int widx{mIdx};
size_t widx{mIdx};
do {
WAVEHDR &waveHdr = mWaveBuffer[widx];
widx = (widx+1) % mWaveBuffer.size();
@ -450,15 +445,14 @@ int WinMMCapture::captureProc()
mReadable.fetch_sub(1, std::memory_order_acq_rel);
waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
} while(--todo);
mIdx = widx;
mIdx = static_cast<uint>(widx);
}
unlock();
return 0;
}
ALCenum WinMMCapture::open(const ALCchar *name)
void WinMMCapture::open(const char *name)
{
if(CaptureDevices.empty())
ProbeCaptureDevices();
@ -467,55 +461,56 @@ ALCenum WinMMCapture::open(const ALCchar *name)
auto iter = name ?
std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
CaptureDevices.cbegin();
if(iter == CaptureDevices.cend()) return ALC_INVALID_VALUE;
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
switch(mDevice->FmtChans)
{
case DevFmtMono:
case DevFmtStereo:
break;
case DevFmtQuad:
case DevFmtX51:
case DevFmtX51Rear:
case DevFmtX61:
case DevFmtX71:
case DevFmtAmbi3D:
return ALC_INVALID_ENUM;
case DevFmtMono:
case DevFmtStereo:
break;
case DevFmtQuad:
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
switch(mDevice->FmtType)
{
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtFloat:
break;
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
return ALC_INVALID_ENUM;
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtFloat:
break;
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
DevFmtTypeString(mDevice->FmtType)};
}
mFormat = WAVEFORMATEX{};
mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ?
WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
mFormat.nChannels = mDevice->channelsFromFmt();
mFormat.wBitsPerSample = mDevice->bytesFromFmt() * 8;
mFormat.nBlockAlign = mFormat.wBitsPerSample * mFormat.nChannels / 8;
mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
mFormat.nSamplesPerSec = mDevice->Frequency;
mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
mFormat.cbSize = 0;
MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat, (DWORD_PTR)&WinMMCapture::waveInProcC,
MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat,
reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res != MMSYSERR_NOERROR)
{
ERR("waveInOpen failed: %u\n", res);
return ALC_INVALID_VALUE;
}
throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res};
// Ensure each buffer is 50ms each
DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
@ -523,15 +518,14 @@ ALCenum WinMMCapture::open(const ALCchar *name)
// Allocate circular memory buffer for the captured audio
// Make sure circular buffer is at least 100ms in size
ALuint CapturedDataSize{mDevice->BufferSize};
CapturedDataSize = static_cast<ALuint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
uint CapturedDataSize{mDevice->BufferSize};
CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
mRing = CreateRingBuffer(CapturedDataSize, mFormat.nBlockAlign, false);
if(!mRing) return ALC_INVALID_VALUE;
mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
al_free(mWaveBuffer[0].lpData);
mWaveBuffer[0] = WAVEHDR{};
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize*4));
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
mWaveBuffer[0].dwBufferLength = BufferSize;
for(size_t i{1};i < mWaveBuffer.size();++i)
{
@ -541,10 +535,9 @@ ALCenum WinMMCapture::open(const ALCchar *name)
}
mDevice->DeviceName = CaptureDevices[DeviceID];
return ALC_NO_ERROR;
}
ALCboolean WinMMCapture::start()
void WinMMCapture::start()
{
try {
for(size_t i{0};i < mWaveBuffer.size();++i)
@ -557,14 +550,11 @@ ALCboolean WinMMCapture::start()
mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
waveInStart(mInHdl);
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: %s\n", e.what());
}
catch(...) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording thread: %s", e.what()};
}
return ALC_FALSE;
}
void WinMMCapture::stop()
@ -586,14 +576,11 @@ void WinMMCapture::stop()
mIdx = 0;
}
ALCenum WinMMCapture::captureSamples(void *buffer, ALCuint samples)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
void WinMMCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
ALCuint WinMMCapture::availableSamples()
{ return (ALCuint)mRing->readSpace(); }
uint WinMMCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
} // namespace
@ -604,31 +591,33 @@ bool WinMMBackendFactory::init()
bool WinMMBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
void WinMMBackendFactory::probe(DevProbe type, std::string *outnames)
std::string WinMMBackendFactory::probe(BackendType type)
{
auto add_device = [outnames](const std::string &dname) -> void
std::string outnames;
auto add_device = [&outnames](const std::string &dname) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
if(!dname.empty())
outnames->append(dname.c_str(), dname.length()+1);
outnames.append(dname.c_str(), dname.length()+1);
};
switch(type)
{
case DevProbe::Playback:
ProbePlaybackDevices();
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case DevProbe::Capture:
ProbeCaptureDevices();
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
case BackendType::Playback:
ProbePlaybackDevices();
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
ProbeCaptureDevices();
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
return outnames;
}
BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr WinMMBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new WinMMPlayback{device}};

+ 3
- 3
modules/openal-soft/Alc/backends/winmm.h View File

@ -1,7 +1,7 @@
#ifndef BACKENDS_WINMM_H
#define BACKENDS_WINMM_H
#include "backends/base.h"
#include "base.h"
struct WinMMBackendFactory final : public BackendFactory {
public:
@ -9,9 +9,9 @@ public:
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};

+ 0
- 201
modules/openal-soft/Alc/bformatdec.cpp View File

@ -1,201 +0,0 @@
#include "config.h"
#include <cmath>
#include <array>
#include <vector>
#include <numeric>
#include <algorithm>
#include <functional>
#include "bformatdec.h"
#include "ambdec.h"
#include "filters/splitter.h"
#include "alu.h"
#include "threads.h"
#include "almalloc.h"
namespace {
using namespace std::placeholders;
constexpr ALfloat Ambi3DDecoderHFScale[MAX_AMBI_ORDER+1] = {
1.00000000e+00f, 1.00000000e+00f
};
constexpr ALfloat Ambi3DDecoderHFScale2O[MAX_AMBI_ORDER+1] = {
7.45355990e-01f, 1.00000000e+00f
};
constexpr ALfloat Ambi3DDecoderHFScale3O[MAX_AMBI_ORDER+1] = {
5.89792205e-01f, 8.79693856e-01f
};
inline auto GetDecoderHFScales(ALsizei order) noexcept -> const ALfloat(&)[MAX_AMBI_ORDER+1]
{
if(order >= 3) return Ambi3DDecoderHFScale3O;
if(order == 2) return Ambi3DDecoderHFScale2O;
return Ambi3DDecoderHFScale;
}
inline auto GetAmbiScales(AmbDecScale scaletype) noexcept -> const std::array<float,MAX_AMBI_CHANNELS>&
{
if(scaletype == AmbDecScale::FuMa) return AmbiScale::FromFuMa;
if(scaletype == AmbDecScale::SN3D) return AmbiScale::FromSN3D;
return AmbiScale::FromN3D;
}
} // namespace
BFormatDec::BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALsizei inchans,
const ALuint srate, const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS])
{
mDualBand = allow_2band && (conf->FreqBands == 2);
if(!mDualBand)
mSamples.resize(2);
else
{
ASSUME(inchans > 0);
mSamples.resize(inchans * 2);
mSamplesHF = mSamples.data();
mSamplesLF = mSamplesHF + inchans;
}
mNumChannels = inchans;
mEnabled = std::accumulate(std::begin(chanmap), std::begin(chanmap)+conf->Speakers.size(), 0u,
[](ALuint mask, const ALsizei &chan) noexcept -> ALuint
{ return mask | (1 << chan); }
);
const ALfloat xover_norm{conf->XOverFreq / static_cast<float>(srate)};
const bool periphonic{(conf->ChanMask&AMBI_PERIPHONIC_MASK) != 0};
const std::array<float,MAX_AMBI_CHANNELS> &coeff_scale = GetAmbiScales(conf->CoeffScale);
const size_t coeff_count{periphonic ? MAX_AMBI_CHANNELS : MAX_AMBI2D_CHANNELS};
if(!mDualBand)
{
for(size_t i{0u};i < conf->Speakers.size();i++)
{
ALfloat (&mtx)[MAX_AMBI_CHANNELS] = mMatrix.Single[chanmap[i]];
for(size_t j{0},k{0};j < coeff_count;j++)
{
const size_t l{periphonic ? j : AmbiIndex::From2D[j]};
if(!(conf->ChanMask&(1u<<l))) continue;
mtx[j] = conf->HFMatrix[i][k] / coeff_scale[l] *
((l>=9) ? conf->HFOrderGain[3] :
(l>=4) ? conf->HFOrderGain[2] :
(l>=1) ? conf->HFOrderGain[1] : conf->HFOrderGain[0]);
++k;
}
}
}
else
{
mXOver[0].init(xover_norm);
std::fill(std::begin(mXOver)+1, std::end(mXOver), mXOver[0]);
const float ratio{std::pow(10.0f, conf->XOverRatio / 40.0f)};
for(size_t i{0u};i < conf->Speakers.size();i++)
{
ALfloat (&mtx)[sNumBands][MAX_AMBI_CHANNELS] = mMatrix.Dual[chanmap[i]];
for(size_t j{0},k{0};j < coeff_count;j++)
{
const size_t l{periphonic ? j : AmbiIndex::From2D[j]};
if(!(conf->ChanMask&(1u<<l))) continue;
mtx[sHFBand][j] = conf->HFMatrix[i][k] / coeff_scale[l] *
((l>=9) ? conf->HFOrderGain[3] :
(l>=4) ? conf->HFOrderGain[2] :
(l>=1) ? conf->HFOrderGain[1] : conf->HFOrderGain[0]) * ratio;
mtx[sLFBand][j] = conf->LFMatrix[i][k] / coeff_scale[l] *
((l>=9) ? conf->LFOrderGain[3] :
(l>=4) ? conf->LFOrderGain[2] :
(l>=1) ? conf->LFOrderGain[1] : conf->LFOrderGain[0]) / ratio;
++k;
}
}
}
}
BFormatDec::BFormatDec(const ALsizei inchans, const ALsizei chancount,
const ChannelDec (&chancoeffs)[MAX_OUTPUT_CHANNELS],
const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS])
{
mSamples.resize(2);
mNumChannels = inchans;
ASSUME(chancount > 0);
mEnabled = std::accumulate(std::begin(chanmap), std::begin(chanmap)+chancount, 0u,
[](ALuint mask, const ALsizei &chan) noexcept -> ALuint
{ return mask | (1 << chan); }
);
const ChannelDec *incoeffs{chancoeffs};
auto set_coeffs = [this,inchans,&incoeffs](const ALsizei chanidx) noexcept -> void
{
ASSUME(chanidx >= 0);
ALfloat (&mtx)[MAX_AMBI_CHANNELS] = mMatrix.Single[chanidx];
const ALfloat (&coeffs)[MAX_AMBI_CHANNELS] = *(incoeffs++);
ASSUME(inchans > 0);
std::copy_n(std::begin(coeffs), inchans, std::begin(mtx));
};
std::for_each(chanmap, chanmap+chancount, set_coeffs);
}
void BFormatDec::process(ALfloat (*OutBuffer)[BUFFERSIZE], const ALsizei OutChannels, const ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo)
{
ASSUME(OutChannels > 0);
ASSUME(mNumChannels > 0);
if(mDualBand)
{
for(ALsizei i{0};i < mNumChannels;i++)
mXOver[i].process(mSamplesHF[i].data(), mSamplesLF[i].data(), InSamples[i],
SamplesToDo);
for(ALsizei chan{0};chan < OutChannels;chan++)
{
if(UNLIKELY(!(mEnabled&(1<<chan))))
continue;
MixRowSamples(OutBuffer[chan], mMatrix.Dual[chan][sHFBand],
&reinterpret_cast<ALfloat(&)[BUFFERSIZE]>(mSamplesHF[0]),
mNumChannels, 0, SamplesToDo);
MixRowSamples(OutBuffer[chan], mMatrix.Dual[chan][sLFBand],
&reinterpret_cast<ALfloat(&)[BUFFERSIZE]>(mSamplesLF[0]),
mNumChannels, 0, SamplesToDo);
}
}
else
{
for(ALsizei chan{0};chan < OutChannels;chan++)
{
if(UNLIKELY(!(mEnabled&(1<<chan))))
continue;
MixRowSamples(OutBuffer[chan], mMatrix.Single[chan], InSamples,
mNumChannels, 0, SamplesToDo);
}
}
}
std::array<ALfloat,MAX_AMBI_ORDER+1> BFormatDec::GetHFOrderScales(const ALsizei in_order, const ALsizei out_order) noexcept
{
std::array<ALfloat,MAX_AMBI_ORDER+1> ret{};
assert(out_order >= in_order);
ASSUME(out_order >= in_order);
const ALfloat (&target)[MAX_AMBI_ORDER+1] = GetDecoderHFScales(out_order);
const ALfloat (&input)[MAX_AMBI_ORDER+1] = GetDecoderHFScales(in_order);
for(ALsizei i{0};i < in_order+1;++i)
ret[i] = input[i] / target[i];
return ret;
}

+ 0
- 56
modules/openal-soft/Alc/bformatdec.h View File

@ -1,56 +0,0 @@
#ifndef BFORMATDEC_H
#define BFORMATDEC_H
#include "alMain.h"
#include "filters/splitter.h"
#include "ambidefs.h"
#include "almalloc.h"
struct AmbDecConf;
using ChannelDec = ALfloat[MAX_AMBI_CHANNELS];
class BFormatDec {
static constexpr size_t sHFBand{0};
static constexpr size_t sLFBand{1};
static constexpr size_t sNumBands{2};
ALuint mEnabled{0u}; /* Bitfield of enabled channels. */
union MatrixU {
ALfloat Dual[MAX_OUTPUT_CHANNELS][sNumBands][MAX_AMBI_CHANNELS];
ALfloat Single[MAX_OUTPUT_CHANNELS][MAX_AMBI_CHANNELS];
} mMatrix{};
/* NOTE: BandSplitter filters are unused with single-band decoding */
BandSplitter mXOver[MAX_AMBI_CHANNELS];
al::vector<std::array<ALfloat,BUFFERSIZE>, 16> mSamples;
/* These two alias into Samples */
std::array<ALfloat,BUFFERSIZE> *mSamplesHF{nullptr};
std::array<ALfloat,BUFFERSIZE> *mSamplesLF{nullptr};
ALsizei mNumChannels{0};
bool mDualBand{false};
public:
BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALsizei inchans,
const ALuint srate, const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]);
BFormatDec(const ALsizei inchans, const ALsizei chancount,
const ChannelDec (&chancoeffs)[MAX_OUTPUT_CHANNELS],
const ALsizei (&chanmap)[MAX_OUTPUT_CHANNELS]);
/* Decodes the ambisonic input to the given output channels. */
void process(ALfloat (*OutBuffer)[BUFFERSIZE], const ALsizei OutChannels,
const ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo);
/* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */
static std::array<ALfloat,MAX_AMBI_ORDER+1> GetHFOrderScales(const ALsizei in_order,
const ALsizei out_order) noexcept;
DEF_NEWDEL(BFormatDec)
};
#endif /* BFORMATDEC_H */

+ 0
- 236
modules/openal-soft/Alc/compat.h View File

@ -1,236 +0,0 @@
#ifndef AL_COMPAT_H
#define AL_COMPAT_H
#ifdef __cplusplus
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <array>
#include <string>
#include <fstream>
inline std::string wstr_to_utf8(const WCHAR *wstr)
{
std::string ret;
int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
if(len > 0)
{
ret.resize(len);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr);
ret.pop_back();
}
return ret;
}
inline std::wstring utf8_to_wstr(const char *str)
{
std::wstring ret;
int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
if(len > 0)
{
ret.resize(len);
MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len);
ret.pop_back();
}
return ret;
}
namespace al {
// Windows' std::ifstream fails with non-ANSI paths since the standard only
// specifies names using const char* (or std::string). MSVC has a non-standard
// extension using const wchar_t* (or std::wstring?) to handle Unicode paths,
// but not all Windows compilers support it. So we have to make our own istream
// that accepts UTF-8 paths and forwards to Unicode-aware I/O functions.
class filebuf final : public std::streambuf {
std::array<char_type,4096> mBuffer;
HANDLE mFile{INVALID_HANDLE_VALUE};
int_type underflow() override
{
if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr())
{
// Read in the next chunk of data, and set the pointers on success
DWORD got = 0;
if(ReadFile(mFile, mBuffer.data(), (DWORD)mBuffer.size(), &got, nullptr))
setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got);
}
if(gptr() == egptr())
return traits_type::eof();
return traits_type::to_int_type(*gptr());
}
pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override
{
if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
return traits_type::eof();
LARGE_INTEGER fpos;
switch(whence)
{
case std::ios_base::beg:
fpos.QuadPart = offset;
if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
return traits_type::eof();
break;
case std::ios_base::cur:
// If the offset remains in the current buffer range, just
// update the pointer.
if((offset >= 0 && offset < off_type(egptr()-gptr())) ||
(offset < 0 && -offset <= off_type(gptr()-eback())))
{
// Get the current file offset to report the correct read
// offset.
fpos.QuadPart = 0;
if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
return traits_type::eof();
setg(eback(), gptr()+offset, egptr());
return fpos.QuadPart - off_type(egptr()-gptr());
}
// Need to offset for the file offset being at egptr() while
// the requested offset is relative to gptr().
offset -= off_type(egptr()-gptr());
fpos.QuadPart = offset;
if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
return traits_type::eof();
break;
case std::ios_base::end:
fpos.QuadPart = offset;
if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_END))
return traits_type::eof();
break;
default:
return traits_type::eof();
}
setg(nullptr, nullptr, nullptr);
return fpos.QuadPart;
}
pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override
{
// Simplified version of seekoff
if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
return traits_type::eof();
LARGE_INTEGER fpos;
fpos.QuadPart = pos;
if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
return traits_type::eof();
setg(nullptr, nullptr, nullptr);
return fpos.QuadPart;
}
public:
bool open(const wchar_t *filename, std::ios_base::openmode mode)
{
if((mode&std::ios_base::out) || !(mode&std::ios_base::in))
return false;
HANDLE f{CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
if(f == INVALID_HANDLE_VALUE) return false;
if(mFile != INVALID_HANDLE_VALUE)
CloseHandle(mFile);
mFile = f;
setg(nullptr, nullptr, nullptr);
return true;
}
bool open(const char *filename, std::ios_base::openmode mode)
{
std::wstring wname{utf8_to_wstr(filename)};
return open(wname.c_str(), mode);
}
bool is_open() const noexcept { return mFile != INVALID_HANDLE_VALUE; }
filebuf() = default;
~filebuf() override
{
if(mFile != INVALID_HANDLE_VALUE)
CloseHandle(mFile);
mFile = INVALID_HANDLE_VALUE;
}
};
// Inherit from std::istream to use our custom streambuf
class ifstream final : public std::istream {
filebuf mStreamBuf;
public:
ifstream(const std::wstring &filename, std::ios_base::openmode mode = std::ios_base::in)
: ifstream(filename.c_str(), mode) { }
ifstream(const wchar_t *filename, std::ios_base::openmode mode = std::ios_base::in)
: std::istream{nullptr}
{
init(&mStreamBuf);
// Set the failbit if the file failed to open.
if((mode&std::ios_base::out) ||
!mStreamBuf.open(filename, mode|std::ios_base::in))
clear(failbit);
}
ifstream(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in)
: ifstream(filename.c_str(), mode) { }
ifstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in)
: std::istream{nullptr}
{
init(&mStreamBuf);
// Set the failbit if the file failed to open.
if((mode&std::ios_base::out) ||
!mStreamBuf.open(filename, mode|std::ios_base::in))
clear(failbit);
}
bool is_open() const noexcept { return mStreamBuf.is_open(); }
};
} // namespace al
#define HAVE_DYNLOAD 1
#else /* _WIN32 */
#include <fstream>
namespace al {
using filebuf = std::filebuf;
using ifstream = std::ifstream;
} // namespace al
#if defined(HAVE_DLFCN_H)
#define HAVE_DYNLOAD 1
#endif
#endif /* _WIN32 */
#include <string>
struct PathNamePair { std::string path, fname; };
const PathNamePair &GetProcBinary(void);
#ifdef HAVE_DYNLOAD
void *LoadLib(const char *name);
void CloseLib(void *handle);
void *GetSymbol(void *handle, const char *name);
#endif
#endif /* __cplusplus */
#endif /* AL_COMPAT_H */

+ 1299
- 0
modules/openal-soft/Alc/context.cpp
File diff suppressed because it is too large
View File


+ 504
- 0
modules/openal-soft/Alc/context.h View File

@ -0,0 +1,504 @@
#ifndef ALC_CONTEXT_H
#define ALC_CONTEXT_H
#include <atomic>
#include <memory>
#include <mutex>
#include <stdint.h>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "al/listener.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "atomic.h"
#include "core/context.h"
#include "intrusive_ptr.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include "al/eax_eax_call.h"
#include "al/eax_fx_slot_index.h"
#include "al/eax_fx_slots.h"
#include "al/eax_utils.h"
using EaxContextSharedDirtyFlagsValue = std::uint_least8_t;
struct EaxContextSharedDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxContextSharedDirtyFlagsValue primary_fx_slot_id : 1;
}; // EaxContextSharedDirtyFlags
using ContextDirtyFlagsValue = std::uint_least8_t;
struct ContextDirtyFlags
{
using EaxIsBitFieldStruct = bool;
ContextDirtyFlagsValue guidPrimaryFXSlotID : 1;
ContextDirtyFlagsValue flDistanceFactor : 1;
ContextDirtyFlagsValue flAirAbsorptionHF : 1;
ContextDirtyFlagsValue flHFReference : 1;
ContextDirtyFlagsValue flMacroFXFactor : 1;
}; // ContextDirtyFlags
struct EaxAlIsExtensionPresentResult
{
ALboolean is_present;
bool is_return;
}; // EaxAlIsExtensionPresentResult
#endif // ALSOFT_EAX
struct ALeffect;
struct ALeffectslot;
struct ALsource;
using uint = unsigned int;
struct SourceSubList {
uint64_t FreeMask{~0_u64};
ALsource *Sources{nullptr}; /* 64 */
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; }
};
struct EffectSlotSubList {
uint64_t FreeMask{~0_u64};
ALeffectslot *EffectSlots{nullptr}; /* 64 */
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; }
};
struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
const al::intrusive_ptr<ALCdevice> mALDevice;
/* Wet buffers used by effect slots. */
al::vector<WetBufferPtr> mWetBuffers;
bool mPropsDirty{true};
bool mDeferUpdates{false};
std::mutex mPropLock;
std::atomic<ALenum> mLastError{AL_NO_ERROR};
DistanceModel mDistanceModel{DistanceModel::Default};
bool mSourceDistanceModel{false};
float mDopplerFactor{1.0f};
float mDopplerVelocity{1.0f};
float mSpeedOfSound{SpeedOfSoundMetersPerSec};
float mAirAbsorptionGainHF{AirAbsorbGainHF};
std::mutex mEventCbLock;
ALEVENTPROCSOFT mEventCb{};
void *mEventParam{nullptr};
ALlistener mListener{};
al::vector<SourceSubList> mSourceList;
ALuint mNumSources{0};
std::mutex mSourceLock;
al::vector<EffectSlotSubList> mEffectSlotList;
ALuint mNumEffectSlots{0u};
std::mutex mEffectSlotLock;
/* Default effect slot */
std::unique_ptr<ALeffectslot> mDefaultSlot;
const char *mExtensionList{nullptr};
ALCcontext(al::intrusive_ptr<ALCdevice> device);
ALCcontext(const ALCcontext&) = delete;
ALCcontext& operator=(const ALCcontext&) = delete;
~ALCcontext();
void init();
/**
* Removes the context from its device and removes it from being current on
* the running thread or globally. Returns true if other contexts still
* exist on the device.
*/
bool deinit();
/**
* Defers/suspends updates for the given context's listener and sources.
* This does *NOT* stop mixing, but rather prevents certain property
* changes from taking effect. mPropLock must be held when called.
*/
void deferUpdates() noexcept { mDeferUpdates = true; }
/**
* Resumes update processing after being deferred. mPropLock must be held
* when called.
*/
void processUpdates()
{
if(std::exchange(mDeferUpdates, false))
applyAllUpdates();
}
/**
* Applies all pending updates for the context, listener, effect slots, and
* sources.
*/
void applyAllUpdates();
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
void setError(ALenum errorCode, const char *msg, ...);
/* Process-wide current context */
static std::atomic<ALCcontext*> sGlobalContext;
private:
/* Thread-local current context. */
static thread_local ALCcontext *sLocalContext;
/* Thread-local context handling. This handles attempting to release the
* context which may have been left current when the thread is destroyed.
*/
class ThreadCtx {
public:
~ThreadCtx();
void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; }
};
static thread_local ThreadCtx sThreadContext;
public:
/* HACK: MinGW generates bad code when accessing an extern thread_local
* object. Add a wrapper function for it that only accesses it where it's
* defined.
*/
#ifdef __MINGW32__
static ALCcontext *getThreadContext() noexcept;
static void setThreadContext(ALCcontext *context) noexcept;
#else
static ALCcontext *getThreadContext() noexcept { return sLocalContext; }
static void setThreadContext(ALCcontext *context) noexcept { sThreadContext.set(context); }
#endif
/* Default effect that applies to sources that don't have an effect on send 0. */
static ALeffect sDefaultEffect;
DEF_NEWDEL(ALCcontext)
#ifdef ALSOFT_EAX
public:
bool has_eax() const noexcept { return eax_is_initialized_; }
bool eax_is_capable() const noexcept;
void eax_uninitialize() noexcept;
ALenum eax_eax_set(
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_value,
ALuint property_value_size);
ALenum eax_eax_get(
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_value,
ALuint property_value_size);
void eax_update_filters();
void eax_commit_and_update_sources();
void eax_set_last_error() noexcept;
EaxFxSlotIndex eax_get_previous_primary_fx_slot_index() const noexcept
{ return eax_previous_primary_fx_slot_index_; }
EaxFxSlotIndex eax_get_primary_fx_slot_index() const noexcept
{ return eax_primary_fx_slot_index_; }
const ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) const
{ return eax_fx_slots_.get(fx_slot_index); }
ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index)
{ return eax_fx_slots_.get(fx_slot_index); }
void eax_commit_fx_slots()
{ eax_fx_slots_.commit(); }
private:
struct Eax
{
EAX50CONTEXTPROPERTIES context{};
}; // Eax
bool eax_is_initialized_{};
bool eax_is_tried_{};
bool eax_are_legacy_fx_slots_unlocked_{};
long eax_last_error_{};
unsigned long eax_speaker_config_{};
EaxFxSlotIndex eax_previous_primary_fx_slot_index_{};
EaxFxSlotIndex eax_primary_fx_slot_index_{};
EaxFxSlots eax_fx_slots_{};
EaxContextSharedDirtyFlags eax_context_shared_dirty_flags_{};
Eax eax_{};
Eax eax_d_{};
EAXSESSIONPROPERTIES eax_session_{};
ContextDirtyFlags eax_context_dirty_flags_{};
std::string eax_extension_list_{};
[[noreturn]]
static void eax_fail(
const char* message);
void eax_initialize_extensions();
void eax_initialize();
bool eax_has_no_default_effect_slot() const noexcept;
void eax_ensure_no_default_effect_slot() const;
bool eax_has_enough_aux_sends() const noexcept;
void eax_ensure_enough_aux_sends() const;
void eax_ensure_compatibility();
unsigned long eax_detect_speaker_configuration() const;
void eax_update_speaker_configuration();
void eax_set_last_error_defaults() noexcept;
void eax_set_session_defaults() noexcept;
void eax_set_context_defaults() noexcept;
void eax_set_defaults() noexcept;
void eax_initialize_sources();
void eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept;
void eax_dispatch_fx_slot(
const EaxEaxCall& eax_call);
void eax_dispatch_source(
const EaxEaxCall& eax_call);
void eax_get_primary_fx_slot_id(
const EaxEaxCall& eax_call);
void eax_get_distance_factor(
const EaxEaxCall& eax_call);
void eax_get_air_absorption_hf(
const EaxEaxCall& eax_call);
void eax_get_hf_reference(
const EaxEaxCall& eax_call);
void eax_get_last_error(
const EaxEaxCall& eax_call);
void eax_get_speaker_config(
const EaxEaxCall& eax_call);
void eax_get_session(
const EaxEaxCall& eax_call);
void eax_get_macro_fx_factor(
const EaxEaxCall& eax_call);
void eax_get_context_all(
const EaxEaxCall& eax_call);
void eax_get(
const EaxEaxCall& eax_call);
void eax_set_primary_fx_slot_id();
void eax_set_distance_factor();
void eax_set_air_absorbtion_hf();
void eax_set_hf_reference();
void eax_set_macro_fx_factor();
void eax_set_context();
void eax_initialize_fx_slots();
void eax_update_sources();
void eax_validate_primary_fx_slot_id(
const GUID& primary_fx_slot_id);
void eax_validate_distance_factor(
float distance_factor);
void eax_validate_air_absorption_hf(
float air_absorption_hf);
void eax_validate_hf_reference(
float hf_reference);
void eax_validate_speaker_config(
unsigned long speaker_config);
void eax_validate_session_eax_version(
unsigned long eax_version);
void eax_validate_session_max_active_sends(
unsigned long max_active_sends);
void eax_validate_session(
const EAXSESSIONPROPERTIES& eax_session);
void eax_validate_macro_fx_factor(
float macro_fx_factor);
void eax_validate_context_all(
const EAX40CONTEXTPROPERTIES& context_all);
void eax_validate_context_all(
const EAX50CONTEXTPROPERTIES& context_all);
void eax_defer_primary_fx_slot_id(
const GUID& primary_fx_slot_id);
void eax_defer_distance_factor(
float distance_factor);
void eax_defer_air_absorption_hf(
float air_absorption_hf);
void eax_defer_hf_reference(
float hf_reference);
void eax_defer_macro_fx_factor(
float macro_fx_factor);
void eax_defer_context_all(
const EAX40CONTEXTPROPERTIES& context_all);
void eax_defer_context_all(
const EAX50CONTEXTPROPERTIES& context_all);
void eax_defer_context_all(
const EaxEaxCall& eax_call);
void eax_defer_primary_fx_slot_id(
const EaxEaxCall& eax_call);
void eax_defer_distance_factor(
const EaxEaxCall& eax_call);
void eax_defer_air_absorption_hf(
const EaxEaxCall& eax_call);
void eax_defer_hf_reference(
const EaxEaxCall& eax_call);
void eax_set_session(
const EaxEaxCall& eax_call);
void eax_defer_macro_fx_factor(
const EaxEaxCall& eax_call);
void eax_set(
const EaxEaxCall& eax_call);
void eax_apply_deferred();
#endif // ALSOFT_EAX
};
#define SETERR_RETURN(ctx, err, retval, ...) do { \
(ctx)->setError((err), __VA_ARGS__); \
return retval; \
} while(0)
using ContextRef = al::intrusive_ptr<ALCcontext>;
ContextRef GetContextRef(void);
void UpdateContextProps(ALCcontext *context);
extern bool TrapALError;
#ifdef ALSOFT_EAX
ALenum AL_APIENTRY EAXSet(
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_value,
ALuint property_value_size) noexcept;
ALenum AL_APIENTRY EAXGet(
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_value,
ALuint property_value_size) noexcept;
#endif // ALSOFT_EAX
#endif /* ALC_CONTEXT_H */

+ 0
- 369
modules/openal-soft/Alc/converter.cpp View File

@ -1,369 +0,0 @@
#include "config.h"
#include "converter.h"
#include <algorithm>
#include "fpu_modes.h"
#include "mixer/defs.h"
namespace {
/* Base template left undefined. Should be marked =delete, but Clang 3.8.1
* chokes on that given the inline specializations.
*/
template<DevFmtType T>
inline ALfloat LoadSample(typename DevFmtTypeTraits<T>::Type val);
template<> inline ALfloat LoadSample<DevFmtByte>(DevFmtTypeTraits<DevFmtByte>::Type val)
{ return val * (1.0f/128.0f); }
template<> inline ALfloat LoadSample<DevFmtShort>(DevFmtTypeTraits<DevFmtShort>::Type val)
{ return val * (1.0f/32768.0f); }
template<> inline ALfloat LoadSample<DevFmtInt>(DevFmtTypeTraits<DevFmtInt>::Type val)
{ return val * (1.0f/2147483648.0f); }
template<> inline ALfloat LoadSample<DevFmtFloat>(DevFmtTypeTraits<DevFmtFloat>::Type val)
{ return val; }
template<> inline ALfloat LoadSample<DevFmtUByte>(DevFmtTypeTraits<DevFmtUByte>::Type val)
{ return LoadSample<DevFmtByte>(val - 128); }
template<> inline ALfloat LoadSample<DevFmtUShort>(DevFmtTypeTraits<DevFmtUShort>::Type val)
{ return LoadSample<DevFmtByte>(val - 32768); }
template<> inline ALfloat LoadSample<DevFmtUInt>(DevFmtTypeTraits<DevFmtUInt>::Type val)
{ return LoadSample<DevFmtByte>(val - 2147483648u); }
template<DevFmtType T>
inline void LoadSampleArray(ALfloat *RESTRICT dst, const void *src, size_t srcstep, ALsizei samples)
{
using SampleType = typename DevFmtTypeTraits<T>::Type;
const SampleType *ssrc = static_cast<const SampleType*>(src);
for(ALsizei i{0};i < samples;i++)
dst[i] = LoadSample<T>(ssrc[i*srcstep]);
}
void LoadSamples(ALfloat *dst, const ALvoid *src, size_t srcstep, DevFmtType srctype, ALsizei samples)
{
#define HANDLE_FMT(T) \
case T: LoadSampleArray<T>(dst, src, srcstep, samples); break
switch(srctype)
{
HANDLE_FMT(DevFmtByte);
HANDLE_FMT(DevFmtUByte);
HANDLE_FMT(DevFmtShort);
HANDLE_FMT(DevFmtUShort);
HANDLE_FMT(DevFmtInt);
HANDLE_FMT(DevFmtUInt);
HANDLE_FMT(DevFmtFloat);
}
#undef HANDLE_FMT
}
template<DevFmtType T>
inline typename DevFmtTypeTraits<T>::Type StoreSample(ALfloat);
template<> inline ALfloat StoreSample<DevFmtFloat>(ALfloat val)
{ return val; }
template<> inline ALint StoreSample<DevFmtInt>(ALfloat val)
{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); }
template<> inline ALshort StoreSample<DevFmtShort>(ALfloat val)
{ return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); }
template<> inline ALbyte StoreSample<DevFmtByte>(ALfloat val)
{ return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); }
/* Define unsigned output variations. */
template<> inline ALuint StoreSample<DevFmtUInt>(ALfloat val)
{ return StoreSample<DevFmtInt>(val) + 2147483648u; }
template<> inline ALushort StoreSample<DevFmtUShort>(ALfloat val)
{ return StoreSample<DevFmtShort>(val) + 32768; }
template<> inline ALubyte StoreSample<DevFmtUByte>(ALfloat val)
{ return StoreSample<DevFmtByte>(val) + 128; }
template<DevFmtType T>
inline void StoreSampleArray(void *dst, const ALfloat *RESTRICT src, size_t dststep,
ALsizei samples)
{
using SampleType = typename DevFmtTypeTraits<T>::Type;
SampleType *sdst = static_cast<SampleType*>(dst);
for(ALsizei i{0};i < samples;i++)
sdst[i*dststep] = StoreSample<T>(src[i]);
}
void StoreSamples(ALvoid *dst, const ALfloat *src, size_t dststep, DevFmtType dsttype, ALsizei samples)
{
#define HANDLE_FMT(T) \
case T: StoreSampleArray<T>(dst, src, dststep, samples); break
switch(dsttype)
{
HANDLE_FMT(DevFmtByte);
HANDLE_FMT(DevFmtUByte);
HANDLE_FMT(DevFmtShort);
HANDLE_FMT(DevFmtUShort);
HANDLE_FMT(DevFmtInt);
HANDLE_FMT(DevFmtUInt);
HANDLE_FMT(DevFmtFloat);
}
#undef HANDLE_FMT
}
template<DevFmtType T>
void Mono2Stereo(ALfloat *RESTRICT dst, const void *src, ALsizei frames)
{
using SampleType = typename DevFmtTypeTraits<T>::Type;
const SampleType *ssrc = static_cast<const SampleType*>(src);
for(ALsizei i{0};i < frames;i++)
dst[i*2 + 1] = dst[i*2 + 0] = LoadSample<T>(ssrc[i]) * 0.707106781187f;
}
template<DevFmtType T>
void Stereo2Mono(ALfloat *RESTRICT dst, const void *src, ALsizei frames)
{
using SampleType = typename DevFmtTypeTraits<T>::Type;
const SampleType *ssrc = static_cast<const SampleType*>(src);
for(ALsizei i{0};i < frames;i++)
dst[i] = (LoadSample<T>(ssrc[i*2 + 0])+LoadSample<T>(ssrc[i*2 + 1])) *
0.707106781187f;
}
} // namespace
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, ALsizei numchans,
ALsizei srcRate, ALsizei dstRate, Resampler resampler)
{
if(numchans <= 0 || srcRate <= 0 || dstRate <= 0)
return nullptr;
void *ptr{al_calloc(16, SampleConverter::Sizeof(numchans))};
SampleConverterPtr converter{new (ptr) SampleConverter{static_cast<size_t>(numchans)}};
converter->mSrcType = srcType;
converter->mDstType = dstType;
converter->mSrcTypeSize = BytesFromDevFmt(srcType);
converter->mDstTypeSize = BytesFromDevFmt(dstType);
converter->mSrcPrepCount = 0;
converter->mFracOffset = 0;
/* Have to set the mixer FPU mode since that's what the resampler code expects. */
FPUCtl mixer_mode{};
auto step = static_cast<ALsizei>(
mind(static_cast<ALdouble>(srcRate)/dstRate*FRACTIONONE + 0.5, MAX_PITCH*FRACTIONONE));
converter->mIncrement = maxi(step, 1);
if(converter->mIncrement == FRACTIONONE)
converter->mResample = Resample_<CopyTag,CTag>;
else
{
if(resampler == BSinc24Resampler)
BsincPrepare(converter->mIncrement, &converter->mState.bsinc, &bsinc24);
else if(resampler == BSinc12Resampler)
BsincPrepare(converter->mIncrement, &converter->mState.bsinc, &bsinc12);
converter->mResample = SelectResampler(resampler);
}
return converter;
}
ALsizei SampleConverter::availableOut(ALsizei srcframes) const
{
ALint prepcount{mSrcPrepCount};
if(prepcount < 0)
{
/* Negative prepcount means we need to skip that many input samples. */
if(-prepcount >= srcframes)
return 0;
srcframes += prepcount;
prepcount = 0;
}
if(srcframes < 1)
{
/* No output samples if there's no input samples. */
return 0;
}
if(prepcount < MAX_RESAMPLE_PADDING*2 &&
MAX_RESAMPLE_PADDING*2 - prepcount >= srcframes)
{
/* Not enough input samples to generate an output sample. */
return 0;
}
auto DataSize64 = static_cast<uint64_t>(prepcount);
DataSize64 += srcframes;
DataSize64 -= MAX_RESAMPLE_PADDING*2;
DataSize64 <<= FRACTIONBITS;
DataSize64 -= mFracOffset;
/* If we have a full prep, we can generate at least one sample. */
return static_cast<ALsizei>(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1, BUFFERSIZE));
}
ALsizei SampleConverter::convert(const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes)
{
const ALsizei SrcFrameSize{static_cast<ALsizei>(mChan.size()) * mSrcTypeSize};
const ALsizei DstFrameSize{static_cast<ALsizei>(mChan.size()) * mDstTypeSize};
const ALsizei increment{mIncrement};
auto SamplesIn = static_cast<const ALbyte*>(*src);
ALsizei NumSrcSamples{*srcframes};
FPUCtl mixer_mode{};
ALsizei pos{0};
while(pos < dstframes && NumSrcSamples > 0)
{
ALint prepcount{mSrcPrepCount};
if(prepcount < 0)
{
/* Negative prepcount means we need to skip that many input samples. */
if(-prepcount >= NumSrcSamples)
{
mSrcPrepCount = prepcount + NumSrcSamples;
NumSrcSamples = 0;
break;
}
SamplesIn += SrcFrameSize*-prepcount;
NumSrcSamples += prepcount;
mSrcPrepCount = 0;
continue;
}
ALint toread{mini(NumSrcSamples, BUFFERSIZE - MAX_RESAMPLE_PADDING*2)};
if(prepcount < MAX_RESAMPLE_PADDING*2 &&
MAX_RESAMPLE_PADDING*2 - prepcount >= toread)
{
/* Not enough input samples to generate an output sample. Store
* what we're given for later.
*/
for(size_t chan{0u};chan < mChan.size();chan++)
LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan,
mChan.size(), mSrcType, toread);
mSrcPrepCount = prepcount + toread;
NumSrcSamples = 0;
break;
}
ALfloat *RESTRICT SrcData{mSrcSamples};
ALfloat *RESTRICT DstData{mDstSamples};
ALsizei DataPosFrac{mFracOffset};
auto DataSize64 = static_cast<uint64_t>(prepcount);
DataSize64 += toread;
DataSize64 -= MAX_RESAMPLE_PADDING*2;
DataSize64 <<= FRACTIONBITS;
DataSize64 -= DataPosFrac;
/* If we have a full prep, we can generate at least one sample. */
auto DstSize = static_cast<ALsizei>(
clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE));
DstSize = mini(DstSize, dstframes-pos);
for(size_t chan{0u};chan < mChan.size();chan++)
{
const ALbyte *SrcSamples = SamplesIn + mSrcTypeSize*chan;
ALbyte *DstSamples = static_cast<ALbyte*>(dst) + mDstTypeSize*chan;
/* Load the previous samples into the source data first, then the
* new samples from the input buffer.
*/
std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData);
LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread);
/* Store as many prep samples for next time as possible, given the
* number of output samples being generated.
*/
ALsizei SrcDataEnd{(DstSize*increment + DataPosFrac)>>FRACTIONBITS};
if(SrcDataEnd >= prepcount+toread)
std::fill(std::begin(mChan[chan].PrevSamples),
std::end(mChan[chan].PrevSamples), 0.0f);
else
{
size_t len = mini(MAX_RESAMPLE_PADDING*2, prepcount+toread-SrcDataEnd);
std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples);
std::fill(std::begin(mChan[chan].PrevSamples)+len,
std::end(mChan[chan].PrevSamples), 0.0f);
}
/* Now resample, and store the result in the output buffer. */
const ALfloat *ResampledData{mResample(&mState, SrcData+MAX_RESAMPLE_PADDING,
DataPosFrac, increment, DstData, DstSize)};
StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize);
}
/* Update the number of prep samples still available, as well as the
* fractional offset.
*/
DataPosFrac += increment*DstSize;
mSrcPrepCount = mini(prepcount + toread - (DataPosFrac>>FRACTIONBITS),
MAX_RESAMPLE_PADDING*2);
mFracOffset = DataPosFrac & FRACTIONMASK;
/* Update the src and dst pointers in case there's still more to do. */
SamplesIn += SrcFrameSize*(DataPosFrac>>FRACTIONBITS);
NumSrcSamples -= mini(NumSrcSamples, (DataPosFrac>>FRACTIONBITS));
dst = static_cast<ALbyte*>(dst) + DstFrameSize*DstSize;
pos += DstSize;
}
*src = SamplesIn;
*srcframes = NumSrcSamples;
return pos;
}
ChannelConverterPtr CreateChannelConverter(DevFmtType srcType, DevFmtChannels srcChans, DevFmtChannels dstChans)
{
if(srcChans != dstChans && !((srcChans == DevFmtMono && dstChans == DevFmtStereo) ||
(srcChans == DevFmtStereo && dstChans == DevFmtMono)))
return nullptr;
return ChannelConverterPtr{new ChannelConverter{srcType, srcChans, dstChans}};
}
void ChannelConverter::convert(const ALvoid *src, ALfloat *dst, ALsizei frames) const
{
if(mSrcChans == mDstChans)
{
LoadSamples(dst, src, 1u, mSrcType, frames*ChannelsFromDevFmt(mSrcChans, 0));
return;
}
if(mSrcChans == DevFmtStereo && mDstChans == DevFmtMono)
{
switch(mSrcType)
{
#define HANDLE_FMT(T) case T: Stereo2Mono<T>(dst, src, frames); break
HANDLE_FMT(DevFmtByte);
HANDLE_FMT(DevFmtUByte);
HANDLE_FMT(DevFmtShort);
HANDLE_FMT(DevFmtUShort);
HANDLE_FMT(DevFmtInt);
HANDLE_FMT(DevFmtUInt);
HANDLE_FMT(DevFmtFloat);
#undef HANDLE_FMT
}
}
else /*if(mSrcChans == DevFmtMono && mDstChans == DevFmtStereo)*/
{
switch(mSrcType)
{
#define HANDLE_FMT(T) case T: Mono2Stereo<T>(dst, src, frames); break
HANDLE_FMT(DevFmtByte);
HANDLE_FMT(DevFmtUByte);
HANDLE_FMT(DevFmtShort);
HANDLE_FMT(DevFmtUShort);
HANDLE_FMT(DevFmtInt);
HANDLE_FMT(DevFmtUInt);
HANDLE_FMT(DevFmtFloat);
#undef HANDLE_FMT
}
}
}

+ 0
- 70
modules/openal-soft/Alc/converter.h View File

@ -1,70 +0,0 @@
#ifndef CONVERTER_H
#define CONVERTER_H
#include <memory>
#include "alMain.h"
#include "alu.h"
#include "almalloc.h"
struct SampleConverter {
DevFmtType mSrcType{};
DevFmtType mDstType{};
ALsizei mSrcTypeSize{};
ALsizei mDstTypeSize{};
ALint mSrcPrepCount{};
ALsizei mFracOffset{};
ALsizei mIncrement{};
InterpState mState{};
ResamplerFunc mResample{};
alignas(16) ALfloat mSrcSamples[BUFFERSIZE]{};
alignas(16) ALfloat mDstSamples[BUFFERSIZE]{};
struct ChanSamples {
alignas(16) ALfloat PrevSamples[MAX_RESAMPLE_PADDING*2];
};
al::FlexArray<ChanSamples> mChan;
SampleConverter(size_t numchans) : mChan{numchans} { }
SampleConverter(const SampleConverter&) = delete;
SampleConverter& operator=(const SampleConverter&) = delete;
ALsizei convert(const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes);
ALsizei availableOut(ALsizei srcframes) const;
static constexpr size_t Sizeof(size_t length) noexcept
{
return maxz(sizeof(SampleConverter),
al::FlexArray<ChanSamples>::Sizeof(length, offsetof(SampleConverter, mChan)));
}
DEF_PLACE_NEWDEL()
};
using SampleConverterPtr = std::unique_ptr<SampleConverter>;
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, ALsizei numchans,
ALsizei srcRate, ALsizei dstRate, Resampler resampler);
struct ChannelConverter {
DevFmtType mSrcType;
DevFmtChannels mSrcChans;
DevFmtChannels mDstChans;
ChannelConverter(DevFmtType srctype, DevFmtChannels srcchans, DevFmtChannels dstchans)
: mSrcType(srctype), mSrcChans(srcchans), mDstChans(dstchans)
{ }
void convert(const ALvoid *src, ALfloat *dst, ALsizei frames) const;
DEF_NEWDEL(ChannelConverter)
};
using ChannelConverterPtr = std::unique_ptr<ChannelConverter>;
ChannelConverterPtr CreateChannelConverter(DevFmtType srcType, DevFmtChannels srcChans,
DevFmtChannels dstChans);
#endif /* CONVERTER_H */

+ 0
- 23
modules/openal-soft/Alc/cpu_caps.h View File

@ -1,23 +0,0 @@
#ifndef CPU_CAPS_H
#define CPU_CAPS_H
#ifdef __cplusplus
extern "C" {
#endif
extern int CPUCapFlags;
enum {
CPU_CAP_SSE = 1<<0,
CPU_CAP_SSE2 = 1<<1,
CPU_CAP_SSE3 = 1<<2,
CPU_CAP_SSE4_1 = 1<<3,
CPU_CAP_NEON = 1<<4,
};
void FillCPUCaps(int capfilter);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* CPU_CAPS_H */

+ 90
- 0
modules/openal-soft/Alc/device.cpp View File

@ -0,0 +1,90 @@
#include "config.h"
#include "device.h"
#include <numeric>
#include <stddef.h>
#include "albit.h"
#include "alconfig.h"
#include "backends/base.h"
#include "core/bformatdec.h"
#include "core/bs2b.h"
#include "core/front_stablizer.h"
#include "core/hrtf.h"
#include "core/logging.h"
#include "core/mastering.h"
#include "core/uhjfilter.h"
namespace {
using voidp = void*;
} // namespace
ALCdevice::ALCdevice(DeviceType type) : DeviceBase{type}
{ }
ALCdevice::~ALCdevice()
{
TRACE("Freeing device %p\n", voidp{this});
Backend = nullptr;
size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), size_t{0u},
[](size_t cur, const BufferSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
if(count > 0)
WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s");
count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u},
[](size_t cur, const EffectSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0)
WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s");
count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u},
[](size_t cur, const FilterSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0)
WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s");
}
void ALCdevice::enumerateHrtfs()
{
mHrtfList = EnumerateHrtf(configValue<std::string>(nullptr, "hrtf-paths"));
if(auto defhrtfopt = configValue<std::string>(nullptr, "default-hrtf"))
{
auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt);
if(iter == mHrtfList.end())
WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str());
else if(iter != mHrtfList.begin())
std::rotate(mHrtfList.begin(), iter, iter+1);
}
}
auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1
{
if(mContexts.load(std::memory_order_relaxed)->empty())
return OutputMode1::Any;
switch(FmtChans)
{
case DevFmtMono: return OutputMode1::Mono;
case DevFmtStereo:
if(mHrtf)
return OutputMode1::Hrtf;
else if(mUhjEncoder)
return OutputMode1::Uhj2;
return OutputMode1::StereoBasic;
case DevFmtQuad: return OutputMode1::Quad;
case DevFmtX51: return OutputMode1::X51;
case DevFmtX61: return OutputMode1::X61;
case DevFmtX71: return OutputMode1::X71;
case DevFmtAmbi3D: break;
}
return OutputMode1::Any;
}

+ 165
- 0
modules/openal-soft/Alc/device.h View File

@ -0,0 +1,165 @@
#ifndef ALC_DEVICE_H
#define ALC_DEVICE_H
#include <atomic>
#include <memory>
#include <mutex>
#include <stdint.h>
#include <string>
#include <utility>
#include "AL/alc.h"
#include "AL/alext.h"
#include "alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "core/device.h"
#include "inprogext.h"
#include "intrusive_ptr.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include "al/eax_x_ram.h"
#endif // ALSOFT_EAX
struct ALbuffer;
struct ALeffect;
struct ALfilter;
struct BackendBase;
using uint = unsigned int;
struct BufferSubList {
uint64_t FreeMask{~0_u64};
ALbuffer *Buffers{nullptr}; /* 64 */
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; }
};
struct EffectSubList {
uint64_t FreeMask{~0_u64};
ALeffect *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; }
};
struct FilterSubList {
uint64_t FreeMask{~0_u64};
ALfilter *Filters{nullptr}; /* 64 */
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; }
};
struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
/* This lock protects the device state (format, update size, etc) from
* being from being changed in multiple threads, or being accessed while
* being changed. It's also used to serialize calls to the backend.
*/
std::mutex StateLock;
std::unique_ptr<BackendBase> Backend;
ALCuint NumMonoSources{};
ALCuint NumStereoSources{};
// Maximum number of sources that can be created
uint SourcesMax{};
// Maximum number of slots that can be created
uint AuxiliaryEffectSlotMax{};
std::string mHrtfName;
al::vector<std::string> mHrtfList;
ALCenum mHrtfStatus{ALC_FALSE};
enum class OutputMode1 : ALCenum {
Any = ALC_ANY_SOFT,
Mono = ALC_MONO_SOFT,
Stereo = ALC_STEREO_SOFT,
StereoBasic = ALC_STEREO_BASIC_SOFT,
Uhj2 = ALC_STEREO_UHJ_SOFT,
Hrtf = ALC_STEREO_HRTF_SOFT,
Quad = ALC_QUAD_SOFT,
X51 = ALC_SURROUND_5_1_SOFT,
X61 = ALC_SURROUND_6_1_SOFT,
X71 = ALC_SURROUND_7_1_SOFT
};
OutputMode1 getOutputMode1() const noexcept;
using OutputMode = OutputMode1;
std::atomic<ALCenum> LastError{ALC_NO_ERROR};
// Map of Buffers for this device
std::mutex BufferLock;
al::vector<BufferSubList> BufferList;
// Map of Effects for this device
std::mutex EffectLock;
al::vector<EffectSubList> EffectList;
// Map of Filters for this device
std::mutex FilterLock;
al::vector<FilterSubList> FilterList;
#ifdef ALSOFT_EAX
ALuint eax_x_ram_free_size{eax_x_ram_max_size};
#endif // ALSOFT_EAX
ALCdevice(DeviceType type);
~ALCdevice();
void enumerateHrtfs();
bool getConfigValueBool(const char *block, const char *key, bool def)
{ return GetConfigValueBool(DeviceName.c_str(), block, key, def); }
template<typename T>
al::optional<T> configValue(const char *block, const char *key) = delete;
DEF_NEWDEL(ALCdevice)
};
template<>
inline al::optional<std::string> ALCdevice::configValue(const char *block, const char *key)
{ return ConfigValueStr(DeviceName.c_str(), block, key); }
template<>
inline al::optional<int> ALCdevice::configValue(const char *block, const char *key)
{ return ConfigValueInt(DeviceName.c_str(), block, key); }
template<>
inline al::optional<uint> ALCdevice::configValue(const char *block, const char *key)
{ return ConfigValueUInt(DeviceName.c_str(), block, key); }
template<>
inline al::optional<float> ALCdevice::configValue(const char *block, const char *key)
{ return ConfigValueFloat(DeviceName.c_str(), block, key); }
template<>
inline al::optional<bool> ALCdevice::configValue(const char *block, const char *key)
{ return ConfigValueBool(DeviceName.c_str(), block, key); }
#endif

+ 93
- 172
modules/openal-soft/Alc/effects/autowah.cpp View File

@ -20,64 +20,75 @@
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <array>
#include <cstdlib>
#include <iterator>
#include <utility>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
#include "alMain.h"
#include "alcontext.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "alu.h"
#include "filters/biquad.h"
#include "vecmat.h"
namespace {
#define MIN_FREQ 20.0f
#define MAX_FREQ 2500.0f
#define Q_FACTOR 5.0f
constexpr float GainScale{31621.0f};
constexpr float MinFreq{20.0f};
constexpr float MaxFreq{2500.0f};
constexpr float QFactor{5.0f};
struct ALautowahState final : public EffectState {
struct AutowahState final : public EffectState {
/* Effect parameters */
ALfloat mAttackRate;
ALfloat mReleaseRate;
ALfloat mResonanceGain;
ALfloat mPeakGain;
ALfloat mFreqMinNorm;
ALfloat mBandwidthNorm;
ALfloat mEnvDelay;
float mAttackRate;
float mReleaseRate;
float mResonanceGain;
float mPeakGain;
float mFreqMinNorm;
float mBandwidthNorm;
float mEnvDelay;
/* Filter components derived from the envelope. */
struct {
ALfloat cos_w0;
ALfloat alpha;
} mEnv[BUFFERSIZE];
float cos_w0;
float alpha;
} mEnv[BufferLineSize];
struct {
/* Effect filters' history. */
struct {
ALfloat z1, z2;
float z1, z2;
} Filter;
/* Effect gains for each output channel */
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
} mChans[MAX_AMBI_CHANNELS];
float CurrentGains[MAX_OUTPUT_CHANNELS];
float TargetGains[MAX_OUTPUT_CHANNELS];
} mChans[MaxAmbiChannels];
/* Effects buffers */
alignas(16) ALfloat mBufferOut[BUFFERSIZE];
alignas(16) float mBufferOut[BufferLineSize];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(ALautowahState)
DEF_NEWDEL(AutowahState)
};
ALboolean ALautowahState::deviceUpdate(const ALCdevice *UNUSED(device))
void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&)
{
/* (Re-)initializing parameters and clear the buffers. */
@ -101,65 +112,61 @@ ALboolean ALautowahState::deviceUpdate(const ALCdevice *UNUSED(device))
chan.Filter.z1 = 0.0f;
chan.Filter.z2 = 0.0f;
}
return AL_TRUE;
}
void ALautowahState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
void AutowahState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->Device};
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
const ALfloat ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
mAttackRate = expf(-1.0f / (props->Autowah.AttackTime*device->Frequency));
mReleaseRate = expf(-1.0f / (ReleaseTime*device->Frequency));
mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency));
mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency));
/* 0-20dB Resonance Peak gain */
mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f);
mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN);
mFreqMinNorm = MIN_FREQ / device->Frequency;
mBandwidthNorm = (MAX_FREQ-MIN_FREQ) / device->Frequency;
mOutBuffer = target.Main->Buffer;
mOutChannels = target.Main->NumChannels;
for(ALsizei i{0};i < slot->Wet.NumChannels;++i)
{
auto coeffs = GetAmbiIdentityRow(i);
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
}
mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain / GainScale);
mFreqMinNorm = MinFreq / frequency;
mBandwidthNorm = (MaxFreq-MinFreq) / frequency;
mOutTarget = target.Main->Buffer;
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
}
void ALautowahState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
void AutowahState::process(const size_t samplesToDo,
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const ALfloat attack_rate = mAttackRate;
const ALfloat release_rate = mReleaseRate;
const ALfloat res_gain = mResonanceGain;
const ALfloat peak_gain = mPeakGain;
const ALfloat freq_min = mFreqMinNorm;
const ALfloat bandwidth = mBandwidthNorm;
ALfloat env_delay;
ALsizei c, i;
env_delay = mEnvDelay;
for(i = 0;i < samplesToDo;i++)
const float attack_rate{mAttackRate};
const float release_rate{mReleaseRate};
const float res_gain{mResonanceGain};
const float peak_gain{mPeakGain};
const float freq_min{mFreqMinNorm};
const float bandwidth{mBandwidthNorm};
float env_delay{mEnvDelay};
for(size_t i{0u};i < samplesToDo;i++)
{
ALfloat w0, sample, a;
float w0, sample, a;
/* Envelope follower described on the book: Audio Effects, Theory,
* Implementation and Application.
*/
sample = peak_gain * std::fabs(samplesIn[0][i]);
a = (sample > env_delay) ? attack_rate : release_rate;
env_delay = lerp(sample, env_delay, a);
env_delay = lerpf(sample, env_delay, a);
/* Calculate the cos and alpha components for this sample's filter. */
w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * al::MathDefs<float>::Tau();
mEnv[i].cos_w0 = cosf(w0);
mEnv[i].alpha = sinf(w0)/(2.0f * Q_FACTOR);
w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * (al::numbers::pi_v<float>*2.0f);
mEnv[i].cos_w0 = std::cos(w0);
mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor);
}
mEnvDelay = env_delay;
ASSUME(numInput > 0);
for(c = 0;c < numInput;++c)
auto chandata = std::addressof(mChans[0]);
for(const auto &insamples : samplesIn)
{
/* This effectively inlines BiquadFilter_setParams for a peaking
* filter and BiquadFilter_processC. The alpha and cosine components
@ -167,15 +174,15 @@ void ALautowahState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT sampl
* envelope. Because the filter changes for each sample, the
* coefficients are transient and don't need to be held.
*/
ALfloat z1 = mChans[c].Filter.z1;
ALfloat z2 = mChans[c].Filter.z2;
float z1{chandata->Filter.z1};
float z2{chandata->Filter.z2};
for(i = 0;i < samplesToDo;i++)
for(size_t i{0u};i < samplesToDo;i++)
{
const ALfloat alpha = mEnv[i].alpha;
const ALfloat cos_w0 = mEnv[i].cos_w0;
ALfloat input, output;
ALfloat a[3], b[3];
const float alpha{mEnv[i].alpha};
const float cos_w0{mEnv[i].cos_w0};
float input, output;
float a[3], b[3];
b[0] = 1.0f + alpha*res_gain;
b[1] = -2.0f * cos_w0;
@ -184,114 +191,28 @@ void ALautowahState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT sampl
a[1] = -2.0f * cos_w0;
a[2] = 1.0f - alpha/res_gain;
input = samplesIn[c][i];
input = insamples[i];
output = input*(b[0]/a[0]) + z1;
z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2;
z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
mBufferOut[i] = output;
}
mChans[c].Filter.z1 = z1;
mChans[c].Filter.z2 = z2;
chandata->Filter.z1 = z1;
chandata->Filter.z2 = z2;
/* Now, mix the processed sound data to the output. */
MixSamples(mBufferOut, numOutput, samplesOut, mChans[c].CurrentGains,
mChans[c].TargetGains, samplesToDo, 0, samplesToDo);
MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains,
chandata->TargetGains, samplesToDo, 0);
++chandata;
}
}
void ALautowah_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_AUTOWAH_ATTACK_TIME:
if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range");
props->Autowah.AttackTime = val;
break;
case AL_AUTOWAH_RELEASE_TIME:
if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range");
props->Autowah.ReleaseTime = val;
break;
case AL_AUTOWAH_RESONANCE:
if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range");
props->Autowah.Resonance = val;
break;
case AL_AUTOWAH_PEAK_GAIN:
if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range");
props->Autowah.PeakGain = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
}
}
void ALautowah_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ ALautowah_setParamf(props, context, param, vals[0]); }
void ALautowah_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
void ALautowah_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
void ALautowah_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_AUTOWAH_ATTACK_TIME:
*val = props->Autowah.AttackTime;
break;
case AL_AUTOWAH_RELEASE_TIME:
*val = props->Autowah.ReleaseTime;
break;
case AL_AUTOWAH_RESONANCE:
*val = props->Autowah.Resonance;
break;
case AL_AUTOWAH_PEAK_GAIN:
*val = props->Autowah.PeakGain;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
}
}
void ALautowah_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ ALautowah_getParamf(props, context, param, vals); }
void ALautowah_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
void ALautowah_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
DEFINE_ALEFFECT_VTABLE(ALautowah);
struct AutowahStateFactory final : public EffectStateFactory {
EffectState *create() override { return new ALautowahState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &ALautowah_vtable; }
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new AutowahState{}}; }
};
EffectProps AutowahStateFactory::getDefaultProps() const 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
EffectStateFactory *AutowahStateFactory_getFactory()

+ 3
- 164
modules/openal-soft/Alc/effects/base.h View File

@ -1,170 +1,7 @@
#ifndef EFFECTS_BASE_H
#define EFFECTS_BASE_H
#include "alMain.h"
#include "almalloc.h"
#include "atomic.h"
struct ALeffectslot;
union EffectProps {
struct {
// Shared Reverb Properties
ALfloat Density;
ALfloat Diffusion;
ALfloat Gain;
ALfloat GainHF;
ALfloat DecayTime;
ALfloat DecayHFRatio;
ALfloat ReflectionsGain;
ALfloat ReflectionsDelay;
ALfloat LateReverbGain;
ALfloat LateReverbDelay;
ALfloat AirAbsorptionGainHF;
ALfloat RoomRolloffFactor;
ALboolean DecayHFLimit;
// Additional EAX Reverb Properties
ALfloat GainLF;
ALfloat DecayLFRatio;
ALfloat ReflectionsPan[3];
ALfloat LateReverbPan[3];
ALfloat EchoTime;
ALfloat EchoDepth;
ALfloat ModulationTime;
ALfloat ModulationDepth;
ALfloat HFReference;
ALfloat LFReference;
} Reverb;
struct {
ALfloat AttackTime;
ALfloat ReleaseTime;
ALfloat Resonance;
ALfloat PeakGain;
} Autowah;
struct {
ALint Waveform;
ALint Phase;
ALfloat Rate;
ALfloat Depth;
ALfloat Feedback;
ALfloat Delay;
} Chorus; /* Also Flanger */
struct {
ALboolean OnOff;
} Compressor;
struct {
ALfloat Edge;
ALfloat Gain;
ALfloat LowpassCutoff;
ALfloat EQCenter;
ALfloat EQBandwidth;
} Distortion;
struct {
ALfloat Delay;
ALfloat LRDelay;
ALfloat Damping;
ALfloat Feedback;
ALfloat Spread;
} Echo;
struct {
ALfloat LowCutoff;
ALfloat LowGain;
ALfloat Mid1Center;
ALfloat Mid1Gain;
ALfloat Mid1Width;
ALfloat Mid2Center;
ALfloat Mid2Gain;
ALfloat Mid2Width;
ALfloat HighCutoff;
ALfloat HighGain;
} Equalizer;
struct {
ALfloat Frequency;
ALint LeftDirection;
ALint RightDirection;
} Fshifter;
struct {
ALfloat Frequency;
ALfloat HighPassCutoff;
ALint Waveform;
} Modulator;
struct {
ALint CoarseTune;
ALint FineTune;
} Pshifter;
struct {
ALfloat Gain;
} Dedicated;
};
struct EffectVtable {
void (*const setParami)(EffectProps *props, ALCcontext *context, ALenum param, ALint val);
void (*const setParamiv)(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals);
void (*const setParamf)(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val);
void (*const setParamfv)(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals);
void (*const getParami)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val);
void (*const getParamiv)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals);
void (*const getParamf)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val);
void (*const getParamfv)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals);
};
#define DEFINE_ALEFFECT_VTABLE(T) \
const EffectVtable T##_vtable = { \
T##_setParami, T##_setParamiv, \
T##_setParamf, T##_setParamfv, \
T##_getParami, T##_getParamiv, \
T##_getParamf, T##_getParamfv, \
}
struct EffectTarget {
MixParams *Main;
RealMixParams *RealOut;
};
struct EffectState {
RefCount mRef{1u};
ALfloat (*mOutBuffer)[BUFFERSIZE]{nullptr};
ALsizei mOutChannels{0};
virtual ~EffectState() = default;
virtual ALboolean deviceUpdate(const ALCdevice *device) = 0;
virtual void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) = 0;
virtual void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) = 0;
void IncRef() noexcept;
void DecRef() noexcept;
};
struct EffectStateFactory {
virtual ~EffectStateFactory() { }
virtual EffectState *create() = 0;
virtual EffectProps getDefaultProps() const noexcept = 0;
virtual const EffectVtable *getEffectVtable() const noexcept = 0;
};
#include "core/effects/base.h"
EffectStateFactory *NullStateFactory_getFactory(void);
@ -180,8 +17,10 @@ EffectStateFactory *FlangerStateFactory_getFactory(void);
EffectStateFactory *FshifterStateFactory_getFactory(void);
EffectStateFactory *ModulatorStateFactory_getFactory(void);
EffectStateFactory *PshifterStateFactory_getFactory(void);
EffectStateFactory* VmorpherStateFactory_getFactory(void);
EffectStateFactory *DedicatedStateFactory_getFactory(void);
EffectStateFactory *ConvolutionStateFactory_getFactory(void);
#endif /* EFFECTS_BASE_H */

+ 151
- 379
modules/openal-soft/Alc/effects/chorus.cpp View File

@ -20,96 +20,77 @@
#include "config.h"
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include "alMain.h"
#include "alcontext.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "alu.h"
#include "filters/biquad.h"
#include <array>
#include <climits>
#include <cstdlib>
#include <iterator>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "core/resampler_limits.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "vector.h"
namespace {
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");
enum class WaveForm {
Sinusoid,
Triangle
};
void GetTriangleDelays(ALint *delays, ALsizei offset, ALsizei lfo_range, ALfloat lfo_scale,
ALfloat depth, ALsizei delay, ALsizei todo)
{
std::generate_n<ALint*RESTRICT>(delays, todo,
[&offset,lfo_range,lfo_scale,depth,delay]() -> ALint
{
offset = (offset+1)%lfo_range;
return fastf2i((1.0f - std::abs(2.0f - lfo_scale*offset)) * depth) + delay;
}
);
}
using uint = unsigned int;
void GetSinusoidDelays(ALint *delays, ALsizei offset, ALsizei lfo_range, ALfloat lfo_scale,
ALfloat depth, ALsizei delay, ALsizei todo)
{
std::generate_n<ALint*RESTRICT>(delays, todo,
[&offset,lfo_range,lfo_scale,depth,delay]() -> ALint
{
offset = (offset+1)%lfo_range;
return fastf2i(std::sin(lfo_scale*offset) * depth) + delay;
}
);
}
#define MAX_UPDATE_SAMPLES 256
struct ChorusState final : public EffectState {
al::vector<ALfloat,16> mSampleBuffer;
ALsizei mOffset{0};
al::vector<float,16> mSampleBuffer;
uint mOffset{0};
ALsizei mLfoOffset{0};
ALsizei mLfoRange{1};
ALfloat mLfoScale{0.0f};
ALint mLfoDisp{0};
uint mLfoOffset{0};
uint mLfoRange{1};
float mLfoScale{0.0f};
uint mLfoDisp{0};
/* Gains for left and right sides */
struct {
ALfloat Current[MAX_OUTPUT_CHANNELS]{};
ALfloat Target[MAX_OUTPUT_CHANNELS]{};
float Current[MAX_OUTPUT_CHANNELS]{};
float Target[MAX_OUTPUT_CHANNELS]{};
} mGains[2];
/* effect parameters */
WaveForm mWaveform{};
ALint mDelay{0};
ALfloat mDepth{0.0f};
ALfloat mFeedback{0.0f};
ChorusWaveform mWaveform{};
int mDelay{0};
float mDepth{0.0f};
float mFeedback{0.0f};
void getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo);
void getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo);
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(ChorusState)
};
ALboolean ChorusState::deviceUpdate(const ALCdevice *Device)
void ChorusState::deviceUpdate(const DeviceBase *Device, const Buffer&)
{
const ALfloat max_delay = maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY);
size_t maxlen;
maxlen = NextPowerOf2(float2int(max_delay*2.0f*Device->Frequency) + 1u);
if(maxlen <= 0) return AL_FALSE;
constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)};
const auto frequency = static_cast<float>(Device->Frequency);
const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
if(maxlen != mSampleBuffer.size())
{
mSampleBuffer.resize(maxlen);
mSampleBuffer.shrink_to_fit();
}
al::vector<float,16>(maxlen).swap(mSampleBuffer);
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
for(auto &e : mGains)
@ -117,45 +98,36 @@ ALboolean ChorusState::deviceUpdate(const ALCdevice *Device)
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
}
return AL_TRUE;
}
void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target)
void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
const EffectProps *props, const EffectTarget target)
{
static constexpr ALsizei mindelay = MAX_RESAMPLE_PADDING << FRACTIONBITS;
switch(props->Chorus.Waveform)
{
case AL_CHORUS_WAVEFORM_TRIANGLE:
mWaveform = WaveForm::Triangle;
break;
case AL_CHORUS_WAVEFORM_SINUSOID:
mWaveform = WaveForm::Sinusoid;
break;
}
constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits};
/* The LFO depth is scaled to be relative to the sample delay. Clamp the
* delay and depth to allow enough padding for resampling.
*/
const ALCdevice *device{Context->Device};
const auto frequency = static_cast<ALfloat>(device->Frequency);
mDelay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 0.5f), mindelay);
mDepth = minf(props->Chorus.Depth * mDelay, static_cast<ALfloat>(mDelay - mindelay));
const DeviceBase *device{Context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
mWaveform = props->Chorus.Waveform;
mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay);
mDepth = minf(props->Chorus.Depth * static_cast<float>(mDelay),
static_cast<float>(mDelay - mindelay));
mFeedback = props->Chorus.Feedback;
/* Gains for left and right sides */
ALfloat coeffs[2][MAX_AMBI_CHANNELS];
CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f, coeffs[0]);
CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f, coeffs[1]);
const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f);
const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f);
mOutBuffer = target.Main->Buffer;
mOutChannels = target.Main->NumChannels;
ComputePanGains(target.Main, coeffs[0], Slot->Params.Gain, mGains[0].Target);
ComputePanGains(target.Main, coeffs[1], Slot->Params.Gain, mGains[1].Target);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs.data(), Slot->Gain, mGains[1].Target);
ALfloat rate{props->Chorus.Rate};
float rate{props->Chorus.Rate};
if(!(rate > 0.0f))
{
mLfoOffset = 0;
@ -168,344 +140,144 @@ void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, co
/* Calculate LFO coefficient (number of samples per cycle). Limit the
* max range to avoid overflow when calculating the displacement.
*/
ALsizei lfo_range = float2int(minf(frequency/rate + 0.5f, static_cast<ALfloat>(INT_MAX/360 - 180)));
uint lfo_range{float2uint(minf(frequency/rate + 0.5f, float{INT_MAX/360 - 180}))};
mLfoOffset = float2int(static_cast<ALfloat>(mLfoOffset)/mLfoRange*lfo_range + 0.5f) % lfo_range;
mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
mLfoRange = lfo_range;
switch(mWaveform)
{
case WaveForm::Triangle:
mLfoScale = 4.0f / mLfoRange;
break;
case WaveForm::Sinusoid:
mLfoScale = al::MathDefs<float>::Tau() / mLfoRange;
break;
case ChorusWaveform::Triangle:
mLfoScale = 4.0f / static_cast<float>(mLfoRange);
break;
case ChorusWaveform::Sinusoid:
mLfoScale = al::numbers::pi_v<float>*2.0f / static_cast<float>(mLfoRange);
break;
}
/* Calculate lfo phase displacement */
ALint phase{props->Chorus.Phase};
int phase{props->Chorus.Phase};
if(phase < 0) phase = 360 + phase;
mLfoDisp = (mLfoRange*phase + 180) / 360;
mLfoDisp = (mLfoRange*static_cast<uint>(phase) + 180) / 360;
}
}
void ChorusState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
{
const auto bufmask = static_cast<ALsizei>(mSampleBuffer.size()-1);
const ALfloat feedback{mFeedback};
const ALsizei avgdelay{(mDelay + (FRACTIONONE>>1)) >> FRACTIONBITS};
ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
ALsizei offset{mOffset};
ALsizei i, c;
ALsizei base;
for(base = 0;base < samplesToDo;)
{
const ALsizei todo = mini(256, samplesToDo-base);
ALint moddelays[2][256];
alignas(16) ALfloat temps[2][256];
if(mWaveform == WaveForm::Sinusoid)
{
GetSinusoidDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
todo);
GetSinusoidDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
mDepth, mDelay, todo);
}
else /*if(mWaveform == WaveForm::Triangle)*/
{
GetTriangleDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
todo);
GetTriangleDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
mDepth, mDelay, todo);
}
mLfoOffset = (mLfoOffset+todo) % mLfoRange;
for(i = 0;i < todo;i++)
{
// Feed the buffer's input first (necessary for delays < 1).
delaybuf[offset&bufmask] = samplesIn[0][base+i];
// Tap for the left output.
ALint delay{offset - (moddelays[0][i]>>FRACTIONBITS)};
ALfloat mu{(moddelays[0][i]&FRACTIONMASK) * (1.0f/FRACTIONONE)};
temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask],
mu);
// Tap for the right output.
delay = offset - (moddelays[1][i]>>FRACTIONBITS);
mu = (moddelays[1][i]&FRACTIONMASK) * (1.0f/FRACTIONONE);
temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask],
mu);
// Accumulate feedback from the average delay of the taps.
delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
offset++;
}
for(c = 0;c < 2;c++)
MixSamples(temps[c], numOutput, samplesOut, mGains[c].Current, mGains[c].Target,
samplesToDo-base, base, todo);
base += todo;
}
mOffset = offset;
}
void Chorus_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
void ChorusState::getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo)
{
switch(param)
{
case AL_CHORUS_WAVEFORM:
if(!(val >= AL_CHORUS_MIN_WAVEFORM && val <= AL_CHORUS_MAX_WAVEFORM))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid chorus waveform");
props->Chorus.Waveform = val;
break;
const uint lfo_range{mLfoRange};
const float lfo_scale{mLfoScale};
const float depth{mDepth};
const int delay{mDelay};
case AL_CHORUS_PHASE:
if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus phase out of range");
props->Chorus.Phase = val;
break;
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
default:
alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
}
}
void Chorus_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Chorus_setParami(props, context, param, vals[0]); }
void Chorus_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
uint offset{mLfoOffset};
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint
{
case AL_CHORUS_RATE:
if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus rate out of range");
props->Chorus.Rate = val;
break;
case AL_CHORUS_DEPTH:
if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus depth out of range");
props->Chorus.Depth = val;
break;
case AL_CHORUS_FEEDBACK:
if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus feedback out of range");
props->Chorus.Feedback = val;
break;
offset = (offset+1)%lfo_range;
const float offset_norm{static_cast<float>(offset) * lfo_scale};
return static_cast<uint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay);
};
std::generate_n(delays[0], todo, gen_lfo);
case AL_CHORUS_DELAY:
if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus delay out of range");
props->Chorus.Delay = val;
break;
offset = (mLfoOffset+mLfoDisp) % lfo_range;
std::generate_n(delays[1], todo, gen_lfo);
default:
alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
}
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
}
void Chorus_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Chorus_setParamf(props, context, param, vals[0]); }
void Chorus_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
void ChorusState::getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo)
{
switch(param)
{
case AL_CHORUS_WAVEFORM:
*val = props->Chorus.Waveform;
break;
const uint lfo_range{mLfoRange};
const float lfo_scale{mLfoScale};
const float depth{mDepth};
const int delay{mDelay};
case AL_CHORUS_PHASE:
*val = props->Chorus.Phase;
break;
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
default:
alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
}
}
void Chorus_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Chorus_getParami(props, context, param, vals); }
void Chorus_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
uint offset{mLfoOffset};
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint
{
case AL_CHORUS_RATE:
*val = props->Chorus.Rate;
break;
case AL_CHORUS_DEPTH:
*val = props->Chorus.Depth;
break;
offset = (offset+1)%lfo_range;
const float offset_norm{static_cast<float>(offset) * lfo_scale};
return static_cast<uint>(fastf2i(std::sin(offset_norm)*depth) + delay);
};
std::generate_n(delays[0], todo, gen_lfo);
case AL_CHORUS_FEEDBACK:
*val = props->Chorus.Feedback;
break;
offset = (mLfoOffset+mLfoDisp) % lfo_range;
std::generate_n(delays[1], todo, gen_lfo);
case AL_CHORUS_DELAY:
*val = props->Chorus.Delay;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
}
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
}
void Chorus_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Chorus_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Chorus);
struct ChorusStateFactory final : public EffectStateFactory {
EffectState *create() override { return new ChorusState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Chorus_vtable; }
};
EffectProps ChorusStateFactory::getDefaultProps() const noexcept
void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
EffectProps props{};
props.Chorus.Waveform = 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;
}
const size_t bufmask{mSampleBuffer.size()-1};
const float feedback{mFeedback};
const uint avgdelay{(static_cast<uint>(mDelay) + (MixerFracOne>>1)) >> MixerFracBits};
float *RESTRICT delaybuf{mSampleBuffer.data()};
uint offset{mOffset};
void Flanger_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
for(size_t base{0u};base < samplesToDo;)
{
case AL_FLANGER_WAVEFORM:
if(!(val >= AL_FLANGER_MIN_WAVEFORM && val <= AL_FLANGER_MAX_WAVEFORM))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid flanger waveform");
props->Chorus.Waveform = val;
break;
const size_t todo{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
case AL_FLANGER_PHASE:
if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger phase out of range");
props->Chorus.Phase = val;
break;
uint moddelays[2][MAX_UPDATE_SAMPLES];
if(mWaveform == ChorusWaveform::Sinusoid)
getSinusoidDelays(moddelays, todo);
else /*if(mWaveform == ChorusWaveform::Triangle)*/
getTriangleDelays(moddelays, todo);
default:
alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
}
}
void Flanger_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Flanger_setParami(props, context, param, vals[0]); }
void Flanger_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_FLANGER_RATE:
if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger rate out of range");
props->Chorus.Rate = val;
break;
case AL_FLANGER_DEPTH:
if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger depth out of range");
props->Chorus.Depth = val;
break;
case AL_FLANGER_FEEDBACK:
if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger feedback out of range");
props->Chorus.Feedback = val;
break;
alignas(16) float temps[2][MAX_UPDATE_SAMPLES];
for(size_t i{0u};i < todo;++i)
{
// Feed the buffer's input first (necessary for delays < 1).
delaybuf[offset&bufmask] = samplesIn[0][base+i];
case AL_FLANGER_DELAY:
if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger delay out of range");
props->Chorus.Delay = val;
break;
// Tap for the left output.
uint delay{offset - (moddelays[0][i]>>MixerFracBits)};
float mu{static_cast<float>(moddelays[0][i]&MixerFracMask) * (1.0f/MixerFracOne)};
temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
default:
alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
}
}
void Flanger_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Flanger_setParamf(props, context, param, vals[0]); }
// Tap for the right output.
delay = offset - (moddelays[1][i]>>MixerFracBits);
mu = static_cast<float>(moddelays[1][i]&MixerFracMask) * (1.0f/MixerFracOne);
temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
void Flanger_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
{
case AL_FLANGER_WAVEFORM:
*val = props->Chorus.Waveform;
break;
// Accumulate feedback from the average delay of the taps.
delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
++offset;
}
case AL_FLANGER_PHASE:
*val = props->Chorus.Phase;
break;
for(size_t c{0};c < 2;++c)
MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target,
samplesToDo-base, base);
default:
alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
base += todo;
}
}
void Flanger_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Flanger_getParami(props, context, param, vals); }
void Flanger_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_FLANGER_RATE:
*val = props->Chorus.Rate;
break;
case AL_FLANGER_DEPTH:
*val = props->Chorus.Depth;
break;
case AL_FLANGER_FEEDBACK:
*val = props->Chorus.Feedback;
break;
case AL_FLANGER_DELAY:
*val = props->Chorus.Delay;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
}
mOffset = offset;
}
void Flanger_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Flanger_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Flanger);
struct ChorusStateFactory final : public EffectStateFactory {
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
};
/* Flanger is basically a chorus with a really short delay. They can both use
* the same processing functions, so piggyback flanger on the chorus functions.
*/
struct FlangerStateFactory final : public EffectStateFactory {
EffectState *create() override { return new ChorusState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Flanger_vtable; }
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
};
EffectProps FlangerStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Chorus.Waveform = 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
EffectStateFactory *ChorusStateFactory_getFactory()

+ 82
- 116
modules/openal-soft/Alc/effects/compressor.cpp View File

@ -1,33 +1,56 @@
/**
* OpenAL cross platform audio library
* This file is part of the OpenAL Soft cross platform audio library
*
* Copyright (C) 2013 by Anis A. Hireche
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
* * Neither the name of Spherical-Harmonic-Transform nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <array>
#include <cstdlib>
#include <iterator>
#include <utility>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
#include "alMain.h"
#include "alcontext.h"
#include "alu.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "vecmat.h"
struct ContextBase;
namespace {
@ -41,72 +64,68 @@ namespace {
struct CompressorState final : public EffectState {
/* Effect gains for each channel */
ALfloat mGain[MAX_AMBI_CHANNELS][MAX_OUTPUT_CHANNELS]{};
float mGain[MaxAmbiChannels][MAX_OUTPUT_CHANNELS]{};
/* Effect parameters */
ALboolean mEnabled{AL_TRUE};
ALfloat mAttackMult{1.0f};
ALfloat mReleaseMult{1.0f};
ALfloat mEnvFollower{1.0f};
bool mEnabled{true};
float mAttackMult{1.0f};
float mReleaseMult{1.0f};
float mEnvFollower{1.0f};
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(CompressorState)
};
ALboolean CompressorState::deviceUpdate(const ALCdevice *device)
void CompressorState::deviceUpdate(const DeviceBase *device, const Buffer&)
{
/* Number of samples to do a full attack and release (non-integer sample
* counts are okay).
*/
const ALfloat attackCount = static_cast<ALfloat>(device->Frequency) * ATTACK_TIME;
const ALfloat releaseCount = static_cast<ALfloat>(device->Frequency) * RELEASE_TIME;
const float attackCount{static_cast<float>(device->Frequency) * ATTACK_TIME};
const float releaseCount{static_cast<float>(device->Frequency) * RELEASE_TIME};
/* Calculate per-sample multipliers to attack and release at the desired
* rates.
*/
mAttackMult = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount);
mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
return AL_TRUE;
}
void CompressorState::update(const ALCcontext* UNUSED(context), const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
void CompressorState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
mEnabled = props->Compressor.OnOff;
mOutBuffer = target.Main->Buffer;
mOutChannels = target.Main->NumChannels;
for(ALsizei i{0};i < slot->Wet.NumChannels;++i)
{
auto coeffs = GetAmbiIdentityRow(i);
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mGain[i]);
}
mOutTarget = target.Main->Buffer;
auto set_gains = [slot,target](auto &gains, al::span<const float,MaxAmbiChannels> coeffs)
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, gains); };
SetAmbiPanIdentity(std::begin(mGain), slot->Wet.Buffer.size(), set_gains);
}
void CompressorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
void CompressorState::process(const size_t samplesToDo,
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
ALsizei i, j, k;
ALsizei base;
for(base = 0;base < samplesToDo;)
for(size_t base{0u};base < samplesToDo;)
{
ALfloat gains[256];
ALsizei td = mini(256, samplesToDo-base);
ALfloat env = mEnvFollower;
float gains[256];
const size_t td{minz(256, samplesToDo-base)};
/* Generate the per-sample gains from the signal envelope. */
float env{mEnvFollower};
if(mEnabled)
{
for(i = 0;i < td;++i)
for(size_t i{0u};i < td;++i)
{
/* Clamp the absolute amplitude to the defined envelope limits,
* then attack or release the envelope to reach it.
*/
const ALfloat amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN,
const float amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN,
AMP_ENVELOPE_MAX)};
if(amplitude > env)
env = minf(env*mAttackMult, amplitude);
@ -125,9 +144,9 @@ void CompressorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samp
* ensure smooth gain changes when the compressor is turned on and
* off.
*/
for(i = 0;i < td;++i)
for(size_t i{0u};i < td;++i)
{
const ALfloat amplitude{1.0f};
const float amplitude{1.0f};
if(amplitude > env)
env = minf(env*mAttackMult, amplitude);
else if(amplitude < env)
@ -139,18 +158,18 @@ void CompressorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samp
mEnvFollower = env;
/* Now compress the signal amplitude to output. */
ASSUME(numInput > 0);
for(j = 0;j < numInput;j++)
auto changains = std::addressof(mGain[0]);
for(const auto &input : samplesIn)
{
ASSUME(numOutput > 0);
for(k = 0;k < numOutput;k++)
const float *outgains{*(changains++)};
for(FloatBufferLine &output : samplesOut)
{
const ALfloat gain{mGain[j][k]};
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
const float gain{*(outgains++)};
if(!(std::fabs(gain) > GainSilenceThreshold))
continue;
for(i = 0;i < td;i++)
samplesOut[k][base+i] += samplesIn[j][base+i] * gains[i] * gain;
for(size_t i{0u};i < td;i++)
output[base+i] += input[base+i] * gains[i] * gain;
}
}
@ -159,64 +178,11 @@ void CompressorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samp
}
void Compressor_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_COMPRESSOR_ONOFF:
if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Compressor state out of range");
props->Compressor.OnOff = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
param);
}
}
void Compressor_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Compressor_setParami(props, context, param, vals[0]); }
void Compressor_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat)
{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
void Compressor_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
void Compressor_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
{
case AL_COMPRESSOR_ONOFF:
*val = props->Compressor.OnOff;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
param);
}
}
void Compressor_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Compressor_getParami(props, context, param, vals); }
void Compressor_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
void Compressor_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
DEFINE_ALEFFECT_VTABLE(Compressor);
struct CompressorStateFactory final : public EffectStateFactory {
EffectState *create() override { return new CompressorState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Compressor_vtable; }
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new CompressorState{}}; }
};
EffectProps CompressorStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
return props;
}
} // namespace
EffectStateFactory *CompressorStateFactory_getFactory()

+ 612
- 0
modules/openal-soft/Alc/effects/convolution.cpp View File

@ -0,0 +1,612 @@
#include "config.h"
#include <algorithm>
#include <array>
#include <complex>
#include <cstddef>
#include <functional>
#include <iterator>
#include <memory>
#include <stdint.h>
#include <utility>
#ifdef HAVE_SSE_INTRINSICS
#include <xmmintrin.h>
#elif defined(HAVE_NEON)
#include <arm_neon.h>
#endif
#include "albyte.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "base.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/buffer_storage.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/filters/splitter.h"
#include "core/fmt_traits.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
#include "polyphase_resampler.h"
#include "vector.h"
namespace {
/* Convolution reverb is implemented using a segmented overlap-add method. The
* impulse response is broken up into multiple segments of 128 samples, and
* each segment has an FFT applied with a 256-sample buffer (the latter half
* left silent) to get its frequency-domain response. The resulting response
* has its positive/non-mirrored frequencies saved (129 bins) in each segment.
*
* Input samples are similarly broken up into 128-sample segments, with an FFT
* applied to each new incoming segment to get its 129 bins. A history of FFT'd
* input segments is maintained, equal to the length of the impulse response.
*
* To apply the reverberation, each impulse response segment is convolved with
* its paired input segment (using complex multiplies, far cheaper than FIRs),
* accumulating into a 256-bin FFT buffer. The input history is then shifted to
* align with later impulse response segments for next time.
*
* An inverse FFT is then applied to the accumulated FFT buffer to get a 256-
* sample time-domain response for output, which is split in two halves. The
* first half is the 128-sample output, and the second half is a 128-sample
* (really, 127) delayed extension, which gets added to the output next time.
* Convolving two time-domain responses of lengths N and M results in a time-
* domain signal of length N+M-1, and this holds true regardless of the
* convolution being applied in the frequency domain, so these "overflow"
* samples need to be accounted for.
*
* To avoid a delay with gathering enough input samples to apply an FFT with,
* the first segment is applied directly in the time-domain as the samples come
* in. Once enough have been retrieved, the FFT is applied on the input and
* it's paired with the remaining (FFT'd) filter segments for processing.
*/
void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype,
const size_t samples) noexcept
{
#define HANDLE_FMT(T) case T: al::LoadSampleArray<T>(dst, src, srcstep, samples); break
switch(srctype)
{
HANDLE_FMT(FmtUByte);
HANDLE_FMT(FmtShort);
HANDLE_FMT(FmtFloat);
HANDLE_FMT(FmtDouble);
HANDLE_FMT(FmtMulaw);
HANDLE_FMT(FmtAlaw);
}
#undef HANDLE_FMT
}
inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept
{
switch(scaletype)
{
case AmbiScaling::FuMa: return AmbiScale::FromFuMa();
case AmbiScaling::SN3D: return AmbiScale::FromSN3D();
case AmbiScaling::UHJ: return AmbiScale::FromUHJ();
case AmbiScaling::N3D: break;
}
return AmbiScale::FromN3D();
}
inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept
{
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa();
return AmbiIndex::FromACN();
}
inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept
{
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D();
return AmbiIndex::FromACN2D();
}
struct ChanMap {
Channel channel;
float angle;
float elevation;
};
constexpr float Deg2Rad(float x) noexcept
{ return static_cast<float>(al::numbers::pi / 180.0 * x); }
using complex_d = std::complex<double>;
constexpr size_t ConvolveUpdateSize{256};
constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2};
void apply_fir(al::span<float> dst, const float *RESTRICT src, const float *RESTRICT filter)
{
#ifdef HAVE_SSE_INTRINSICS
for(float &output : dst)
{
__m128 r4{_mm_setzero_ps()};
for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
{
const __m128 coeffs{_mm_load_ps(&filter[j])};
const __m128 s{_mm_loadu_ps(&src[j])};
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
}
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
output = _mm_cvtss_f32(r4);
++src;
}
#elif defined(HAVE_NEON)
for(float &output : dst)
{
float32x4_t r4{vdupq_n_f32(0.0f)};
for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j]));
r4 = vaddq_f32(r4, vrev64q_f32(r4));
output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
++src;
}
#else
for(float &output : dst)
{
float ret{0.0f};
for(size_t j{0};j < ConvolveUpdateSamples;++j)
ret += src[j] * filter[j];
output = ret;
++src;
}
#endif
}
struct ConvolutionState final : public EffectState {
FmtChannels mChannels{};
AmbiLayout mAmbiLayout{};
AmbiScaling mAmbiScaling{};
uint mAmbiOrder{};
size_t mFifoPos{0};
std::array<float,ConvolveUpdateSamples*2> mInput{};
al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter;
al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput;
alignas(16) std::array<complex_d,ConvolveUpdateSize> mFftBuffer{};
size_t mCurrentSegment{0};
size_t mNumConvolveSegs{0};
struct ChannelData {
alignas(16) FloatBufferLine mBuffer{};
float mHfScale{};
BandSplitter mFilter{};
float Current[MAX_OUTPUT_CHANNELS]{};
float Target[MAX_OUTPUT_CHANNELS]{};
};
using ChannelDataArray = al::FlexArray<ChannelData>;
std::unique_ptr<ChannelDataArray> mChans;
std::unique_ptr<complex_d[]> mComplexData;
ConvolutionState() = default;
~ConvolutionState() override = default;
void NormalMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo);
void UpsampleMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo);
void (ConvolutionState::*mMix)(const al::span<FloatBufferLine>,const size_t)
{&ConvolutionState::NormalMix};
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(ConvolutionState)
};
void ConvolutionState::NormalMix(const al::span<FloatBufferLine> samplesOut,
const size_t samplesToDo)
{
for(auto &chan : *mChans)
MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current, chan.Target,
samplesToDo, 0);
}
void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
const size_t samplesToDo)
{
for(auto &chan : *mChans)
{
const al::span<float> src{chan.mBuffer.data(), samplesToDo};
chan.mFilter.processHfScale(src, chan.mHfScale);
MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0);
}
}
void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buffer)
{
constexpr uint MaxConvolveAmbiOrder{1u};
mFifoPos = 0;
mInput.fill(0.0f);
decltype(mFilter){}.swap(mFilter);
decltype(mOutput){}.swap(mOutput);
mFftBuffer.fill(complex_d{});
mCurrentSegment = 0;
mNumConvolveSegs = 0;
mChans = nullptr;
mComplexData = nullptr;
/* An empty buffer doesn't need a convolution filter. */
if(!buffer.storage || buffer.storage->mSampleLen < 1) return;
constexpr size_t m{ConvolveUpdateSize/2 + 1};
auto bytesPerSample = BytesFromFmt(buffer.storage->mType);
auto realChannels = ChannelsFromFmt(buffer.storage->mChannels, buffer.storage->mAmbiOrder);
auto numChannels = ChannelsFromFmt(buffer.storage->mChannels,
minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder));
mChans = ChannelDataArray::Create(numChannels);
/* The impulse response needs to have the same sample rate as the input and
* output. The bsinc24 resampler is decent, but there is high-frequency
* attenation that some people may be able to pick up on. Since this is
* called very infrequently, go ahead and use the polyphase resampler.
*/
PPhaseResampler resampler;
if(device->Frequency != buffer.storage->mSampleRate)
resampler.init(buffer.storage->mSampleRate, device->Frequency);
const auto resampledCount = static_cast<uint>(
(uint64_t{buffer.storage->mSampleLen}*device->Frequency+(buffer.storage->mSampleRate-1)) /
buffer.storage->mSampleRate);
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
for(auto &e : *mChans)
e.mFilter = splitter;
mFilter.resize(numChannels, {});
mOutput.resize(numChannels, {});
/* Calculate the number of segments needed to hold the impulse response and
* the input history (rounded up), and allocate them. Exclude one segment
* which gets applied as a time-domain FIR filter. Make sure at least one
* segment is allocated to simplify handling.
*/
mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples;
mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1;
const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)};
mComplexData = std::make_unique<complex_d[]>(complex_length);
std::fill_n(mComplexData.get(), complex_length, complex_d{});
mChannels = buffer.storage->mChannels;
mAmbiLayout = buffer.storage->mAmbiLayout;
mAmbiScaling = buffer.storage->mAmbiScaling;
mAmbiOrder = minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder);
auto srcsamples = std::make_unique<double[]>(maxz(buffer.storage->mSampleLen, resampledCount));
complex_d *filteriter = mComplexData.get() + mNumConvolveSegs*m;
for(size_t c{0};c < numChannels;++c)
{
/* Load the samples from the buffer, and resample to match the device. */
LoadSamples(srcsamples.get(), buffer.samples.data() + bytesPerSample*c, realChannels,
buffer.storage->mType, buffer.storage->mSampleLen);
if(device->Frequency != buffer.storage->mSampleRate)
resampler.process(buffer.storage->mSampleLen, srcsamples.get(), resampledCount,
srcsamples.get());
/* Store the first segment's samples in reverse in the time-domain, to
* apply as a FIR filter.
*/
const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)};
std::transform(srcsamples.get(), srcsamples.get()+first_size, mFilter[c].rbegin(),
[](const double d) noexcept -> float { return static_cast<float>(d); });
size_t done{first_size};
for(size_t s{0};s < mNumConvolveSegs;++s)
{
const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)};
auto iter = std::copy_n(&srcsamples[done], todo, mFftBuffer.begin());
done += todo;
std::fill(iter, mFftBuffer.end(), complex_d{});
forward_fft(mFftBuffer);
filteriter = std::copy_n(mFftBuffer.cbegin(), m, filteriter);
}
}
}
void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps* /*props*/, const EffectTarget target)
{
/* NOTE: Stereo and Rear are slightly different from normal mixing (as
* defined in alu.cpp). These are 45 degrees from center, rather than the
* 30 degrees used there.
*
* TODO: LFE is not mixed to output. This will require each buffer channel
* to have its own output target since the main mixing buffer won't have an
* LFE channel (due to being B-Format).
*/
static constexpr ChanMap MonoMap[1]{
{ FrontCenter, 0.0f, 0.0f }
}, StereoMap[2]{
{ FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }
}, RearMap[2]{
{ BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
{ BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
}, QuadMap[4]{
{ FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
{ BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
{ BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
}, X51Map[6]{
{ FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
{ LFE, 0.0f, 0.0f },
{ SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
{ SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
}, X61Map[7]{
{ FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
{ LFE, 0.0f, 0.0f },
{ BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
{ SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
}, X71Map[8]{
{ FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
{ LFE, 0.0f, 0.0f },
{ BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
{ BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
{ SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
};
if(mNumConvolveSegs < 1)
return;
mMix = &ConvolutionState::NormalMix;
for(auto &chan : *mChans)
std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f);
const float gain{slot->Gain};
/* TODO: UHJ should be decoded to B-Format and processed that way, since
* there's no telling if it can ever do a direct-out mix (even if the
* device is outputing UHJ, the effect slot can feed another effect that's
* not UHJ).
*
* Not that UHJ should really ever be used for convolution, but it's a
* valid format regardless.
*/
if((mChannels == FmtUHJ2 || mChannels == FmtUHJ3 || mChannels == FmtUHJ4) && target.RealOut
&& target.RealOut->ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX
&& target.RealOut->ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX)
{
mOutTarget = target.RealOut->Buffer;
const uint lidx = target.RealOut->ChannelIndex[FrontLeft];
const uint ridx = target.RealOut->ChannelIndex[FrontRight];
(*mChans)[0].Target[lidx] = gain;
(*mChans)[1].Target[ridx] = gain;
}
else if(IsBFormat(mChannels))
{
DeviceBase *device{context->mDevice};
if(device->mAmbiOrder > mAmbiOrder)
{
mMix = &ConvolutionState::UpsampleMix;
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder);
(*mChans)[0].mHfScale = scales[0];
for(size_t i{1};i < mChans->size();++i)
(*mChans)[i].mHfScale = scales[1];
}
mOutTarget = target.Main->Buffer;
auto&& scales = GetAmbiScales(mAmbiScaling);
const uint8_t *index_map{(mChannels == FmtBFormat2D) ?
GetAmbi2DLayout(mAmbiLayout).data() :
GetAmbiLayout(mAmbiLayout).data()};
std::array<float,MaxAmbiChannels> coeffs{};
for(size_t c{0u};c < mChans->size();++c)
{
const size_t acn{index_map[c]};
coeffs[acn] = scales[acn];
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[c].Target);
coeffs[acn] = 0.0f;
}
}
else
{
DeviceBase *device{context->mDevice};
al::span<const ChanMap> chanmap{};
switch(mChannels)
{
case FmtMono: chanmap = MonoMap; break;
case FmtSuperStereo:
case FmtStereo: chanmap = StereoMap; break;
case FmtRear: chanmap = RearMap; break;
case FmtQuad: chanmap = QuadMap; break;
case FmtX51: chanmap = X51Map; break;
case FmtX61: chanmap = X61Map; break;
case FmtX71: chanmap = X71Map; break;
case FmtBFormat2D:
case FmtBFormat3D:
case FmtUHJ2:
case FmtUHJ3:
case FmtUHJ4:
break;
}
mOutTarget = target.Main->Buffer;
if(device->mRenderMode == RenderMode::Pairwise)
{
auto ScaleAzimuthFront = [](float azimuth, float scale) -> float
{
constexpr float half_pi{al::numbers::pi_v<float>*0.5f};
const float abs_azi{std::fabs(azimuth)};
if(!(abs_azi >= half_pi))
return std::copysign(minf(abs_azi*scale, half_pi), azimuth);
return azimuth;
};
for(size_t i{0};i < chanmap.size();++i)
{
if(chanmap[i].channel == LFE) continue;
const auto coeffs = CalcAngleCoeffs(ScaleAzimuthFront(chanmap[i].angle, 2.0f),
chanmap[i].elevation, 0.0f);
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target);
}
}
else for(size_t i{0};i < chanmap.size();++i)
{
if(chanmap[i].channel == LFE) continue;
const auto coeffs = CalcAngleCoeffs(chanmap[i].angle, chanmap[i].elevation, 0.0f);
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target);
}
}
}
void ConvolutionState::process(const size_t samplesToDo,
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
if(mNumConvolveSegs < 1)
return;
constexpr size_t m{ConvolveUpdateSize/2 + 1};
size_t curseg{mCurrentSegment};
auto &chans = *mChans;
for(size_t base{0u};base < samplesToDo;)
{
const size_t todo{minz(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)};
std::copy_n(samplesIn[0].begin() + base, todo,
mInput.begin()+ConvolveUpdateSamples+mFifoPos);
/* Apply the FIR for the newly retrieved input samples, and combine it
* with the inverse FFT'd output samples.
*/
for(size_t c{0};c < chans.size();++c)
{
auto buf_iter = chans[c].mBuffer.begin() + base;
apply_fir({std::addressof(*buf_iter), todo}, mInput.data()+1 + mFifoPos,
mFilter[c].data());
auto fifo_iter = mOutput[c].begin() + mFifoPos;
std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{});
}
mFifoPos += todo;
base += todo;
/* Check whether the input buffer is filled with new samples. */
if(mFifoPos < ConvolveUpdateSamples) break;
mFifoPos = 0;
/* Move the newest input to the front for the next iteration's history. */
std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin());
/* Calculate the frequency domain response and add the relevant
* frequency bins to the FFT history.
*/
auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin());
std::fill(fftiter, mFftBuffer.end(), complex_d{});
forward_fft(mFftBuffer);
std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]);
const complex_d *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m};
for(size_t c{0};c < chans.size();++c)
{
std::fill_n(mFftBuffer.begin(), m, complex_d{});
/* Convolve each input segment with its IR filter counterpart
* (aligned in time).
*/
const complex_d *RESTRICT input{&mComplexData[curseg*m]};
for(size_t s{curseg};s < mNumConvolveSegs;++s)
{
for(size_t i{0};i < m;++i,++input,++filter)
mFftBuffer[i] += *input * *filter;
}
input = mComplexData.get();
for(size_t s{0};s < curseg;++s)
{
for(size_t i{0};i < m;++i,++input,++filter)
mFftBuffer[i] += *input * *filter;
}
/* Reconstruct the mirrored/negative frequencies to do a proper
* inverse FFT.
*/
for(size_t i{m};i < ConvolveUpdateSize;++i)
mFftBuffer[i] = std::conj(mFftBuffer[ConvolveUpdateSize-i]);
/* Apply iFFT to get the 256 (really 255) samples for output. The
* 128 output samples are combined with the last output's 127
* second-half samples (and this output's second half is
* subsequently saved for next time).
*/
inverse_fft(mFftBuffer);
/* The iFFT'd response is scaled up by the number of bins, so apply
* the inverse to normalize the output.
*/
for(size_t i{0};i < ConvolveUpdateSamples;++i)
mOutput[c][i] =
static_cast<float>(mFftBuffer[i].real() * (1.0/double{ConvolveUpdateSize})) +
mOutput[c][ConvolveUpdateSamples+i];
for(size_t i{0};i < ConvolveUpdateSamples;++i)
mOutput[c][ConvolveUpdateSamples+i] =
static_cast<float>(mFftBuffer[ConvolveUpdateSamples+i].real() *
(1.0/double{ConvolveUpdateSize}));
}
/* Shift the input history. */
curseg = curseg ? (curseg-1) : (mNumConvolveSegs-1);
}
mCurrentSegment = curseg;
/* Finally, mix to the output. */
(this->*mMix)(samplesOut, samplesToDo);
}
struct ConvolutionStateFactory final : public EffectStateFactory {
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new ConvolutionState{}}; }
};
} // namespace
EffectStateFactory *ConvolutionStateFactory_getFactory()
{
static ConvolutionStateFactory ConvolutionFactory{};
return &ConvolutionFactory;
}

+ 45
- 87
modules/openal-soft/Alc/effects/dedicated.cpp View File

@ -20,139 +20,97 @@
#include "config.h"
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <array>
#include <cstdlib>
#include <iterator>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alspan.h"
#include "core/bufferline.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
#include "alMain.h"
#include "alcontext.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "alu.h"
struct ContextBase;
namespace {
using uint = unsigned int;
struct DedicatedState final : public EffectState {
ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS];
ALfloat mTargetGains[MAX_OUTPUT_CHANNELS];
float mCurrentGains[MAX_OUTPUT_CHANNELS];
float mTargetGains[MAX_OUTPUT_CHANNELS];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(DedicatedState)
};
ALboolean DedicatedState::deviceUpdate(const ALCdevice *UNUSED(device))
void DedicatedState::deviceUpdate(const DeviceBase*, const Buffer&)
{
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
return AL_TRUE;
}
void DedicatedState::update(const ALCcontext* UNUSED(context), const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
const ALfloat Gain{slot->Params.Gain * props->Dedicated.Gain};
const float Gain{slot->Gain * props->Dedicated.Gain};
if(slot->Params.EffectType == AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT)
if(slot->EffectType == EffectSlotType::DedicatedLFE)
{
const int idx{!target.RealOut ? -1 : GetChannelIdxByName(*target.RealOut, LFE)};
if(idx != -1)
const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
GetChannelIdxByName(*target.RealOut, LFE)};
if(idx != INVALID_CHANNEL_INDEX)
{
mOutBuffer = target.RealOut->Buffer;
mOutChannels = target.RealOut->NumChannels;
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
}
else if(slot->Params.EffectType == AL_EFFECT_DEDICATED_DIALOGUE)
else if(slot->EffectType == EffectSlotType::DedicatedDialog)
{
/* Dialog goes to the front-center speaker if it exists, otherwise it
* plays from the front-center location. */
const int idx{!target.RealOut ? -1 : GetChannelIdxByName(*target.RealOut, FrontCenter)};
if(idx != -1)
const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
GetChannelIdxByName(*target.RealOut, FrontCenter)};
if(idx != INVALID_CHANNEL_INDEX)
{
mOutBuffer = target.RealOut->Buffer;
mOutChannels = target.RealOut->NumChannels;
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
else
{
ALfloat coeffs[MAX_AMBI_CHANNELS];
CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
mOutBuffer = target.Main->Buffer;
mOutChannels = target.Main->NumChannels;
ComputePanGains(target.Main, coeffs, Gain, mTargetGains);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains);
}
}
}
void DedicatedState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
void DedicatedState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
MixSamples(samplesIn[0], numOutput, samplesOut, mCurrentGains, mTargetGains, samplesToDo, 0,
samplesToDo);
MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
samplesToDo, 0);
}
void Dedicated_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
void Dedicated_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
void Dedicated_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_DEDICATED_GAIN:
if(!(val >= 0.0f && std::isfinite(val)))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Dedicated gain out of range");
props->Dedicated.Gain = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
}
}
void Dedicated_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Dedicated_setParamf(props, context, param, vals[0]); }
void Dedicated_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
void Dedicated_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
void Dedicated_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_DEDICATED_GAIN:
*val = props->Dedicated.Gain;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
}
}
void Dedicated_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Dedicated_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Dedicated);
struct DedicatedStateFactory final : public EffectStateFactory {
EffectState *create() override { return new DedicatedState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Dedicated_vtable; }
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new DedicatedState{}}; }
};
EffectProps DedicatedStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Dedicated.Gain = 1.0f;
return props;
}
} // namespace
EffectStateFactory *DedicatedStateFactory_getFactory()

+ 66
- 163
modules/openal-soft/Alc/effects/distortion.cpp View File

@ -20,143 +20,143 @@
#include "config.h"
#include <cmath>
#include <algorithm>
#include <array>
#include <cstdlib>
#include <cmath>
#include "alMain.h"
#include "alcontext.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "alu.h"
#include "filters/biquad.h"
#include <iterator>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
namespace {
struct DistortionState final : public EffectState {
/* Effect gains for each channel */
ALfloat mGain[MAX_OUTPUT_CHANNELS]{};
float mGain[MAX_OUTPUT_CHANNELS]{};
/* Effect parameters */
BiquadFilter mLowpass;
BiquadFilter mBandpass;
ALfloat mAttenuation{};
ALfloat mEdgeCoeff{};
float mAttenuation{};
float mEdgeCoeff{};
ALfloat mBuffer[2][BUFFERSIZE]{};
float mBuffer[2][BufferLineSize]{};
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(DistortionState)
};
ALboolean DistortionState::deviceUpdate(const ALCdevice *UNUSED(device))
void DistortionState::deviceUpdate(const DeviceBase*, const Buffer&)
{
mLowpass.clear();
mBandpass.clear();
return AL_TRUE;
}
void DistortionState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->Device};
const DeviceBase *device{context->mDevice};
/* Store waveshaper edge settings. */
const ALfloat edge{
minf(std::sin(al::MathDefs<float>::Pi()*0.5f * props->Distortion.Edge), 0.99f)};
const float edge{minf(std::sin(al::numbers::pi_v<float>*0.5f * props->Distortion.Edge),
0.99f)};
mEdgeCoeff = 2.0f * edge / (1.0f-edge);
ALfloat cutoff{props->Distortion.LowpassCutoff};
float cutoff{props->Distortion.LowpassCutoff};
/* Bandwidth value is constant in octaves. */
ALfloat bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
/* Multiply sampling frequency by the amount of oversampling done during
float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
/* Divide normalized frequency by the amount of oversampling done during
* processing.
*/
auto frequency = static_cast<ALfloat>(device->Frequency);
mLowpass.setParams(BiquadType::LowPass, 1.0f, cutoff / (frequency*4.0f),
calc_rcpQ_from_bandwidth(cutoff / (frequency*4.0f), bandwidth)
);
auto frequency = static_cast<float>(device->Frequency);
mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
cutoff = props->Distortion.EQCenter;
/* Convert bandwidth in Hz to octaves. */
bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
mBandpass.setParams(BiquadType::BandPass, 1.0f, cutoff / (frequency*4.0f),
calc_rcpQ_from_bandwidth(cutoff / (frequency*4.0f), bandwidth)
);
mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
ALfloat coeffs[MAX_AMBI_CHANNELS];
CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
mOutBuffer = target.Main->Buffer;
mOutChannels = target.Main->NumChannels;
ComputePanGains(target.Main, coeffs, slot->Params.Gain*props->Distortion.Gain, mGain);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain);
}
void DistortionState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
ALfloat (*RESTRICT buffer)[BUFFERSIZE] = mBuffer;
const ALfloat fc = mEdgeCoeff;
ALsizei base;
ALsizei i, k;
for(base = 0;base < samplesToDo;)
const float fc{mEdgeCoeff};
for(size_t base{0u};base < samplesToDo;)
{
/* Perform 4x oversampling to avoid aliasing. Oversampling greatly
* improves distortion quality and allows to implement lowpass and
* bandpass filters using high frequencies, at which classic IIR
* filters became unstable.
*/
ALsizei todo{mini(BUFFERSIZE, (samplesToDo-base) * 4)};
size_t todo{minz(BufferLineSize, (samplesToDo-base) * 4)};
/* Fill oversample buffer using zero stuffing. Multiply the sample by
* the amount of oversampling to maintain the signal's power.
*/
for(i = 0;i < todo;i++)
buffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f;
for(size_t i{0u};i < todo;i++)
mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f;
/* First step, do lowpass filtering of original signal. Additionally
* perform buffer interpolation and lowpass cutoff for oversampling
* (which is fortunately first step of distortion). So combine three
* operations into the one.
*/
mLowpass.process(buffer[1], buffer[0], todo);
mLowpass.process({mBuffer[0], todo}, mBuffer[1]);
/* Second step, do distortion using waveshaper function to emulate
* signal processing during tube overdriving. Three steps of
* waveshaping are intended to modify waveform without boost/clipping/
* attenuation process.
*/
for(i = 0;i < todo;i++)
auto proc_sample = [fc](float smp) -> float
{
ALfloat smp = buffer[1][i];
smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp));
smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp)) * -1.0f;
smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp));
buffer[0][i] = smp;
}
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f;
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
return smp;
};
std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]),
proc_sample);
/* Third step, do bandpass filtering of distorted signal. */
mBandpass.process(buffer[1], buffer[0], todo);
mBandpass.process({mBuffer[0], todo}, mBuffer[1]);
todo >>= 2;
for(k = 0;k < numOutput;k++)
const float *outgains{mGain};
for(FloatBufferLine &output : samplesOut)
{
/* Fourth step, final, do attenuation and perform decimation,
* storing only one sample out of four.
*/
const ALfloat gain{mGain[k]};
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
const float gain{*(outgains++)};
if(!(std::fabs(gain) > GainSilenceThreshold))
continue;
for(i = 0;i < todo;i++)
samplesOut[k][base+i] += gain * buffer[1][i*4];
for(size_t i{0u};i < todo;i++)
output[base+i] += gain * mBuffer[1][i*4];
}
base += todo;
@ -164,108 +164,11 @@ void DistortionState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samp
}
void Distortion_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
void Distortion_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
void Distortion_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_DISTORTION_EDGE:
if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion edge out of range");
props->Distortion.Edge = val;
break;
case AL_DISTORTION_GAIN:
if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion gain out of range");
props->Distortion.Gain = val;
break;
case AL_DISTORTION_LOWPASS_CUTOFF:
if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion low-pass cutoff out of range");
props->Distortion.LowpassCutoff = val;
break;
case AL_DISTORTION_EQCENTER:
if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ center out of range");
props->Distortion.EQCenter = val;
break;
case AL_DISTORTION_EQBANDWIDTH:
if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ bandwidth out of range");
props->Distortion.EQBandwidth = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid distortion float property 0x%04x",
param);
}
}
void Distortion_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Distortion_setParamf(props, context, param, vals[0]); }
void Distortion_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
void Distortion_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
void Distortion_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_DISTORTION_EDGE:
*val = props->Distortion.Edge;
break;
case AL_DISTORTION_GAIN:
*val = props->Distortion.Gain;
break;
case AL_DISTORTION_LOWPASS_CUTOFF:
*val = props->Distortion.LowpassCutoff;
break;
case AL_DISTORTION_EQCENTER:
*val = props->Distortion.EQCenter;
break;
case AL_DISTORTION_EQBANDWIDTH:
*val = props->Distortion.EQBandwidth;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid distortion float property 0x%04x",
param);
}
}
void Distortion_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Distortion_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Distortion);
struct DistortionStateFactory final : public EffectStateFactory {
EffectState *create() override { return new DistortionState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Distortion_vtable; }
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new DistortionState{}}; }
};
EffectProps DistortionStateFactory::getDefaultProps() const 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
EffectStateFactory *DistortionStateFactory_getFactory()

+ 69
- 161
modules/openal-soft/Alc/effects/echo.cpp View File

@ -20,67 +20,74 @@
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include "alMain.h"
#include "alcontext.h"
#include "alFilter.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "alu.h"
#include "filters/biquad.h"
#include <array>
#include <cstdlib>
#include <iterator>
#include <tuple>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "vector.h"
namespace {
using uint = unsigned int;
constexpr float LowpassFreqRef{5000.0f};
struct EchoState final : public EffectState {
al::vector<ALfloat,16> mSampleBuffer;
al::vector<float,16> mSampleBuffer;
// The echo is two tap. The delay is the number of samples from before the
// current offset
struct {
ALsizei delay{0};
size_t delay{0u};
} mTap[2];
ALsizei mOffset{0};
size_t mOffset{0u};
/* The panning gains for the two taps */
struct {
ALfloat Current[MAX_OUTPUT_CHANNELS]{};
ALfloat Target[MAX_OUTPUT_CHANNELS]{};
float Current[MAX_OUTPUT_CHANNELS]{};
float Target[MAX_OUTPUT_CHANNELS]{};
} mGains[2];
BiquadFilter mFilter;
ALfloat mFeedGain{0.0f};
float mFeedGain{0.0f};
alignas(16) ALfloat mTempBuffer[2][BUFFERSIZE];
alignas(16) float mTempBuffer[2][BufferLineSize];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(EchoState)
};
ALboolean EchoState::deviceUpdate(const ALCdevice *Device)
void EchoState::deviceUpdate(const DeviceBase *Device, const Buffer&)
{
ALuint maxlen;
const auto frequency = static_cast<float>(Device->Frequency);
// Use the next power of 2 for the buffer length, so the tap offsets can be
// wrapped using a mask instead of a modulo
maxlen = float2int(AL_ECHO_MAX_DELAY*Device->Frequency + 0.5f) +
float2int(AL_ECHO_MAX_LRDELAY*Device->Frequency + 0.5f);
maxlen = NextPowerOf2(maxlen);
if(maxlen <= 0) return AL_FALSE;
const uint maxlen{NextPowerOf2(float2uint(EchoMaxDelay*frequency + 0.5f) +
float2uint(EchoMaxLRDelay*frequency + 0.5f))};
if(maxlen != mSampleBuffer.size())
{
mSampleBuffer.resize(maxlen);
mSampleBuffer.shrink_to_fit();
}
al::vector<float,16>(maxlen).swap(mSampleBuffer);
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
for(auto &e : mGains)
@ -88,57 +95,53 @@ ALboolean EchoState::deviceUpdate(const ALCdevice *Device)
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
}
return AL_TRUE;
}
void EchoState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
void EchoState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device = context->Device;
const auto frequency = static_cast<ALfloat>(device->Frequency);
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
mTap[0].delay = maxi(float2int(props->Echo.Delay*frequency + 0.5f), 1);
mTap[1].delay = float2int(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1);
mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
const ALfloat gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
mFilter.setParams(BiquadType::HighShelf, gainhf, LOWPASSFREQREF/frequency,
calc_rcpQ_from_slope(gainhf, 1.0f));
const float gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f);
mFeedGain = props->Echo.Feedback;
/* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
const ALfloat angle{std::asin(props->Echo.Spread)};
const float angle{std::asin(props->Echo.Spread)};
ALfloat coeffs[2][MAX_AMBI_CHANNELS];
CalcAngleCoeffs(-angle, 0.0f, 0.0f, coeffs[0]);
CalcAngleCoeffs( angle, 0.0f, 0.0f, coeffs[1]);
const auto coeffs0 = CalcAngleCoeffs(-angle, 0.0f, 0.0f);
const auto coeffs1 = CalcAngleCoeffs( angle, 0.0f, 0.0f);
mOutBuffer = target.Main->Buffer;
mOutChannels = target.Main->NumChannels;
ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target);
ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs0.data(), slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, coeffs1.data(), slot->Gain, mGains[1].Target);
}
void EchoState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
void EchoState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const auto mask = static_cast<ALsizei>(mSampleBuffer.size()-1);
ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
ALsizei offset{mOffset};
ALsizei tap1{offset - mTap[0].delay};
ALsizei tap2{offset - mTap[1].delay};
ALfloat z1, z2;
const size_t mask{mSampleBuffer.size()-1};
float *RESTRICT delaybuf{mSampleBuffer.data()};
size_t offset{mOffset};
size_t tap1{offset - mTap[0].delay};
size_t tap2{offset - mTap[1].delay};
float z1, z2;
ASSUME(samplesToDo > 0);
ASSUME(mask > 0);
const BiquadFilter filter{mFilter};
std::tie(z1, z2) = mFilter.getComponents();
for(ALsizei i{0};i < samplesToDo;)
for(size_t i{0u};i < samplesToDo;)
{
offset &= mask;
tap1 &= mask;
tap2 &= mask;
ALsizei td{mini(mask+1 - maxi(offset, maxi(tap1, tap2)), samplesToDo-i)};
size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)};
do {
/* Feed the delay buffer's input first. */
delaybuf[offset] = samplesIn[0][i];
@ -151,118 +154,23 @@ void EchoState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)
const float feedb{mTempBuffer[1][i++]};
/* Add feedback to the delay buffer with damping and attenuation. */
delaybuf[offset++] += mFilter.processOne(feedb, z1, z2) * mFeedGain;
delaybuf[offset++] += filter.processOne(feedb, z1, z2) * mFeedGain;
} while(--td);
}
mFilter.setComponents(z1, z2);
mOffset = offset;
for(ALsizei c{0};c < 2;c++)
MixSamples(mTempBuffer[c], numOutput, samplesOut, mGains[c].Current, mGains[c].Target,
samplesToDo, 0, samplesToDo);
for(size_t c{0};c < 2;c++)
MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
samplesToDo, 0);
}
void Echo_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
void Echo_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
void Echo_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_ECHO_DELAY:
if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range");
props->Echo.Delay = val;
break;
case AL_ECHO_LRDELAY:
if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range");
props->Echo.LRDelay = val;
break;
case AL_ECHO_DAMPING:
if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range");
props->Echo.Damping = val;
break;
case AL_ECHO_FEEDBACK:
if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range");
props->Echo.Feedback = val;
break;
case AL_ECHO_SPREAD:
if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range");
props->Echo.Spread = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
}
}
void Echo_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Echo_setParamf(props, context, param, vals[0]); }
void Echo_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
void Echo_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
void Echo_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_ECHO_DELAY:
*val = props->Echo.Delay;
break;
case AL_ECHO_LRDELAY:
*val = props->Echo.LRDelay;
break;
case AL_ECHO_DAMPING:
*val = props->Echo.Damping;
break;
case AL_ECHO_FEEDBACK:
*val = props->Echo.Feedback;
break;
case AL_ECHO_SPREAD:
*val = props->Echo.Spread;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
}
}
void Echo_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Echo_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Echo);
struct EchoStateFactory final : public EffectStateFactory {
EffectState *create() override { return new EchoState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Echo_vtable; }
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new EchoState{}}; }
};
EffectProps EchoStateFactory::getDefaultProps() const 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
EffectStateFactory *EchoStateFactory_getFactory()

+ 67
- 213
modules/openal-soft/Alc/effects/equalizer.cpp View File

@ -20,19 +20,25 @@
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <array>
#include <cstdlib>
#include <functional>
#include "alMain.h"
#include "alcontext.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "alu.h"
#include "filters/biquad.h"
#include "vecmat.h"
#include <iterator>
#include <utility>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
namespace {
@ -85,63 +91,64 @@ struct EqualizerState final : public EffectState {
BiquadFilter filter[4];
/* Effect gains for each channel */
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
} mChans[MAX_AMBI_CHANNELS];
float CurrentGains[MAX_OUTPUT_CHANNELS]{};
float TargetGains[MAX_OUTPUT_CHANNELS]{};
} mChans[MaxAmbiChannels];
ALfloat mSampleBuffer[BUFFERSIZE]{};
FloatBufferLine mSampleBuffer{};
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(EqualizerState)
};
ALboolean EqualizerState::deviceUpdate(const ALCdevice *UNUSED(device))
void EqualizerState::deviceUpdate(const DeviceBase*, const Buffer&)
{
for(auto &e : mChans)
{
std::for_each(std::begin(e.filter), std::end(e.filter),
std::mem_fn(&BiquadFilter::clear));
std::for_each(std::begin(e.filter), std::end(e.filter), std::mem_fn(&BiquadFilter::clear));
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
}
return AL_TRUE;
}
void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device = context->Device;
auto frequency = static_cast<ALfloat>(device->Frequency);
ALfloat gain, f0norm;
const DeviceBase *device{context->mDevice};
auto frequency = static_cast<float>(device->Frequency);
float gain, f0norm;
/* Calculate coefficients for the each type of filter. Note that the shelf
* filters' gain is for the reference frequency, which is the centerpoint
* of the transition band.
* and peaking filters' gain is for the centerpoint of the transition band,
* while the effect property gains are for the shelf/peak itself. So the
* property gains need their dB halved (sqrt of linear gain) for the
* shelf/peak to reach the provided gain.
*/
gain = maxf(sqrtf(props->Equalizer.LowGain), 0.0625f); /* Limit -24dB */
f0norm = props->Equalizer.LowCutoff/frequency;
mChans[0].filter[0].setParams(BiquadType::LowShelf, gain, f0norm,
calc_rcpQ_from_slope(gain, 0.75f));
gain = std::sqrt(props->Equalizer.LowGain);
f0norm = props->Equalizer.LowCutoff / frequency;
mChans[0].filter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
gain = maxf(props->Equalizer.Mid1Gain, 0.0625f);
f0norm = props->Equalizer.Mid1Center/frequency;
mChans[0].filter[1].setParams(BiquadType::Peaking, gain, f0norm,
calc_rcpQ_from_bandwidth(f0norm, props->Equalizer.Mid1Width));
gain = std::sqrt(props->Equalizer.Mid1Gain);
f0norm = props->Equalizer.Mid1Center / frequency;
mChans[0].filter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
props->Equalizer.Mid1Width);
gain = maxf(props->Equalizer.Mid2Gain, 0.0625f);
f0norm = props->Equalizer.Mid2Center/frequency;
mChans[0].filter[2].setParams(BiquadType::Peaking, gain, f0norm,
calc_rcpQ_from_bandwidth(f0norm, props->Equalizer.Mid2Width));
gain = std::sqrt(props->Equalizer.Mid2Gain);
f0norm = props->Equalizer.Mid2Center / frequency;
mChans[0].filter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
props->Equalizer.Mid2Width);
gain = maxf(sqrtf(props->Equalizer.HighGain), 0.0625f);
f0norm = props->Equalizer.HighCutoff/frequency;
mChans[0].filter[3].setParams(BiquadType::HighShelf, gain, f0norm,
calc_rcpQ_from_slope(gain, 0.75f));
gain = std::sqrt(props->Equalizer.HighGain);
f0norm = props->Equalizer.HighCutoff / frequency;
mChans[0].filter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f);
/* Copy the filter coefficients for the other input channels. */
for(ALsizei i{1};i < slot->Wet.NumChannels;++i)
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
{
mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]);
mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]);
@ -149,186 +156,33 @@ void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot,
mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]);
}
mOutBuffer = target.Main->Buffer;
mOutChannels = target.Main->NumChannels;
for(ALsizei i{0};i < slot->Wet.NumChannels;++i)
{
auto coeffs = GetAmbiIdentityRow(i);
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
}
mOutTarget = target.Main->Buffer;
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
}
void EqualizerState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
ASSUME(numInput > 0);
for(ALsizei c{0};c < numInput;c++)
const al::span<float> buffer{mSampleBuffer.data(), samplesToDo};
auto chan = std::addressof(mChans[0]);
for(const auto &input : samplesIn)
{
mChans[c].filter[0].process(mSampleBuffer, samplesIn[c], samplesToDo);
mChans[c].filter[1].process(mSampleBuffer, mSampleBuffer, samplesToDo);
mChans[c].filter[2].process(mSampleBuffer, mSampleBuffer, samplesToDo);
mChans[c].filter[3].process(mSampleBuffer, mSampleBuffer, samplesToDo);
const al::span<const float> inbuf{input.data(), samplesToDo};
DualBiquad{chan->filter[0], chan->filter[1]}.process(inbuf, buffer.begin());
DualBiquad{chan->filter[2], chan->filter[3]}.process(buffer, buffer.begin());
MixSamples(mSampleBuffer, numOutput, samplesOut, mChans[c].CurrentGains,
mChans[c].TargetGains, samplesToDo, 0, samplesToDo);
MixSamples(buffer, samplesOut, chan->CurrentGains, chan->TargetGains, samplesToDo, 0u);
++chan;
}
}
void Equalizer_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
void Equalizer_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
void Equalizer_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_EQUALIZER_LOW_GAIN:
if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band gain out of range");
props->Equalizer.LowGain = val;
break;
case AL_EQUALIZER_LOW_CUTOFF:
if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band cutoff out of range");
props->Equalizer.LowCutoff = val;
break;
case AL_EQUALIZER_MID1_GAIN:
if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band gain out of range");
props->Equalizer.Mid1Gain = val;
break;
case AL_EQUALIZER_MID1_CENTER:
if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band center out of range");
props->Equalizer.Mid1Center = val;
break;
case AL_EQUALIZER_MID1_WIDTH:
if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band width out of range");
props->Equalizer.Mid1Width = val;
break;
case AL_EQUALIZER_MID2_GAIN:
if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band gain out of range");
props->Equalizer.Mid2Gain = val;
break;
case AL_EQUALIZER_MID2_CENTER:
if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band center out of range");
props->Equalizer.Mid2Center = val;
break;
case AL_EQUALIZER_MID2_WIDTH:
if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band width out of range");
props->Equalizer.Mid2Width = val;
break;
case AL_EQUALIZER_HIGH_GAIN:
if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band gain out of range");
props->Equalizer.HighGain = val;
break;
case AL_EQUALIZER_HIGH_CUTOFF:
if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band cutoff out of range");
props->Equalizer.HighCutoff = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
}
}
void Equalizer_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Equalizer_setParamf(props, context, param, vals[0]); }
void Equalizer_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
void Equalizer_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
void Equalizer_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_EQUALIZER_LOW_GAIN:
*val = props->Equalizer.LowGain;
break;
case AL_EQUALIZER_LOW_CUTOFF:
*val = props->Equalizer.LowCutoff;
break;
case AL_EQUALIZER_MID1_GAIN:
*val = props->Equalizer.Mid1Gain;
break;
case AL_EQUALIZER_MID1_CENTER:
*val = props->Equalizer.Mid1Center;
break;
case AL_EQUALIZER_MID1_WIDTH:
*val = props->Equalizer.Mid1Width;
break;
case AL_EQUALIZER_MID2_GAIN:
*val = props->Equalizer.Mid2Gain;
break;
case AL_EQUALIZER_MID2_CENTER:
*val = props->Equalizer.Mid2Center;
break;
case AL_EQUALIZER_MID2_WIDTH:
*val = props->Equalizer.Mid2Width;
break;
case AL_EQUALIZER_HIGH_GAIN:
*val = props->Equalizer.HighGain;
break;
case AL_EQUALIZER_HIGH_CUTOFF:
*val = props->Equalizer.HighCutoff;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
}
}
void Equalizer_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Equalizer_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Equalizer);
struct EqualizerStateFactory final : public EffectStateFactory {
EffectState *create() override { return new EqualizerState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Equalizer_vtable; }
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new EqualizerState{}}; }
};
EffectProps EqualizerStateFactory::getDefaultProps() const 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
EffectStateFactory *EqualizerStateFactory_getFactory()

+ 136
- 191
modules/openal-soft/Alc/effects/fshifter.cpp View File

@ -20,22 +20,32 @@
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <array>
#include <cmath>
#include <complex>
#include <algorithm>
#include "alMain.h"
#include "alcontext.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "alu.h"
#include <cstdlib>
#include <iterator>
#include "alc/effects/base.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
namespace {
using uint = unsigned int;
using complex_d = std::complex<double>;
#define HIL_SIZE 1024
@ -45,254 +55,189 @@ using complex_d = std::complex;
#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1))
/* Define a Hann window, used to filter the HIL input and output. */
/* Making this constexpr seems to require C++14. */
std::array<ALdouble,HIL_SIZE> InitHannWindow()
std::array<double,HIL_SIZE> InitHannWindow()
{
std::array<ALdouble,HIL_SIZE> ret;
std::array<double,HIL_SIZE> ret;
/* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
for(ALsizei i{0};i < HIL_SIZE>>1;i++)
for(size_t i{0};i < HIL_SIZE>>1;i++)
{
ALdouble val = std::sin(al::MathDefs<double>::Pi() * i / ALdouble{HIL_SIZE-1});
constexpr double scale{al::numbers::pi / double{HIL_SIZE}};
const double val{std::sin(static_cast<double>(i+1) * scale)};
ret[i] = ret[HIL_SIZE-1-i] = val * val;
}
return ret;
}
alignas(16) const std::array<ALdouble,HIL_SIZE> HannWindow = InitHannWindow();
alignas(16) const std::array<double,HIL_SIZE> HannWindow = InitHannWindow();
struct FshifterState final : public EffectState {
/* Effect parameters */
ALsizei mCount{};
ALsizei mPhaseStep{};
ALsizei mPhase{};
ALdouble mLdSign{};
/*Effects buffers*/
ALfloat mInFIFO[HIL_SIZE]{};
complex_d mOutFIFO[HIL_SIZE]{};
size_t mCount{};
size_t mPos{};
uint mPhaseStep[2]{};
uint mPhase[2]{};
double mSign[2]{};
/* Effects buffers */
double mInFIFO[HIL_SIZE]{};
complex_d mOutFIFO[HIL_STEP]{};
complex_d mOutputAccum[HIL_SIZE]{};
complex_d mAnalytic[HIL_SIZE]{};
complex_d mOutdata[BUFFERSIZE]{};
complex_d mOutdata[BufferLineSize]{};
alignas(16) ALfloat mBufferOut[BUFFERSIZE]{};
alignas(16) float mBufferOut[BufferLineSize]{};
/* Effect gains for each output channel */
ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS]{};
ALfloat mTargetGains[MAX_OUTPUT_CHANNELS]{};
struct {
float Current[MAX_OUTPUT_CHANNELS]{};
float Target[MAX_OUTPUT_CHANNELS]{};
} mGains[2];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(FshifterState)
};
ALboolean FshifterState::deviceUpdate(const ALCdevice *UNUSED(device))
void FshifterState::deviceUpdate(const DeviceBase*, const Buffer&)
{
/* (Re-)initializing parameters and clear the buffers. */
mCount = FIFO_LATENCY;
mPhaseStep = 0;
mPhase = 0;
mLdSign = 1.0;
mCount = 0;
mPos = FIFO_LATENCY;
std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f);
std::fill(std::begin(mPhaseStep), std::end(mPhaseStep), 0u);
std::fill(std::begin(mPhase), std::end(mPhase), 0u);
std::fill(std::begin(mSign), std::end(mSign), 1.0);
std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0);
std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), complex_d{});
std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), complex_d{});
std::fill(std::begin(mAnalytic), std::end(mAnalytic), complex_d{});
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
return AL_TRUE;
for(auto &gain : mGains)
{
std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f);
std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f);
}
}
void FshifterState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->Device};
const DeviceBase *device{context->mDevice};
ALfloat step{props->Fshifter.Frequency / static_cast<ALfloat>(device->Frequency)};
mPhaseStep = fastf2i(minf(step, 0.5f) * FRACTIONONE);
const float step{props->Fshifter.Frequency / static_cast<float>(device->Frequency)};
mPhaseStep[0] = mPhaseStep[1] = fastf2u(minf(step, 1.0f) * MixerFracOne);
switch(props->Fshifter.LeftDirection)
{
case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN:
mLdSign = -1.0;
break;
case AL_FREQUENCY_SHIFTER_DIRECTION_UP:
mLdSign = 1.0;
break;
case AL_FREQUENCY_SHIFTER_DIRECTION_OFF:
mPhase = 0;
mPhaseStep = 0;
break;
case FShifterDirection::Down:
mSign[0] = -1.0;
break;
case FShifterDirection::Up:
mSign[0] = 1.0;
break;
case FShifterDirection::Off:
mPhase[0] = 0;
mPhaseStep[0] = 0;
break;
}
switch(props->Fshifter.RightDirection)
{
case FShifterDirection::Down:
mSign[1] = -1.0;
break;
case FShifterDirection::Up:
mSign[1] = 1.0;
break;
case FShifterDirection::Off:
mPhase[1] = 0;
mPhaseStep[1] = 0;
break;
}
ALfloat coeffs[MAX_AMBI_CHANNELS];
CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f);
const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f);
mOutBuffer = target.Main->Buffer;
mOutChannels = target.Main->NumChannels;
ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, lcoeffs.data(), slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs.data(), slot->Gain, mGains[1].Target);
}
void FshifterState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
void FshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
static constexpr complex_d complex_zero{0.0, 0.0};
ALfloat *RESTRICT BufferOut = mBufferOut;
ALsizei j, k, base;
for(base = 0;base < samplesToDo;)
for(size_t base{0u};base < samplesToDo;)
{
const ALsizei todo{mini(HIL_SIZE-mCount, samplesToDo-base)};
ASSUME(todo > 0);
size_t todo{minz(HIL_STEP-mCount, samplesToDo-base)};
/* Fill FIFO buffer with samples data */
k = mCount;
for(j = 0;j < todo;j++,k++)
{
mInFIFO[k] = samplesIn[0][base+j];
mOutdata[base+j] = mOutFIFO[k-FIFO_LATENCY];
}
mCount += todo;
base += todo;
const size_t pos{mPos};
size_t count{mCount};
do {
mInFIFO[pos+count] = samplesIn[0][base];
mOutdata[base] = mOutFIFO[count];
++base; ++count;
} while(--todo);
mCount = count;
/* Check whether FIFO buffer is filled */
if(mCount < HIL_SIZE) continue;
mCount = FIFO_LATENCY;
if(mCount < HIL_STEP) break;
mCount = 0;
mPos = (mPos+HIL_STEP) & (HIL_SIZE-1);
/* Real signal windowing and store in Analytic buffer */
for(k = 0;k < HIL_SIZE;k++)
{
mAnalytic[k].real(mInFIFO[k] * HannWindow[k]);
mAnalytic[k].imag(0.0);
}
for(size_t src{mPos}, k{0u};src < HIL_SIZE;++src,++k)
mAnalytic[k] = mInFIFO[src]*HannWindow[k];
for(size_t src{0u}, k{HIL_SIZE-mPos};src < mPos;++src,++k)
mAnalytic[k] = mInFIFO[src]*HannWindow[k];
/* Processing signal by Discrete Hilbert Transform (analytical signal). */
complex_hilbert(mAnalytic, HIL_SIZE);
complex_hilbert(mAnalytic);
/* Windowing and add to output accumulator */
for(k = 0;k < HIL_SIZE;k++)
mOutputAccum[k] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
/* Shift accumulator, input & output FIFO */
for(k = 0;k < HIL_STEP;k++) mOutFIFO[k] = mOutputAccum[k];
for(j = 0;k < HIL_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k];
for(;j < HIL_SIZE;j++) mOutputAccum[j] = complex_zero;
for(k = 0;k < FIFO_LATENCY;k++)
mInFIFO[k] = mInFIFO[k+HIL_STEP];
for(size_t dst{mPos}, k{0u};dst < HIL_SIZE;++dst,++k)
mOutputAccum[dst] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
for(size_t dst{0u}, k{HIL_SIZE-mPos};dst < mPos;++dst,++k)
mOutputAccum[dst] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
/* Copy out the accumulated result, then clear for the next iteration. */
std::copy_n(mOutputAccum + mPos, HIL_STEP, mOutFIFO);
std::fill_n(mOutputAccum + mPos, HIL_STEP, complex_d{});
}
/* Process frequency shifter using the analytic signal obtained. */
for(k = 0;k < samplesToDo;k++)
{
double phase = mPhase * ((1.0/FRACTIONONE) * al::MathDefs<double>::Tau());
BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) +
mOutdata[k].imag()*std::sin(phase)*mLdSign);
mPhase += mPhaseStep;
mPhase &= FRACTIONMASK;
}
/* Now, mix the processed sound data to the output. */
MixSamples(BufferOut, numOutput, samplesOut, mCurrentGains, mTargetGains,
maxi(samplesToDo, 512), 0, samplesToDo);
}
void Fshifter_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_FREQUENCY:
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter frequency out of range");
props->Fshifter.Frequency = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", param);
}
}
void Fshifter_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Fshifter_setParamf(props, context, param, vals[0]); }
void Fshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter left direction out of range");
props->Fshifter.LeftDirection = val;
break;
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter right direction out of range");
props->Fshifter.RightDirection = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", param);
}
}
void Fshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Fshifter_setParami(props, context, param, vals[0]); }
void Fshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
float *RESTRICT BufferOut{mBufferOut};
for(int c{0};c < 2;++c)
{
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
*val = props->Fshifter.LeftDirection;
break;
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
*val = props->Fshifter.RightDirection;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", param);
}
}
void Fshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Fshifter_getParami(props, context, param, vals); }
const uint phase_step{mPhaseStep[c]};
uint phase_idx{mPhase[c]};
for(size_t k{0};k < samplesToDo;++k)
{
const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)};
BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) +
mOutdata[k].imag()*std::sin(phase)*mSign[c]);
void Fshifter_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_FREQUENCY:
*val = props->Fshifter.Frequency;
break;
phase_idx += phase_step;
phase_idx &= MixerFracMask;
}
mPhase[c] = phase_idx;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", param);
/* Now, mix the processed sound data to the output. */
MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
maxz(samplesToDo, 512), 0);
}
}
void Fshifter_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Fshifter_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Fshifter);
struct FshifterStateFactory final : public EffectStateFactory {
EffectState *create() override { return new FshifterState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Fshifter_vtable; }
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new FshifterState{}}; }
};
EffectProps FshifterStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
props.Fshifter.LeftDirection = AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION;
props.Fshifter.RightDirection = AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION;
return props;
}
} // namespace
EffectStateFactory *FshifterStateFactory_getFactory()

+ 74
- 174
modules/openal-soft/Alc/effects/modulator.cpp View File

@ -20,55 +20,55 @@
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include "alMain.h"
#include "alcontext.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "alu.h"
#include "filters/biquad.h"
#include "vecmat.h"
#include <array>
#include <cstdlib>
#include <iterator>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
namespace {
using uint = unsigned int;
#define MAX_UPDATE_SAMPLES 128
#define WAVEFORM_FRACBITS 24
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
inline ALfloat Sin(ALsizei index)
inline float Sin(uint index)
{
return std::sin(static_cast<ALfloat>(index) *
(al::MathDefs<float>::Tau() / ALfloat{WAVEFORM_FRACONE}));
constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE};
return std::sin(static_cast<float>(index) * scale);
}
inline ALfloat Saw(ALsizei index)
{
return static_cast<ALfloat>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f;
}
inline float Saw(uint index)
{ return static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; }
inline ALfloat Square(ALsizei index)
{
return static_cast<ALfloat>(((index>>(WAVEFORM_FRACBITS-2))&2) - 1);
}
inline float Square(uint index)
{ return static_cast<float>(static_cast<int>((index>>(WAVEFORM_FRACBITS-2))&2) - 1); }
inline ALfloat One(ALsizei UNUSED(index))
{
return 1.0f;
}
inline float One(uint) { return 1.0f; }
template<ALfloat func(ALsizei)>
void Modulate(ALfloat *RESTRICT dst, ALsizei index, const ALsizei step, ALsizei todo)
template<float (&func)(uint)>
void Modulate(float *RESTRICT dst, uint index, const uint step, size_t todo)
{
ALsizei i;
for(i = 0;i < todo;i++)
for(size_t i{0u};i < todo;i++)
{
index += step;
index &= WAVEFORM_FRACMASK;
@ -78,95 +78,90 @@ void Modulate(ALfloat *RESTRICT dst, ALsizei index, const ALsizei step, ALsizei
struct ModulatorState final : public EffectState {
void (*mGetSamples)(ALfloat*RESTRICT, ALsizei, const ALsizei, ALsizei){};
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
ALsizei mIndex{0};
ALsizei mStep{1};
uint mIndex{0};
uint mStep{1};
struct {
BiquadFilter Filter;
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
} mChans[MAX_AMBI_CHANNELS];
float CurrentGains[MAX_OUTPUT_CHANNELS]{};
float TargetGains[MAX_OUTPUT_CHANNELS]{};
} mChans[MaxAmbiChannels];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(ModulatorState)
};
ALboolean ModulatorState::deviceUpdate(const ALCdevice *UNUSED(device))
void ModulatorState::deviceUpdate(const DeviceBase*, const Buffer&)
{
for(auto &e : mChans)
{
e.Filter.clear();
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
}
return AL_TRUE;
}
void ModulatorState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->Device};
const DeviceBase *device{context->mDevice};
const float step{props->Modulator.Frequency / static_cast<ALfloat>(device->Frequency)};
mStep = fastf2i(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1}));
const float step{props->Modulator.Frequency / static_cast<float>(device->Frequency)};
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1}));
if(mStep == 0)
mGetSamples = Modulate<One>;
else if(props->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID)
else if(props->Modulator.Waveform == ModulatorWaveform::Sinusoid)
mGetSamples = Modulate<Sin>;
else if(props->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH)
else if(props->Modulator.Waveform == ModulatorWaveform::Sawtooth)
mGetSamples = Modulate<Saw>;
else /*if(Slot->Params.EffectProps.Modulator.Waveform == AL_RING_MODULATOR_SQUARE)*/
else /*if(props->Modulator.Waveform == ModulatorWaveform::Square)*/
mGetSamples = Modulate<Square>;
ALfloat f0norm{props->Modulator.HighPassCutoff / static_cast<ALfloat>(device->Frequency)};
float f0norm{props->Modulator.HighPassCutoff / static_cast<float>(device->Frequency)};
f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
/* Bandwidth value is constant in octaves. */
mChans[0].Filter.setParams(BiquadType::HighPass, 1.0f, f0norm,
calc_rcpQ_from_bandwidth(f0norm, 0.75f));
for(ALsizei i{1};i < slot->Wet.NumChannels;++i)
mChans[0].Filter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
mChans[i].Filter.copyParamsFrom(mChans[0].Filter);
mOutBuffer = target.Main->Buffer;
mOutChannels = target.Main->NumChannels;
for(ALsizei i{0};i < slot->Wet.NumChannels;++i)
{
auto coeffs = GetAmbiIdentityRow(i);
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
}
mOutTarget = target.Main->Buffer;
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
}
void ModulatorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
void ModulatorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const ALsizei step = mStep;
ALsizei base;
for(base = 0;base < samplesToDo;)
for(size_t base{0u};base < samplesToDo;)
{
alignas(16) ALfloat modsamples[MAX_UPDATE_SAMPLES];
ALsizei td = mini(MAX_UPDATE_SAMPLES, samplesToDo-base);
ALsizei c, i;
alignas(16) float modsamples[MAX_UPDATE_SAMPLES];
const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
mGetSamples(modsamples, mIndex, step, td);
mIndex += (step*td) & WAVEFORM_FRACMASK;
mGetSamples(modsamples, mIndex, mStep, td);
mIndex += static_cast<uint>(mStep * td);
mIndex &= WAVEFORM_FRACMASK;
ASSUME(numInput > 0);
for(c = 0;c < numInput;c++)
auto chandata = std::begin(mChans);
for(const auto &input : samplesIn)
{
alignas(16) ALfloat temps[MAX_UPDATE_SAMPLES];
alignas(16) float temps[MAX_UPDATE_SAMPLES];
mChans[c].Filter.process(temps, &samplesIn[c][base], td);
for(i = 0;i < td;i++)
chandata->Filter.process({&input[base], td}, temps);
for(size_t i{0u};i < td;i++)
temps[i] *= modsamples[i];
MixSamples(temps, numOutput, samplesOut, mChans[c].CurrentGains,
mChans[c].TargetGains, samplesToDo-base, base, td);
MixSamples({temps, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
samplesToDo-base, base);
++chandata;
}
base += td;
@ -174,106 +169,11 @@ void ModulatorState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT sampl
}
void Modulator_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator frequency out of range");
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))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator high-pass cutoff out of range");
props->Modulator.HighPassCutoff = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
}
}
void Modulator_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Modulator_setParamf(props, context, param, vals[0]); }
void Modulator_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
Modulator_setParamf(props, context, param, static_cast<ALfloat>(val));
break;
case AL_RING_MODULATOR_WAVEFORM:
if(!(val >= AL_RING_MODULATOR_MIN_WAVEFORM && val <= AL_RING_MODULATOR_MAX_WAVEFORM))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid modulator waveform");
props->Modulator.Waveform = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
}
}
void Modulator_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Modulator_setParami(props, context, param, vals[0]); }
void Modulator_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
*val = static_cast<ALint>(props->Modulator.Frequency);
break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
*val = static_cast<ALint>(props->Modulator.HighPassCutoff);
break;
case AL_RING_MODULATOR_WAVEFORM:
*val = props->Modulator.Waveform;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
}
}
void Modulator_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Modulator_getParami(props, context, param, vals); }
void Modulator_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
*val = props->Modulator.Frequency;
break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
*val = props->Modulator.HighPassCutoff;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
}
}
void Modulator_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Modulator_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Modulator);
struct ModulatorStateFactory final : public EffectStateFactory {
EffectState *create() override { return new ModulatorState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Modulator_vtable; }
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new ModulatorState{}}; }
};
EffectProps ModulatorStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
props.Modulator.Waveform = AL_RING_MODULATOR_DEFAULT_WAVEFORM;
return props;
}
} // namespace
EffectStateFactory *ModulatorStateFactory_getFactory()

+ 27
- 104
modules/openal-soft/Alc/effects/null.cpp View File

@ -1,14 +1,17 @@
#include "config.h"
#include <cstdlib>
#include <stddef.h>
#include "AL/al.h"
#include "AL/alc.h"
#include "almalloc.h"
#include "alspan.h"
#include "base.h"
#include "core/bufferline.h"
#include "intrusive_ptr.h"
#include "alMain.h"
#include "alcontext.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
struct ContextBase;
struct DeviceBase;
struct EffectSlot;
namespace {
@ -17,9 +20,11 @@ struct NullState final : public EffectState {
NullState();
~NullState() override;
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(NullState)
};
@ -34,20 +39,20 @@ NullState::NullState() = default;
*/
NullState::~NullState() = default;
/* This updates the device-dependant effect state. This is called on
/* This updates the device-dependant effect state. This is called on state
* initialization and any time the device parameters (e.g. playback frequency,
* format) have been changed. Will always be followed by a call to the update
* method, if successful.
*/
ALboolean NullState::deviceUpdate(const ALCdevice* UNUSED(device))
void NullState::deviceUpdate(const DeviceBase* /*device*/, const Buffer& /*buffer*/)
{
return AL_TRUE;
}
/* This updates the effect state. This is called any time the effect is
* (re)loaded into a slot.
/* This updates the effect state with new properties. This is called any time
* the effect is (re)loaded into a slot.
*/
void NullState::update(const ALCcontext* UNUSED(context), const ALeffectslot* UNUSED(slot), const EffectProps* UNUSED(props), const EffectTarget UNUSED(target))
void NullState::update(const ContextBase* /*context*/, const EffectSlot* /*slot*/,
const EffectProps* /*props*/, const EffectTarget /*target*/)
{
}
@ -55,102 +60,20 @@ void NullState::update(const ALCcontext* UNUSED(context), const ALeffectslot* UN
* input to the output buffer. The result should be added to the output buffer,
* not replace it.
*/
void NullState::process(ALsizei /*samplesToDo*/, const ALfloat (*RESTRICT /*samplesIn*/)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT /*samplesOut*/)[BUFFERSIZE], const ALsizei /*numOutput*/)
{
}
void NullEffect_setParami(EffectProps *UNUSED(props), ALCcontext *context, ALenum param, ALint UNUSED(val))
{
switch(param)
{
default:
alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
}
}
void NullEffect_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{
switch(param)
{
default:
NullEffect_setParami(props, context, param, vals[0]);
}
}
void NullEffect_setParamf(EffectProps *UNUSED(props), ALCcontext *context, ALenum param, ALfloat UNUSED(val))
{
switch(param)
{
default:
alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
}
}
void NullEffect_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{
switch(param)
{
default:
NullEffect_setParamf(props, context, param, vals[0]);
}
}
void NullEffect_getParami(const EffectProps *UNUSED(props), ALCcontext *context, ALenum param, ALint* UNUSED(val))
{
switch(param)
{
default:
alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
}
}
void NullEffect_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{
switch(param)
{
default:
NullEffect_getParami(props, context, param, vals);
}
}
void NullEffect_getParamf(const EffectProps *UNUSED(props), ALCcontext *context, ALenum param, ALfloat* UNUSED(val))
{
switch(param)
{
default:
alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
}
}
void NullEffect_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
void NullState::process(const size_t/*samplesToDo*/,
const al::span<const FloatBufferLine> /*samplesIn*/,
const al::span<FloatBufferLine> /*samplesOut*/)
{
switch(param)
{
default:
NullEffect_getParamf(props, context, param, vals);
}
}
DEFINE_ALEFFECT_VTABLE(NullEffect);
struct NullStateFactory final : public EffectStateFactory {
EffectState *create() override;
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override;
al::intrusive_ptr<EffectState> create() override;
};
/* Creates EffectState objects of the appropriate type. */
EffectState *NullStateFactory::create()
{ return new NullState{}; }
/* Returns an ALeffectProps initialized with this effect type's default
* property values.
*/
EffectProps NullStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
return props;
}
/* Returns a pointer to this effect type's global set/get vtable. */
const EffectVtable *NullStateFactory::getEffectVtable() const noexcept
{ return &NullEffect_vtable; }
al::intrusive_ptr<EffectState> NullStateFactory::create()
{ return al::intrusive_ptr<EffectState>{new NullState{}}; }
} // namespace

+ 148
- 280
modules/openal-soft/Alc/effects/pshifter.cpp View File

@ -20,27 +20,33 @@
#include "config.h"
#ifdef HAVE_SSE_INTRINSICS
#include <emmintrin.h>
#endif
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <array>
#include <cmath>
#include <complex>
#include <algorithm>
#include "alMain.h"
#include "alcontext.h"
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "alu.h"
#include <cstdlib>
#include <iterator>
#include "alc/effects/base.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/bufferline.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
struct ContextBase;
namespace {
using uint = unsigned int;
using complex_d = std::complex<double>;
#define STFT_SIZE 1024
@ -50,353 +56,215 @@ using complex_d = std::complex;
#define STFT_STEP (STFT_SIZE / OVERSAMP)
#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1))
inline int double2int(double d)
{
#if defined(HAVE_SSE_INTRINSICS)
return _mm_cvttsd_si32(_mm_set_sd(d));
#elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
!defined(__SSE2_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2)
int sign, shift;
int64_t mant;
union {
double d;
int64_t i64;
} conv;
conv.d = d;
sign = (conv.i64>>63) | 1;
shift = ((conv.i64>>52)&0x7ff) - (1023+52);
/* Over/underflow */
if(UNLIKELY(shift >= 63 || shift < -52))
return 0;
mant = (conv.i64&0xfffffffffffff_i64) | 0x10000000000000_i64;
if(LIKELY(shift < 0))
return (int)(mant >> -shift) * sign;
return (int)(mant << shift) * sign;
#else
return static_cast<int>(d);
#endif
}
/* Define a Hann window, used to filter the STFT input and output. */
/* Making this constexpr seems to require C++14. */
std::array<ALdouble,STFT_SIZE> InitHannWindow()
std::array<double,STFT_SIZE> InitHannWindow()
{
std::array<ALdouble,STFT_SIZE> ret;
/* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
for(ALsizei i{0};i < STFT_SIZE>>1;i++)
std::array<double,STFT_SIZE> ret;
/* Create lookup table of the Hann window for the desired size, i.e. STFT_SIZE */
for(size_t i{0};i < STFT_SIZE>>1;i++)
{
ALdouble val = std::sin(al::MathDefs<double>::Pi() * i / ALdouble{STFT_SIZE-1});
constexpr double scale{al::numbers::pi / double{STFT_SIZE}};
const double val{std::sin(static_cast<double>(i+1) * scale)};
ret[i] = ret[STFT_SIZE-1-i] = val * val;
}
return ret;
}
alignas(16) const std::array<ALdouble,STFT_SIZE> HannWindow = InitHannWindow();
alignas(16) const std::array<double,STFT_SIZE> HannWindow = InitHannWindow();
struct ALphasor {
ALdouble Amplitude;
ALdouble Phase;
};
struct ALfrequencyDomain {
ALdouble Amplitude;
ALdouble Frequency;
struct FrequencyBin {
double Amplitude;
double FreqBin;
};
/* Converts complex to ALphasor */
inline ALphasor rect2polar(const complex_d &number)
{
ALphasor polar;
polar.Amplitude = std::abs(number);
polar.Phase = std::arg(number);
return polar;
}
/* Converts ALphasor to complex */
inline complex_d polar2rect(const ALphasor &number)
{ return std::polar<double>(number.Amplitude, number.Phase); }
struct PshifterState final : public EffectState {
/* Effect parameters */
ALsizei mCount;
ALsizei mPitchShiftI;
ALfloat mPitchShift;
ALfloat mFreqPerBin;
size_t mCount;
size_t mPos;
uint mPitchShiftI;
double mPitchShift;
/* Effects buffers */
ALfloat mInFIFO[STFT_SIZE];
ALfloat mOutFIFO[STFT_STEP];
ALdouble mLastPhase[STFT_HALF_SIZE+1];
ALdouble mSumPhase[STFT_HALF_SIZE+1];
ALdouble mOutputAccum[STFT_SIZE];
std::array<double,STFT_SIZE> mFIFO;
std::array<double,STFT_HALF_SIZE+1> mLastPhase;
std::array<double,STFT_HALF_SIZE+1> mSumPhase;
std::array<double,STFT_SIZE> mOutputAccum;
complex_d mFFTbuffer[STFT_SIZE];
std::array<complex_d,STFT_SIZE> mFftBuffer;
ALfrequencyDomain mAnalysis_buffer[STFT_HALF_SIZE+1];
ALfrequencyDomain mSyntesis_buffer[STFT_HALF_SIZE+1];
std::array<FrequencyBin,STFT_HALF_SIZE+1> mAnalysisBuffer;
std::array<FrequencyBin,STFT_HALF_SIZE+1> mSynthesisBuffer;
alignas(16) ALfloat mBufferOut[BUFFERSIZE];
alignas(16) FloatBufferLine mBufferOut;
/* Effect gains for each output channel */
ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS];
ALfloat mTargetGains[MAX_OUTPUT_CHANNELS];
float mCurrentGains[MAX_OUTPUT_CHANNELS];
float mTargetGains[MAX_OUTPUT_CHANNELS];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(PshifterState)
};
ALboolean PshifterState::deviceUpdate(const ALCdevice *device)
void PshifterState::deviceUpdate(const DeviceBase*, const Buffer&)
{
/* (Re-)initializing parameters and clear the buffers. */
mCount = FIFO_LATENCY;
mPitchShiftI = FRACTIONONE;
mPitchShift = 1.0f;
mFreqPerBin = device->Frequency / static_cast<ALfloat>(STFT_SIZE);
std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f);
std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), 0.0f);
std::fill(std::begin(mLastPhase), std::end(mLastPhase), 0.0);
std::fill(std::begin(mSumPhase), std::end(mSumPhase), 0.0);
std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), 0.0);
std::fill(std::begin(mFFTbuffer), std::end(mFFTbuffer), complex_d{});
std::fill(std::begin(mAnalysis_buffer), std::end(mAnalysis_buffer), ALfrequencyDomain{});
std::fill(std::begin(mSyntesis_buffer), std::end(mSyntesis_buffer), ALfrequencyDomain{});
mCount = 0;
mPos = FIFO_LATENCY;
mPitchShiftI = MixerFracOne;
mPitchShift = 1.0;
std::fill(mFIFO.begin(), mFIFO.end(), 0.0);
std::fill(mLastPhase.begin(), mLastPhase.end(), 0.0);
std::fill(mSumPhase.begin(), mSumPhase.end(), 0.0);
std::fill(mOutputAccum.begin(), mOutputAccum.end(), 0.0);
std::fill(mFftBuffer.begin(), mFftBuffer.end(), complex_d{});
std::fill(mAnalysisBuffer.begin(), mAnalysisBuffer.end(), FrequencyBin{});
std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{});
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
return AL_TRUE;
}
void PshifterState::update(const ALCcontext* UNUSED(context), const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
void PshifterState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
const float pitch{std::pow(2.0f,
static_cast<ALfloat>(props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune) / 1200.0f
)};
mPitchShiftI = fastf2i(pitch*FRACTIONONE);
mPitchShift = mPitchShiftI * (1.0f/FRACTIONONE);
ALfloat coeffs[MAX_AMBI_CHANNELS];
CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
mOutBuffer = target.Main->Buffer;
mOutChannels = target.Main->NumChannels;
ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains);
const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune};
const float pitch{std::pow(2.0f, static_cast<float>(tune) / 1200.0f)};
mPitchShiftI = fastf2u(pitch*MixerFracOne);
mPitchShift = mPitchShiftI * double{1.0/MixerFracOne};
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), slot->Gain, mTargetGains);
}
void PshifterState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei /*numInput*/, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
void PshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
/* Pitch shifter engine based on the work of Stephan Bernsee.
* http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
*/
static constexpr ALdouble expected{al::MathDefs<double>::Tau() / OVERSAMP};
const ALdouble freq_per_bin{mFreqPerBin};
ALfloat *RESTRICT bufferOut{mBufferOut};
ALsizei count{mCount};
/* Cycle offset per update expected of each frequency bin (bin 0 is none,
* bin 1 is x1, bin 2 is x2, etc).
*/
constexpr double expected_cycles{al::numbers::pi*2.0 / OVERSAMP};
for(ALsizei i{0};i < samplesToDo;)
for(size_t base{0u};base < samplesToDo;)
{
do {
/* Fill FIFO buffer with samples data */
mInFIFO[count] = samplesIn[0][i];
bufferOut[i] = mOutFIFO[count - FIFO_LATENCY];
const size_t todo{minz(STFT_STEP-mCount, samplesToDo-base)};
count++;
} while(++i < samplesToDo && count < STFT_SIZE);
/* Retrieve the output samples from the FIFO and fill in the new input
* samples.
*/
auto fifo_iter = mFIFO.begin()+mPos + mCount;
std::transform(fifo_iter, fifo_iter+todo, mBufferOut.begin()+base,
[](double d) noexcept -> float { return static_cast<float>(d); });
/* Check whether FIFO buffer is filled */
if(count < STFT_SIZE) break;
count = FIFO_LATENCY;
std::copy_n(samplesIn[0].begin()+base, todo, fifo_iter);
mCount += todo;
base += todo;
/* Real signal windowing and store in FFTbuffer */
for(ALsizei k{0};k < STFT_SIZE;k++)
{
mFFTbuffer[k].real(mInFIFO[k] * HannWindow[k]);
mFFTbuffer[k].imag(0.0);
}
/* Check whether FIFO buffer is filled with new samples. */
if(mCount < STFT_STEP) break;
mCount = 0;
mPos = (mPos+STFT_STEP) & (mFIFO.size()-1);
/* ANALYSIS */
/* Apply FFT to FFTbuffer data */
complex_fft(mFFTbuffer, STFT_SIZE, -1.0);
/* Time-domain signal windowing, store in FftBuffer, and apply a
* forward FFT to get the frequency-domain signal.
*/
for(size_t src{mPos}, k{0u};src < STFT_SIZE;++src,++k)
mFftBuffer[k] = mFIFO[src] * HannWindow[k];
for(size_t src{0u}, k{STFT_SIZE-mPos};src < mPos;++src,++k)
mFftBuffer[k] = mFIFO[src] * HannWindow[k];
forward_fft(mFftBuffer);
/* Analyze the obtained data. Since the real FFT is symmetric, only
* STFT_HALF_SIZE+1 samples are needed.
*/
for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++)
for(size_t k{0u};k < STFT_HALF_SIZE+1;k++)
{
/* Compute amplitude and phase */
ALphasor component{rect2polar(mFFTbuffer[k])};
const double amplitude{std::abs(mFftBuffer[k])};
const double phase{std::arg(mFftBuffer[k])};
/* Compute phase difference and subtract expected phase difference */
double tmp{(component.Phase - mLastPhase[k]) - k*expected};
double tmp{(phase - mLastPhase[k]) - static_cast<double>(k)*expected_cycles};
/* Map delta phase into +/- Pi interval */
int qpd{double2int(tmp / al::MathDefs<double>::Pi())};
tmp -= al::MathDefs<double>::Pi() * (qpd + (qpd%2));
int qpd{double2int(tmp / al::numbers::pi)};
tmp -= al::numbers::pi * (qpd + (qpd%2));
/* Get deviation from bin frequency from the +/- Pi interval */
tmp /= expected;
tmp /= expected_cycles;
/* Compute the k-th partials' true frequency, twice the amplitude
* for maintain the gain (because half of bins are used) and store
* amplitude and true frequency in analysis buffer.
/* Compute the k-th partials' true frequency and store the
* amplitude and frequency bin in the analysis buffer.
*/
mAnalysis_buffer[k].Amplitude = 2.0 * component.Amplitude;
mAnalysis_buffer[k].Frequency = (k + tmp) * freq_per_bin;
/* Store actual phase[k] for the calculations in the next frame*/
mLastPhase[k] = component.Phase;
}
mAnalysisBuffer[k].Amplitude = amplitude;
mAnalysisBuffer[k].FreqBin = static_cast<double>(k) + tmp;
/* PROCESSING */
/* pitch shifting */
for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++)
{
mSyntesis_buffer[k].Amplitude = 0.0;
mSyntesis_buffer[k].Frequency = 0.0;
/* Store the actual phase[k] for the next frame. */
mLastPhase[k] = phase;
}
for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++)
/* Shift the frequency bins according to the pitch adjustment,
* accumulating the amplitudes of overlapping frequency bins.
*/
std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{});
const size_t bin_count{minz(STFT_HALF_SIZE+1,
(((STFT_HALF_SIZE+1)<<MixerFracBits) - (MixerFracOne>>1) - 1)/mPitchShiftI + 1)};
for(size_t k{0u};k < bin_count;k++)
{
ALsizei j{(k*mPitchShiftI) >> FRACTIONBITS};
if(j >= STFT_HALF_SIZE+1) break;
mSyntesis_buffer[j].Amplitude += mAnalysis_buffer[k].Amplitude;
mSyntesis_buffer[j].Frequency = mAnalysis_buffer[k].Frequency * mPitchShift;
const size_t j{(k*mPitchShiftI + (MixerFracOne>>1)) >> MixerFracBits};
mSynthesisBuffer[j].Amplitude += mAnalysisBuffer[k].Amplitude;
mSynthesisBuffer[j].FreqBin = mAnalysisBuffer[k].FreqBin * mPitchShift;
}
/* SYNTHESIS */
/* Synthesis the processing data */
for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++)
/* Reconstruct the frequency-domain signal from the adjusted frequency
* bins.
*/
for(size_t k{0u};k < STFT_HALF_SIZE+1;k++)
{
ALphasor component;
ALdouble tmp;
/* Compute bin deviation from scaled freq */
tmp = mSyntesis_buffer[k].Frequency/freq_per_bin - k;
/* Calculate actual delta phase and accumulate it to get bin phase */
mSumPhase[k] += (k + tmp) * expected;
mSumPhase[k] += mSynthesisBuffer[k].FreqBin * expected_cycles;
component.Amplitude = mSyntesis_buffer[k].Amplitude;
component.Phase = mSumPhase[k];
/* Compute phasor component to cartesian complex number and storage it into FFTbuffer*/
mFFTbuffer[k] = polar2rect(component);
mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Amplitude, mSumPhase[k]);
}
/* zero negative frequencies for recontruct a real signal */
for(ALsizei k{STFT_HALF_SIZE+1};k < STFT_SIZE;k++)
mFFTbuffer[k] = complex_d{};
/* Apply iFFT to buffer data */
complex_fft(mFFTbuffer, STFT_SIZE, 1.0);
/* Windowing and add to output */
for(ALsizei k{0};k < STFT_SIZE;k++)
mOutputAccum[k] += HannWindow[k] * mFFTbuffer[k].real() /
(0.5 * STFT_HALF_SIZE * OVERSAMP);
/* Shift accumulator, input & output FIFO */
ALsizei j, k;
for(k = 0;k < STFT_STEP;k++) mOutFIFO[k] = static_cast<ALfloat>(mOutputAccum[k]);
for(j = 0;k < STFT_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k];
for(;j < STFT_SIZE;j++) mOutputAccum[j] = 0.0;
for(k = 0;k < FIFO_LATENCY;k++)
mInFIFO[k] = mInFIFO[k+STFT_STEP];
}
mCount = count;
/* Now, mix the processed sound data to the output. */
MixSamples(bufferOut, numOutput, samplesOut, mCurrentGains, mTargetGains,
maxi(samplesToDo, 512), 0, samplesToDo);
}
for(size_t k{STFT_HALF_SIZE+1};k < STFT_SIZE;++k)
mFftBuffer[k] = std::conj(mFftBuffer[STFT_SIZE-k]);
void Pshifter_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat)
{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); }
void Pshifter_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", param); }
void Pshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_PITCH_SHIFTER_COARSE_TUNE:
if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter coarse tune out of range");
props->Pshifter.CoarseTune = val;
break;
case AL_PITCH_SHIFTER_FINE_TUNE:
if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter fine tune out of range");
props->Pshifter.FineTune = val;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param);
/* Apply an inverse FFT to get the time-domain siganl, and accumulate
* for the output with windowing.
*/
inverse_fft(mFftBuffer);
for(size_t dst{mPos}, k{0u};dst < STFT_SIZE;++dst,++k)
mOutputAccum[dst] += HannWindow[k]*mFftBuffer[k].real() * (4.0/OVERSAMP/STFT_SIZE);
for(size_t dst{0u}, k{STFT_SIZE-mPos};dst < mPos;++dst,++k)
mOutputAccum[dst] += HannWindow[k]*mFftBuffer[k].real() * (4.0/OVERSAMP/STFT_SIZE);
/* Copy out the accumulated result, then clear for the next iteration. */
std::copy_n(mOutputAccum.begin() + mPos, STFT_STEP, mFIFO.begin() + mPos);
std::fill_n(mOutputAccum.begin() + mPos, STFT_STEP, 0.0);
}
}
void Pshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Pshifter_setParami(props, context, param, vals[0]); }
void Pshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
{
case AL_PITCH_SHIFTER_COARSE_TUNE:
*val = props->Pshifter.CoarseTune;
break;
case AL_PITCH_SHIFTER_FINE_TUNE:
*val = props->Pshifter.FineTune;
break;
default:
alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param);
}
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
maxz(samplesToDo, 512), 0);
}
void Pshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Pshifter_getParami(props, context, param, vals); }
void Pshifter_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); }
void Pshifter_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param); }
DEFINE_ALEFFECT_VTABLE(Pshifter);
struct PshifterStateFactory final : public EffectStateFactory {
EffectState *create() override;
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Pshifter_vtable; }
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new PshifterState{}}; }
};
EffectState *PshifterStateFactory::create()
{ return new PshifterState{}; }
EffectProps PshifterStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
return props;
}
} // namespace
EffectStateFactory *PshifterStateFactory_getFactory()

+ 780
- 1156
modules/openal-soft/Alc/effects/reverb.cpp
File diff suppressed because it is too large
View File


+ 337
- 0
modules/openal-soft/Alc/effects/vmorpher.cpp View File

@ -0,0 +1,337 @@
/**
* This file is part of the OpenAL Soft cross platform audio library
*
* Copyright (C) 2019 by Anis A. Hireche
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Spherical-Harmonic-Transform nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <algorithm>
#include <array>
#include <cstdlib>
#include <functional>
#include <iterator>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
namespace {
using uint = unsigned int;
#define MAX_UPDATE_SAMPLES 256
#define NUM_FORMANTS 4
#define NUM_FILTERS 2
#define Q_FACTOR 5.0f
#define VOWEL_A_INDEX 0
#define VOWEL_B_INDEX 1
#define WAVEFORM_FRACBITS 24
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
inline float Sin(uint index)
{
constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE};
return std::sin(static_cast<float>(index) * scale)*0.5f + 0.5f;
}
inline float Saw(uint index)
{ return static_cast<float>(index) / float{WAVEFORM_FRACONE}; }
inline float Triangle(uint index)
{ return std::fabs(static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); }
inline float Half(uint) { return 0.5f; }
template<float (&func)(uint)>
void Oscillate(float *RESTRICT dst, uint index, const uint step, size_t todo)
{
for(size_t i{0u};i < todo;i++)
{
index += step;
index &= WAVEFORM_FRACMASK;
dst[i] = func(index);
}
}
struct FormantFilter
{
float mCoeff{0.0f};
float mGain{1.0f};
float mS1{0.0f};
float mS2{0.0f};
FormantFilter() = default;
FormantFilter(float f0norm, float gain)
: mCoeff{std::tan(al::numbers::pi_v<float> * f0norm)}, mGain{gain}
{ }
inline void process(const float *samplesIn, float *samplesOut, const size_t numInput)
{
/* A state variable filter from a topology-preserving transform.
* Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg
*/
const float g{mCoeff};
const float gain{mGain};
const float h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))};
float s1{mS1};
float s2{mS2};
for(size_t i{0u};i < numInput;i++)
{
const float H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h};
const float B{g*H + s1};
const float L{g*B + s2};
s1 = g*H + B;
s2 = g*B + L;
// Apply peak and accumulate samples.
samplesOut[i] += B * gain;
}
mS1 = s1;
mS2 = s2;
}
inline void clear()
{
mS1 = 0.0f;
mS2 = 0.0f;
}
};
struct VmorpherState final : public EffectState {
struct {
/* Effect parameters */
FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS];
/* Effect gains for each channel */
float CurrentGains[MAX_OUTPUT_CHANNELS]{};
float TargetGains[MAX_OUTPUT_CHANNELS]{};
} mChans[MaxAmbiChannels];
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
uint mIndex{0};
uint mStep{1};
/* Effects buffers */
alignas(16) float mSampleBufferA[MAX_UPDATE_SAMPLES]{};
alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{};
alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{};
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
static std::array<FormantFilter,4> getFiltersByPhoneme(VMorpherPhenome phoneme,
float frequency, float pitch);
DEF_NEWDEL(VmorpherState)
};
std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme,
float frequency, float pitch)
{
/* Using soprano formant set of values to
* better match mid-range frequency space.
*
* See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html
*/
switch(phoneme)
{
case VMorpherPhenome::A:
return {{
{( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
{(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */
{(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */
{(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */
}};
case VMorpherPhenome::E:
return {{
{( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
{(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */
{(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */
{(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
}};
case VMorpherPhenome::I:
return {{
{( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
{(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */
{(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */
{(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */
}};
case VMorpherPhenome::O:
return {{
{( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
{( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */
{(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */
{(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */
}};
case VMorpherPhenome::U:
return {{
{( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
{( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */
{(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */
{(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
}};
default:
break;
}
return {};
}
void VmorpherState::deviceUpdate(const DeviceBase*, const Buffer&)
{
for(auto &e : mChans)
{
std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]),
std::mem_fn(&FormantFilter::clear));
std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]),
std::mem_fn(&FormantFilter::clear));
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
}
}
void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
const DeviceBase *device{context->mDevice};
const float frequency{static_cast<float>(device->Frequency)};
const float step{props->Vmorpher.Rate / frequency};
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1}));
if(mStep == 0)
mGetSamples = Oscillate<Half>;
else if(props->Vmorpher.Waveform == VMorpherWaveform::Sinusoid)
mGetSamples = Oscillate<Sin>;
else if(props->Vmorpher.Waveform == VMorpherWaveform::Triangle)
mGetSamples = Oscillate<Triangle>;
else /*if(props->Vmorpher.Waveform == VMorpherWaveform::Sawtooth)*/
mGetSamples = Oscillate<Saw>;
const float pitchA{std::pow(2.0f,
static_cast<float>(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)};
const float pitchB{std::pow(2.0f,
static_cast<float>(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)};
auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA);
auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB);
/* Copy the filter coefficients to the input channels. */
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
{
std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].Formants[VOWEL_A_INDEX]));
std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[VOWEL_B_INDEX]));
}
mOutTarget = target.Main->Buffer;
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
}
void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
/* Following the EFX specification for a conformant implementation which describes
* the effect as a pair of 4-band formant filters blended together using an LFO.
*/
for(size_t base{0u};base < samplesToDo;)
{
const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
mGetSamples(mLfo, mIndex, mStep, td);
mIndex += static_cast<uint>(mStep * td);
mIndex &= WAVEFORM_FRACMASK;
auto chandata = std::begin(mChans);
for(const auto &input : samplesIn)
{
auto& vowelA = chandata->Formants[VOWEL_A_INDEX];
auto& vowelB = chandata->Formants[VOWEL_B_INDEX];
/* Process first vowel. */
std::fill_n(std::begin(mSampleBufferA), td, 0.0f);
vowelA[0].process(&input[base], mSampleBufferA, td);
vowelA[1].process(&input[base], mSampleBufferA, td);
vowelA[2].process(&input[base], mSampleBufferA, td);
vowelA[3].process(&input[base], mSampleBufferA, td);
/* Process second vowel. */
std::fill_n(std::begin(mSampleBufferB), td, 0.0f);
vowelB[0].process(&input[base], mSampleBufferB, td);
vowelB[1].process(&input[base], mSampleBufferB, td);
vowelB[2].process(&input[base], mSampleBufferB, td);
vowelB[3].process(&input[base], mSampleBufferB, td);
alignas(16) float blended[MAX_UPDATE_SAMPLES];
for(size_t i{0u};i < td;i++)
blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]);
/* Now, mix the processed sound data to the output. */
MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
samplesToDo-base, base);
++chandata;
}
base += td;
}
}
struct VmorpherStateFactory final : public EffectStateFactory {
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new VmorpherState{}}; }
};
} // namespace
EffectStateFactory *VmorpherStateFactory_getFactory()
{
static VmorpherStateFactory VmorpherFactory{};
return &VmorpherFactory;
}

+ 0
- 137
modules/openal-soft/Alc/filters/biquad.h View File

@ -1,137 +0,0 @@
#ifndef FILTERS_BIQUAD_H
#define FILTERS_BIQUAD_H
#include <cmath>
#include <utility>
#include "AL/al.h"
#include "math_defs.h"
/* Filters implementation is based on the "Cookbook formulae for audio
* EQ biquad filter coefficients" by Robert Bristow-Johnson
* http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
*/
/* Implementation note: For the shelf filters, the specified gain is for the
* reference frequency, which is the centerpoint of the transition band. This
* better matches EFX filter design. To set the gain for the shelf itself, use
* the square root of the desired linear gain (or halve the dB gain).
*/
enum class BiquadType {
/** EFX-style low-pass filter, specifying a gain and reference frequency. */
HighShelf,
/** EFX-style high-pass filter, specifying a gain and reference frequency. */
LowShelf,
/** Peaking filter, specifying a gain and reference frequency. */
Peaking,
/** Low-pass cut-off filter, specifying a cut-off frequency. */
LowPass,
/** High-pass cut-off filter, specifying a cut-off frequency. */
HighPass,
/** Band-pass filter, specifying a center frequency. */
BandPass,
};
template<typename Real>
class BiquadFilterR {
/* Last two delayed components for direct form II. */
Real z1{0.0f}, z2{0.0f};
/* Transfer function coefficients "b" (numerator) */
Real b0{1.0f}, b1{0.0f}, b2{0.0f};
/* Transfer function coefficients "a" (denominator; a0 is pre-applied). */
Real a1{0.0f}, a2{0.0f};
public:
void clear() noexcept { z1 = z2 = 0.0f; }
/**
* Sets the filter state for the specified filter type and its parameters.
*
* \param type The type of filter to apply.
* \param gain The gain for the reference frequency response. Only used by
* the Shelf and Peaking filter types.
* \param f0norm The reference frequency normal (ref_freq / sample_rate).
* This is the center point for the Shelf, Peaking, and
* BandPass filter types, or the cutoff frequency for the
* LowPass and HighPass filter types.
* \param rcpQ The reciprocal of the Q coefficient for the filter's
* transition band. Can be generated from calc_rcpQ_from_slope
* or calc_rcpQ_from_bandwidth as needed.
*/
void setParams(BiquadType type, Real gain, Real f0norm, Real rcpQ);
void copyParamsFrom(const BiquadFilterR &other)
{
b0 = other.b0;
b1 = other.b1;
b2 = other.b2;
a1 = other.a1;
a2 = other.a2;
}
void process(Real *dst, const Real *src, int numsamples);
void passthru(int numsamples) noexcept
{
if(LIKELY(numsamples >= 2))
{
z1 = 0.0f;
z2 = 0.0f;
}
else if(numsamples == 1)
{
z1 = z2;
z2 = 0.0f;
}
}
/* Rather hacky. It's just here to support "manual" processing. */
std::pair<Real,Real> getComponents() const noexcept
{ return {z1, z2}; }
void setComponents(Real z1_, Real z2_) noexcept
{ z1 = z1_; z2 = z2_; }
Real processOne(const Real in, Real &z1_, Real &z2_) const noexcept
{
Real out{in*b0 + z1_};
z1_ = in*b1 - out*a1 + z2_;
z2_ = in*b2 - out*a2;
return out;
}
};
using BiquadFilter = BiquadFilterR<float>;
/**
* Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using the
* reference gain and shelf slope parameter.
* \param gain 0 < gain
* \param slope 0 < slope <= 1
*/
inline float calc_rcpQ_from_slope(float gain, float slope)
{ return std::sqrt((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f); }
inline double calc_rcpQ_from_slope(double gain, double slope)
{ return std::sqrt((gain + 1.0/gain)*(1.0/slope - 1.0) + 2.0); }
/**
* Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the normalized
* reference frequency and bandwidth.
* \param f0norm 0 < f0norm < 0.5.
* \param bandwidth 0 < bandwidth
*/
inline float calc_rcpQ_from_bandwidth(float f0norm, float bandwidth)
{
const float w0{al::MathDefs<float>::Tau() * f0norm};
return 2.0f*std::sinh(std::log(2.0f)/2.0f*bandwidth*w0/std::sin(w0));
}
inline double calc_rcpQ_from_bandwidth(double f0norm, double bandwidth)
{
const double w0{al::MathDefs<double>::Tau() * f0norm};
return 2.0*std::sinh(std::log(2.0)/2.0*bandwidth*w0/std::sin(w0));
}
#endif /* FILTERS_BIQUAD_H */

+ 0
- 132
modules/openal-soft/Alc/filters/splitter.cpp View File

@ -1,132 +0,0 @@
#include "config.h"
#include "splitter.h"
#include <cmath>
#include <limits>
#include <algorithm>
#include "math_defs.h"
template<typename Real>
void BandSplitterR<Real>::init(Real f0norm)
{
const Real w{f0norm * al::MathDefs<Real>::Tau()};
const Real cw{std::cos(w)};
if(cw > std::numeric_limits<float>::epsilon())
coeff = (std::sin(w) - 1.0f) / cw;
else
coeff = cw * -0.5f;
lp_z1 = 0.0f;
lp_z2 = 0.0f;
ap_z1 = 0.0f;
}
template<typename Real>
void BandSplitterR<Real>::process(Real *hpout, Real *lpout, const Real *input, const int count)
{
ASSUME(count > 0);
const Real ap_coeff{this->coeff};
const Real lp_coeff{this->coeff*0.5f + 0.5f};
Real lp_z1{this->lp_z1};
Real lp_z2{this->lp_z2};
Real ap_z1{this->ap_z1};
auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpout](const Real in) noexcept -> Real
{
/* Low-pass sample processing. */
Real d{(in - lp_z1) * lp_coeff};
Real lp_y{lp_z1 + d};
lp_z1 = lp_y + d;
d = (lp_y - lp_z2) * lp_coeff;
lp_y = lp_z2 + d;
lp_z2 = lp_y + d;
*(lpout++) = lp_y;
/* All-pass sample processing. */
Real ap_y{in*ap_coeff + ap_z1};
ap_z1 = in - ap_y*ap_coeff;
/* High-pass generated from removing low-passed output. */
return ap_y - lp_y;
};
std::transform(input, input+count, hpout, proc_sample);
this->lp_z1 = lp_z1;
this->lp_z2 = lp_z2;
this->ap_z1 = ap_z1;
}
template<typename Real>
void BandSplitterR<Real>::applyHfScale(Real *samples, const Real hfscale, const int count)
{
ASSUME(count > 0);
const Real ap_coeff{this->coeff};
const Real lp_coeff{this->coeff*0.5f + 0.5f};
Real lp_z1{this->lp_z1};
Real lp_z2{this->lp_z2};
Real ap_z1{this->ap_z1};
auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real
{
/* Low-pass sample processing. */
Real d{(in - lp_z1) * lp_coeff};
Real lp_y{lp_z1 + d};
lp_z1 = lp_y + d;
d = (lp_y - lp_z2) * lp_coeff;
lp_y = lp_z2 + d;
lp_z2 = lp_y + d;
/* All-pass sample processing. */
Real ap_y{in*ap_coeff + ap_z1};
ap_z1 = in - ap_y*ap_coeff;
/* High-pass generated from removing low-passed output. */
return (ap_y-lp_y)*hfscale + lp_y;
};
std::transform(samples, samples+count, samples, proc_sample);
this->lp_z1 = lp_z1;
this->lp_z2 = lp_z2;
this->ap_z1 = ap_z1;
}
template class BandSplitterR<float>;
template class BandSplitterR<double>;
template<typename Real>
void SplitterAllpassR<Real>::init(Real f0norm)
{
const Real w{f0norm * al::MathDefs<Real>::Tau()};
const Real cw{std::cos(w)};
if(cw > std::numeric_limits<float>::epsilon())
coeff = (std::sin(w) - 1.0f) / cw;
else
coeff = cw * -0.5f;
z1 = 0.0f;
}
template<typename Real>
void SplitterAllpassR<Real>::process(Real *samples, int count)
{
ASSUME(count > 0);
const Real coeff{this->coeff};
Real z1{this->z1};
auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real
{
const Real out{in*coeff + z1};
z1 = in - out*coeff;
return out;
};
std::transform(samples, samples+count, samples, proc_sample);
this->z1 = z1;
}
template class SplitterAllpassR<float>;
template class SplitterAllpassR<double>;

+ 0
- 63
modules/openal-soft/Alc/filters/splitter.h View File

@ -1,63 +0,0 @@
#ifndef FILTER_SPLITTER_H
#define FILTER_SPLITTER_H
#include "alMain.h"
#include "almalloc.h"
/* Band splitter. Splits a signal into two phase-matching frequency bands. */
template<typename Real>
class BandSplitterR {
Real coeff{0.0f};
Real lp_z1{0.0f};
Real lp_z2{0.0f};
Real ap_z1{0.0f};
public:
BandSplitterR() = default;
BandSplitterR(const BandSplitterR&) = default;
BandSplitterR(Real f0norm) { init(f0norm); }
void init(Real f0norm);
void clear() noexcept { lp_z1 = lp_z2 = ap_z1 = 0.0f; }
void process(Real *hpout, Real *lpout, const Real *input, const int count);
void applyHfScale(Real *samples, const Real hfscale, const int count);
};
using BandSplitter = BandSplitterR<float>;
/* The all-pass portion of the band splitter. Applies the same phase shift
* without splitting the signal.
*/
template<typename Real>
class SplitterAllpassR {
Real coeff{0.0f};
Real z1{0.0f};
public:
SplitterAllpassR() = default;
SplitterAllpassR(const SplitterAllpassR&) = default;
SplitterAllpassR(Real f0norm) { init(f0norm); }
void init(Real f0norm);
void clear() noexcept { z1 = 0.0f; }
void process(Real *samples, int count);
};
using SplitterAllpass = SplitterAllpassR<float>;
struct FrontStablizer {
static constexpr size_t DelayLength{256u};
alignas(16) float DelayBuf[MAX_OUTPUT_CHANNELS][DelayLength];
SplitterAllpass APFilter;
BandSplitter LFilter, RFilter;
alignas(16) float LSplit[2][BUFFERSIZE];
alignas(16) float RSplit[2][BUFFERSIZE];
alignas(16) float TempBuf[BUFFERSIZE + DelayLength];
DEF_NEWDEL(FrontStablizer)
};
#endif /* FILTER_SPLITTER_H */

+ 0
- 25
modules/openal-soft/Alc/fpu_modes.h View File

@ -1,25 +0,0 @@
#ifndef FPU_MODES_H
#define FPU_MODES_H
class FPUCtl {
#if defined(HAVE_SSE_INTRINSICS) || (defined(__GNUC__) && defined(HAVE_SSE))
unsigned int sse_state{};
#endif
bool in_mode{};
public:
FPUCtl();
/* HACK: 32-bit targets for GCC seem to have a problem here with certain
* noexcept methods (which destructors are) causing an internal compiler
* error. No idea why it's these methods specifically, but this is needed
* to get it to compile.
*/
~FPUCtl() noexcept(false) { leave(); }
FPUCtl(const FPUCtl&) = delete;
FPUCtl& operator=(const FPUCtl&) = delete;
void leave();
};
#endif /* FPU_MODES_H */

+ 0
- 740
modules/openal-soft/Alc/helpers.cpp View File

@ -1,740 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2011 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#ifdef _WIN32
#ifdef __MINGW32__
#define _WIN32_IE 0x501
#else
#define _WIN32_IE 0x400
#endif
#endif
#include "config.h"
#include <cstdlib>
#include <ctime>
#include <cerrno>
#include <cstdarg>
#include <cctype>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#ifdef HAVE_PROC_PIDPATH
#include <libproc.h>
#endif
#ifdef __FreeBSD__
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
#ifndef AL_NO_UID_DEFS
#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H)
#define INITGUID
#include <windows.h>
#ifdef HAVE_GUIDDEF_H
#include <guiddef.h>
#else
#include <initguid.h>
#endif
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71);
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71);
DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x00,0xa0,0xc9,0x25,0xcd,0x16);
DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e);
DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6);
DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2);
DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2);
DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4,0xde, 0x18,0x5c,0x39,0x5c,0xd3,0x17);
#ifdef HAVE_WASAPI
#include <wtypes.h>
#include <devpropdef.h>
#include <propkeydef.h>
DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
#endif
#endif
#endif /* AL_NO_UID_DEFS */
#ifdef HAVE_DLFCN_H
#include <dlfcn.h>
#endif
#ifdef HAVE_INTRIN_H
#include <intrin.h>
#endif
#ifdef HAVE_CPUID_H
#include <cpuid.h>
#endif
#ifdef HAVE_SSE_INTRINSICS
#include <xmmintrin.h>
#endif
#ifdef HAVE_SYS_SYSCONF_H
#include <sys/sysconf.h>
#endif
#ifdef HAVE_FLOAT_H
#include <cfloat>
#endif
#ifdef HAVE_IEEEFP_H
#include <ieeefp.h>
#endif
#ifndef _WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#elif defined(_WIN32_IE)
#include <shlobj.h>
#endif
#include <mutex>
#include <vector>
#include <string>
#include <algorithm>
#include "alMain.h"
#include "alu.h"
#include "cpu_caps.h"
#include "fpu_modes.h"
#include "vector.h"
#include "compat.h"
#include "threads.h"
#if defined(HAVE_GCC_GET_CPUID) && (defined(__i386__) || defined(__x86_64__) || \
defined(_M_IX86) || defined(_M_X64))
using reg_type = unsigned int;
static inline void get_cpuid(int f, reg_type *regs)
{ __get_cpuid(f, &regs[0], &regs[1], &regs[2], &regs[3]); }
#define CAN_GET_CPUID
#elif defined(HAVE_CPUID_INTRINSIC) && (defined(__i386__) || defined(__x86_64__) || \
defined(_M_IX86) || defined(_M_X64))
using reg_type = int;
static inline void get_cpuid(int f, reg_type *regs)
{ (__cpuid)(regs, f); }
#define CAN_GET_CPUID
#endif
int CPUCapFlags = 0;
void FillCPUCaps(int capfilter)
{
int caps = 0;
/* FIXME: We really should get this for all available CPUs in case different
* CPUs have different caps (is that possible on one machine?). */
#ifdef CAN_GET_CPUID
union {
reg_type regs[4];
char str[sizeof(reg_type[4])];
} cpuinf[3] = {{ { 0, 0, 0, 0 } }};
get_cpuid(0, cpuinf[0].regs);
if(cpuinf[0].regs[0] == 0)
ERR("Failed to get CPUID\n");
else
{
unsigned int maxfunc = cpuinf[0].regs[0];
unsigned int maxextfunc;
get_cpuid(0x80000000, cpuinf[0].regs);
maxextfunc = cpuinf[0].regs[0];
TRACE("Detected max CPUID function: 0x%x (ext. 0x%x)\n", maxfunc, maxextfunc);
TRACE("Vendor ID: \"%.4s%.4s%.4s\"\n", cpuinf[0].str+4, cpuinf[0].str+12, cpuinf[0].str+8);
if(maxextfunc >= 0x80000004)
{
get_cpuid(0x80000002, cpuinf[0].regs);
get_cpuid(0x80000003, cpuinf[1].regs);
get_cpuid(0x80000004, cpuinf[2].regs);
TRACE("Name: \"%.16s%.16s%.16s\"\n", cpuinf[0].str, cpuinf[1].str, cpuinf[2].str);
}
if(maxfunc >= 1)
{
get_cpuid(1, cpuinf[0].regs);
if((cpuinf[0].regs[3]&(1<<25)))
caps |= CPU_CAP_SSE;
if((caps&CPU_CAP_SSE) && (cpuinf[0].regs[3]&(1<<26)))
caps |= CPU_CAP_SSE2;
if((caps&CPU_CAP_SSE2) && (cpuinf[0].regs[2]&(1<<0)))
caps |= CPU_CAP_SSE3;
if((caps&CPU_CAP_SSE3) && (cpuinf[0].regs[2]&(1<<19)))
caps |= CPU_CAP_SSE4_1;
}
}
#else
/* Assume support for whatever's supported if we can't check for it */
#if defined(HAVE_SSE4_1)
#warning "Assuming SSE 4.1 run-time support!"
caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1;
#elif defined(HAVE_SSE3)
#warning "Assuming SSE 3 run-time support!"
caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3;
#elif defined(HAVE_SSE2)
#warning "Assuming SSE 2 run-time support!"
caps |= CPU_CAP_SSE | CPU_CAP_SSE2;
#elif defined(HAVE_SSE)
#warning "Assuming SSE run-time support!"
caps |= CPU_CAP_SSE;
#endif
#endif
#ifdef HAVE_NEON
FILE *file = fopen("/proc/cpuinfo", "rt");
if(!file)
ERR("Failed to open /proc/cpuinfo, cannot check for NEON support\n");
else
{
std::string features;
char buf[256];
while(fgets(buf, sizeof(buf), file) != nullptr)
{
if(strncmp(buf, "Features\t:", 10) != 0)
continue;
features = buf+10;
while(features.back() != '\n')
{
if(fgets(buf, sizeof(buf), file) == nullptr)
break;
features += buf;
}
break;
}
fclose(file);
file = nullptr;
if(!features.empty())
{
const char *str = features.c_str();
while(isspace(str[0])) ++str;
TRACE("Got features string:%s\n", str);
while((str=strstr(str, "neon")) != nullptr)
{
if(isspace(*(str-1)) && (str[4] == 0 || isspace(str[4])))
{
caps |= CPU_CAP_NEON;
break;
}
++str;
}
}
}
#endif
TRACE("Extensions:%s%s%s%s%s%s\n",
((capfilter&CPU_CAP_SSE) ? ((caps&CPU_CAP_SSE) ? " +SSE" : " -SSE") : ""),
((capfilter&CPU_CAP_SSE2) ? ((caps&CPU_CAP_SSE2) ? " +SSE2" : " -SSE2") : ""),
((capfilter&CPU_CAP_SSE3) ? ((caps&CPU_CAP_SSE3) ? " +SSE3" : " -SSE3") : ""),
((capfilter&CPU_CAP_SSE4_1) ? ((caps&CPU_CAP_SSE4_1) ? " +SSE4.1" : " -SSE4.1") : ""),
((capfilter&CPU_CAP_NEON) ? ((caps&CPU_CAP_NEON) ? " +NEON" : " -NEON") : ""),
((!capfilter) ? " -none-" : "")
);
CPUCapFlags = caps & capfilter;
}
FPUCtl::FPUCtl()
{
#if defined(HAVE_SSE_INTRINSICS)
this->sse_state = _mm_getcsr();
unsigned int sseState = this->sse_state;
sseState |= 0x8000; /* set flush-to-zero */
sseState |= 0x0040; /* set denormals-are-zero */
_mm_setcsr(sseState);
#elif defined(__GNUC__) && defined(HAVE_SSE)
if((CPUCapFlags&CPU_CAP_SSE))
{
__asm__ __volatile__("stmxcsr %0" : "=m" (*&this->sse_state));
unsigned int sseState = this->sse_state;
sseState |= 0x8000; /* set flush-to-zero */
if((CPUCapFlags&CPU_CAP_SSE2))
sseState |= 0x0040; /* set denormals-are-zero */
__asm__ __volatile__("ldmxcsr %0" : : "m" (*&sseState));
}
#endif
this->in_mode = true;
}
void FPUCtl::leave()
{
if(!this->in_mode) return;
#if defined(HAVE_SSE_INTRINSICS)
_mm_setcsr(this->sse_state);
#elif defined(__GNUC__) && defined(HAVE_SSE)
if((CPUCapFlags&CPU_CAP_SSE))
__asm__ __volatile__("ldmxcsr %0" : : "m" (*&this->sse_state));
#endif
this->in_mode = false;
}
#ifdef _WIN32
const PathNamePair &GetProcBinary()
{
static PathNamePair ret;
if(!ret.fname.empty() || !ret.path.empty())
return ret;
al::vector<WCHAR> fullpath(256);
DWORD len;
while((len=GetModuleFileNameW(nullptr, fullpath.data(), static_cast<DWORD>(fullpath.size()))) == fullpath.size())
fullpath.resize(fullpath.size() << 1);
if(len == 0)
{
ERR("Failed to get process name: error %lu\n", GetLastError());
return ret;
}
fullpath.resize(len);
if(fullpath.back() != 0)
fullpath.push_back(0);
auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\');
sep = std::find(fullpath.rbegin()+1, sep, '/');
if(sep != fullpath.rend())
{
*sep = 0;
ret.fname = wstr_to_utf8(&*sep + 1);
ret.path = wstr_to_utf8(fullpath.data());
}
else
ret.fname = wstr_to_utf8(fullpath.data());
TRACE("Got: %s, %s\n", ret.path.c_str(), ret.fname.c_str());
return ret;
}
void *LoadLib(const char *name)
{
std::wstring wname{utf8_to_wstr(name)};
return LoadLibraryW(wname.c_str());
}
void CloseLib(void *handle)
{ FreeLibrary(static_cast<HMODULE>(handle)); }
void *GetSymbol(void *handle, const char *name)
{
void *ret{reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(handle), name))};
if(!ret) ERR("Failed to load %s\n", name);
return ret;
}
void al_print(const char *type, const char *prefix, const char *func, const char *fmt, ...)
{
al::vector<char> dynmsg;
char stcmsg[256];
char *str{stcmsg};
va_list args, args2;
va_start(args, fmt);
va_copy(args2, args);
int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)};
if(UNLIKELY(msglen >= 0 && static_cast<size_t>(msglen) >= sizeof(stcmsg)))
{
dynmsg.resize(static_cast<size_t>(msglen) + 1u);
str = dynmsg.data();
msglen = std::vsnprintf(str, dynmsg.size(), fmt, args2);
}
va_end(args2);
va_end(args);
std::wstring wstr{utf8_to_wstr(str)};
fprintf(gLogFile, "AL lib: %s %s%s: %ls", type, prefix, func, wstr.c_str());
fflush(gLogFile);
}
static inline int is_slash(int c)
{ return (c == '\\' || c == '/'); }
static void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
{
std::string pathstr{path};
pathstr += "\\*";
pathstr += ext;
TRACE("Searching %s\n", pathstr.c_str());
std::wstring wpath{utf8_to_wstr(pathstr.c_str())};
WIN32_FIND_DATAW fdata;
HANDLE hdl{FindFirstFileW(wpath.c_str(), &fdata)};
if(hdl != INVALID_HANDLE_VALUE)
{
size_t base = results->size();
do {
results->emplace_back();
std::string &str = results->back();
str = path;
str += '\\';
str += wstr_to_utf8(fdata.cFileName);
TRACE("Got result %s\n", str.c_str());
} while(FindNextFileW(hdl, &fdata));
FindClose(hdl);
std::sort(results->begin()+base, results->end());
}
}
al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
{
static std::mutex search_lock;
std::lock_guard<std::mutex> _{search_lock};
/* If the path is absolute, use it directly. */
al::vector<std::string> results;
if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2]))
{
std::string path{subdir};
std::replace(path.begin(), path.end(), '/', '\\');
DirectorySearch(path.c_str(), ext, &results);
return results;
}
if(subdir[0] == '\\' && subdir[1] == '\\' && subdir[2] == '?' && subdir[3] == '\\')
{
DirectorySearch(subdir, ext, &results);
return results;
}
std::string path;
/* Search the app-local directory. */
WCHAR *cwdbuf{_wgetenv(L"ALSOFT_LOCAL_PATH")};
if(cwdbuf && *cwdbuf != '\0')
{
path = wstr_to_utf8(cwdbuf);
if(is_slash(path.back()))
path.pop_back();
}
else if(!(cwdbuf=_wgetcwd(nullptr, 0)))
path = ".";
else
{
path = wstr_to_utf8(cwdbuf);
if(is_slash(path.back()))
path.pop_back();
free(cwdbuf);
}
std::replace(path.begin(), path.end(), '/', '\\');
DirectorySearch(path.c_str(), ext, &results);
/* Search the local and global data dirs. */
static constexpr int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA };
for(int id : ids)
{
WCHAR buffer[MAX_PATH];
if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE)
continue;
path = wstr_to_utf8(buffer);
if(!is_slash(path.back()))
path += '\\';
path += subdir;
std::replace(path.begin(), path.end(), '/', '\\');
DirectorySearch(path.c_str(), ext, &results);
}
return results;
}
void SetRTPriority(void)
{
bool failed = false;
if(RTPrioLevel > 0)
failed = !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
if(failed) ERR("Failed to set priority level for thread\n");
}
#else
const PathNamePair &GetProcBinary()
{
static PathNamePair ret;
if(!ret.fname.empty() || !ret.path.empty())
return ret;
al::vector<char> pathname;
#ifdef __FreeBSD__
size_t pathlen;
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
if(sysctl(mib, 4, nullptr, &pathlen, nullptr, 0) == -1)
WARN("Failed to sysctl kern.proc.pathname: %s\n", strerror(errno));
else
{
pathname.resize(pathlen + 1);
sysctl(mib, 4, pathname.data(), &pathlen, nullptr, 0);
pathname.resize(pathlen);
}
#endif
#ifdef HAVE_PROC_PIDPATH
if(pathname.empty())
{
char procpath[PROC_PIDPATHINFO_MAXSIZE]{};
const pid_t pid{getpid()};
if(proc_pidpath(pid, procpath, sizeof(procpath)) < 1)
ERR("proc_pidpath(%d, ...) failed: %s\n", pid, strerror(errno));
else
pathname.insert(pathname.end(), procpath, procpath+strlen(procpath));
}
#endif
if(pathname.empty())
{
pathname.resize(256);
const char *selfname{"/proc/self/exe"};
ssize_t len{readlink(selfname, pathname.data(), pathname.size())};
if(len == -1 && errno == ENOENT)
{
selfname = "/proc/self/file";
len = readlink(selfname, pathname.data(), pathname.size());
}
if(len == -1 && errno == ENOENT)
{
selfname = "/proc/curproc/exe";
len = readlink(selfname, pathname.data(), pathname.size());
}
if(len == -1 && errno == ENOENT)
{
selfname = "/proc/curproc/file";
len = readlink(selfname, pathname.data(), pathname.size());
}
while(len > 0 && static_cast<size_t>(len) == pathname.size())
{
pathname.resize(pathname.size() << 1);
len = readlink(selfname, pathname.data(), pathname.size());
}
if(len <= 0)
{
WARN("Failed to readlink %s: %s\n", selfname, strerror(errno));
return ret;
}
pathname.resize(len);
}
while(!pathname.empty() && pathname.back() == 0)
pathname.pop_back();
auto sep = std::find(pathname.crbegin(), pathname.crend(), '/');
if(sep != pathname.crend())
{
ret.path = std::string(pathname.cbegin(), sep.base()-1);
ret.fname = std::string(sep.base(), pathname.cend());
}
else
ret.fname = std::string(pathname.cbegin(), pathname.cend());
TRACE("Got: %s, %s\n", ret.path.c_str(), ret.fname.c_str());
return ret;
}
#ifdef HAVE_DLFCN_H
void *LoadLib(const char *name)
{
dlerror();
void *handle{dlopen(name, RTLD_NOW)};
const char *err{dlerror()};
if(err) handle = nullptr;
return handle;
}
void CloseLib(void *handle)
{ dlclose(handle); }
void *GetSymbol(void *handle, const char *name)
{
dlerror();
void *sym{dlsym(handle, name)};
const char *err{dlerror()};
if(err)
{
WARN("Failed to load %s: %s\n", name, err);
sym = nullptr;
}
return sym;
}
#endif /* HAVE_DLFCN_H */
void al_print(const char *type, const char *prefix, const char *func, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(gLogFile, "AL lib: %s %s%s: ", type, prefix, func);
vfprintf(gLogFile, fmt, ap);
va_end(ap);
fflush(gLogFile);
}
static void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
{
TRACE("Searching %s for *%s\n", path, ext);
DIR *dir{opendir(path)};
if(dir != nullptr)
{
const size_t extlen = strlen(ext);
size_t base = results->size();
struct dirent *dirent;
while((dirent=readdir(dir)) != nullptr)
{
if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
continue;
size_t len{strlen(dirent->d_name)};
if(len <= extlen) continue;
if(strcasecmp(dirent->d_name+len-extlen, ext) != 0)
continue;
results->emplace_back();
std::string &str = results->back();
str = path;
if(str.back() != '/')
str.push_back('/');
str += dirent->d_name;
TRACE("Got result %s\n", str.c_str());
}
closedir(dir);
std::sort(results->begin()+base, results->end());
}
}
al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
{
static std::mutex search_lock;
std::lock_guard<std::mutex> _{search_lock};
al::vector<std::string> results;
if(subdir[0] == '/')
{
DirectorySearch(subdir, ext, &results);
return results;
}
/* Search the app-local directory. */
const char *str{getenv("ALSOFT_LOCAL_PATH")};
if(str && *str != '\0')
DirectorySearch(str, ext, &results);
else
{
al::vector<char> cwdbuf(256);
while(!getcwd(cwdbuf.data(), cwdbuf.size()))
{
if(errno != ERANGE)
{
cwdbuf.clear();
break;
}
cwdbuf.resize(cwdbuf.size() << 1);
}
if(cwdbuf.empty())
DirectorySearch(".", ext, &results);
else
{
DirectorySearch(cwdbuf.data(), ext, &results);
cwdbuf.clear();
}
}
// Search local data dir
if((str=getenv("XDG_DATA_HOME")) != nullptr && str[0] != '\0')
{
std::string path{str};
if(path.back() != '/')
path += '/';
path += subdir;
DirectorySearch(path.c_str(), ext, &results);
}
else if((str=getenv("HOME")) != nullptr && str[0] != '\0')
{
std::string path{str};
if(path.back() == '/')
path.pop_back();
path += "/.local/share/";
path += subdir;
DirectorySearch(path.c_str(), ext, &results);
}
// Search global data dirs
if((str=getenv("XDG_DATA_DIRS")) == nullptr || str[0] == '\0')
str = "/usr/local/share/:/usr/share/";
const char *next{str};
while((str=next) != nullptr && str[0] != '\0')
{
next = strchr(str, ':');
std::string path = (next ? std::string(str, next++) : std::string(str));
if(path.empty()) continue;
if(path.back() != '/')
path += '/';
path += subdir;
DirectorySearch(path.c_str(), ext, &results);
}
return results;
}
void SetRTPriority()
{
bool failed = false;
#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
if(RTPrioLevel > 0)
{
struct sched_param param;
/* Use the minimum real-time priority possible for now (on Linux this
* should be 1 for SCHED_RR) */
param.sched_priority = sched_get_priority_min(SCHED_RR);
failed = !!pthread_setschedparam(pthread_self(), SCHED_RR, &param);
}
#else
/* Real-time priority not available */
failed = (RTPrioLevel>0);
#endif
if(failed)
ERR("Failed to set priority level for thread\n");
}
#endif

+ 0
- 1394
modules/openal-soft/Alc/hrtf.cpp
File diff suppressed because it is too large
View File


+ 0
- 121
modules/openal-soft/Alc/hrtf.h View File

@ -1,121 +0,0 @@
#ifndef ALC_HRTF_H
#define ALC_HRTF_H
#include <array>
#include <memory>
#include <string>
#include "AL/al.h"
#include "AL/alc.h"
#include "vector.h"
#include "almalloc.h"
#define HRTF_HISTORY_BITS (6)
#define HRTF_HISTORY_LENGTH (1<<HRTF_HISTORY_BITS)
#define HRTF_HISTORY_MASK (HRTF_HISTORY_LENGTH-1)
#define HRIR_BITS (7)
#define HRIR_LENGTH (1<<HRIR_BITS)
#define HRIR_MASK (HRIR_LENGTH-1)
struct HrtfHandle;
struct HrtfEntry {
RefCount ref;
ALuint sampleRate;
ALsizei irSize;
struct Field {
ALfloat distance;
ALubyte evCount;
};
/* NOTE: Fields are stored *backwards*. field[0] is the farthest field, and
* field[fdCount-1] is the nearest.
*/
ALsizei fdCount;
const Field *field;
struct Elevation {
ALushort azCount;
ALushort irOffset;
};
Elevation *elev;
const ALfloat (*coeffs)[2];
const ALubyte (*delays)[2];
void IncRef();
void DecRef();
static constexpr inline const char *CurrentPrefix() noexcept { return "HrtfEntry::"; }
DEF_PLACE_NEWDEL()
};
struct EnumeratedHrtf {
std::string name;
HrtfHandle *hrtf;
};
using float2 = std::array<float,2>;
template<typename T>
using HrirArray = std::array<std::array<T,2>,HRIR_LENGTH>;
struct HrtfState {
alignas(16) std::array<ALfloat,HRTF_HISTORY_LENGTH> History;
alignas(16) HrirArray<ALfloat> Values;
};
struct HrtfParams {
alignas(16) HrirArray<ALfloat> Coeffs;
ALsizei Delay[2];
ALfloat Gain;
};
struct DirectHrtfState {
/* HRTF filter state for dry buffer content */
ALsizei IrSize{0};
struct ChanData {
alignas(16) HrirArray<ALfloat> Values;
alignas(16) HrirArray<ALfloat> Coeffs;
};
al::FlexArray<ChanData> Chan;
DirectHrtfState(size_t numchans) : Chan{numchans} { }
DirectHrtfState(const DirectHrtfState&) = delete;
DirectHrtfState& operator=(const DirectHrtfState&) = delete;
static std::unique_ptr<DirectHrtfState> Create(size_t num_chans);
static constexpr size_t Sizeof(size_t numchans) noexcept
{ return al::FlexArray<ChanData>::Sizeof(numchans, offsetof(DirectHrtfState, Chan)); }
DEF_PLACE_NEWDEL()
};
struct AngularPoint {
ALfloat Elev;
ALfloat Azim;
};
al::vector<EnumeratedHrtf> EnumerateHrtf(const char *devname);
HrtfEntry *GetLoadedHrtf(HrtfHandle *handle);
void GetHrtfCoeffs(const HrtfEntry *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat distance,
ALfloat spread, HrirArray<ALfloat> &coeffs, ALsizei (&delays)[2]);
/**
* Produces HRTF filter coefficients for decoding B-Format, given a set of
* virtual speaker positions, a matching decoding matrix, and per-order high-
* frequency gains for the decoder. The calculated impulse responses are
* ordered and scaled according to the matrix input. Note the specified virtual
* positions should be in degrees, not radians!
*/
void BuildBFormatHrtf(const HrtfEntry *Hrtf, DirectHrtfState *state, const ALsizei NumChannels, const AngularPoint *AmbiPoints, const ALfloat (*RESTRICT AmbiMatrix)[MAX_AMBI_CHANNELS], const size_t AmbiCount, const ALfloat *RESTRICT AmbiOrderHFGain);
#endif /* ALC_HRTF_H */

+ 30
- 49
modules/openal-soft/Alc/inprogext.h View File

@ -9,25 +9,6 @@
extern "C" {
#endif
#ifndef ALC_SOFT_loopback_bformat
#define ALC_SOFT_loopback_bformat 1
#define ALC_AMBISONIC_LAYOUT_SOFT 0x1997
#define ALC_AMBISONIC_SCALING_SOFT 0x1998
#define ALC_AMBISONIC_ORDER_SOFT 0x1999
#define ALC_MAX_AMBISONIC_ORDER_SOFT 0x199B
#define ALC_BFORMAT3D_SOFT 0x1508
/* Ambisonic layouts */
#define ALC_FUMA_SOFT 0x0000
#define ALC_ACN_SOFT 0x0001
/* Ambisonic scalings (normalization) */
/*#define ALC_FUMA_SOFT*/
#define ALC_SN3D_SOFT 0x0001
#define ALC_N3D_SOFT 0x0002
#endif
#ifndef AL_SOFT_map_buffer
#define AL_SOFT_map_buffer 1
typedef unsigned int ALbitfieldSOFT;
@ -47,44 +28,44 @@ AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, A
#endif
#endif
#ifndef AL_SOFT_events
#define AL_SOFT_events 1
#define AL_EVENT_CALLBACK_FUNCTION_SOFT 0x1220
#define AL_EVENT_CALLBACK_USER_PARAM_SOFT 0x1221
#define AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT 0x1222
#define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x1223
#define AL_EVENT_TYPE_ERROR_SOFT 0x1224
#define AL_EVENT_TYPE_PERFORMANCE_SOFT 0x1225
#define AL_EVENT_TYPE_DEPRECATED_SOFT 0x1226
#define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x1227
typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param,
ALsizei length, const ALchar *message,
void *userParam);
typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable);
typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam);
typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname);
typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values);
#ifdef AL_ALEXT_PROTOTYPES
AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable);
AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam);
AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname);
AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values);
#endif
#ifndef AL_SOFT_bformat_hoa
#define AL_SOFT_bformat_hoa
#define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D
#endif
#ifndef AL_SOFT_buffer_layers
#define AL_SOFT_buffer_layers
typedef void (AL_APIENTRY*LPALSOURCEQUEUEBUFFERLAYERSSOFT)(ALuint src, ALsizei nb, const ALuint *buffers);
#ifndef AL_SOFT_convolution_reverb
#define AL_SOFT_convolution_reverb
#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000
#define AL_EFFECTSLOT_STATE_SOFT 0x199D
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYSOFT)(ALuint slotid);
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYVSOFT)(ALsizei n, const ALuint *slotids);
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPSOFT)(ALuint slotid);
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPVSOFT)(ALsizei n, const ALuint *slotids);
#ifdef AL_ALEXT_PROTOTYPES
AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, const ALuint *buffers);
AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid);
AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids);
AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid);
AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids);
#endif
#endif
#ifndef AL_SOFT_effect_chain
#define AL_SOFT_effect_chain
#define AL_EFFECTSLOT_TARGET_SOFT 0xf000
#ifndef AL_SOFT_hold_on_disconnect
#define AL_SOFT_hold_on_disconnect
#define AL_STOP_SOURCES_ON_DISCONNECT_SOFT 0x19AB
#endif
/* Non-standard export. Not part of any extension. */
AL_API const ALchar* AL_APIENTRY alsoft_get_version(void);
/* Functions from abandoned extenions. Only here for binary compatibility. */
AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb,
const ALuint *buffers);
AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname);
AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values);
#ifdef __cplusplus
} /* extern "C" */
#endif

+ 0
- 65
modules/openal-soft/Alc/logging.h View File

@ -1,65 +0,0 @@
#ifndef LOGGING_H
#define LOGGING_H
#include <stdio.h>
#include "opthelpers.h"
#ifdef __GNUC__
#define DECL_FORMAT(x, y, z) __attribute__((format(x, (y), (z))))
#else
#define DECL_FORMAT(x, y, z)
#endif
extern FILE *gLogFile;
constexpr inline const char *CurrentPrefix() noexcept { return ""; }
#if defined(__GNUC__) && !defined(_WIN32)
#define AL_PRINT(T, MSG, ...) fprintf(gLogFile, "AL lib: %s %s%s: " MSG, T, CurrentPrefix(), __FUNCTION__ , ## __VA_ARGS__)
#else
void al_print(const char *type, const char *prefix, const char *func, const char *fmt, ...) DECL_FORMAT(printf, 4,5);
#define AL_PRINT(T, ...) al_print((T), CurrentPrefix(), __FUNCTION__, __VA_ARGS__)
#endif
#ifdef __ANDROID__
#include <android/log.h>
#define LOG_ANDROID(T, MSG, ...) __android_log_print(T, "openal", "AL lib: %s%s: " MSG, CurrentPrefix(), __FUNCTION__ , ## __VA_ARGS__)
#else
#define LOG_ANDROID(T, MSG, ...) ((void)0)
#endif
enum LogLevel {
NoLog,
LogError,
LogWarning,
LogTrace,
LogRef
};
extern LogLevel gLogLevel;
#define TRACEREF(...) do { \
if(UNLIKELY(gLogLevel >= LogRef)) \
AL_PRINT("(--)", __VA_ARGS__); \
} while(0)
#define TRACE(...) do { \
if(UNLIKELY(gLogLevel >= LogTrace)) \
AL_PRINT("(II)", __VA_ARGS__); \
LOG_ANDROID(ANDROID_LOG_DEBUG, __VA_ARGS__); \
} while(0)
#define WARN(...) do { \
if(UNLIKELY(gLogLevel >= LogWarning)) \
AL_PRINT("(WW)", __VA_ARGS__); \
LOG_ANDROID(ANDROID_LOG_WARN, __VA_ARGS__); \
} while(0)
#define ERR(...) do { \
if(UNLIKELY(gLogLevel >= LogError)) \
AL_PRINT("(EE)", __VA_ARGS__); \
LOG_ANDROID(ANDROID_LOG_ERROR, __VA_ARGS__); \
} while(0)
#endif /* LOGGING_H */

+ 0
- 107
modules/openal-soft/Alc/mastering.h View File

@ -1,107 +0,0 @@
#ifndef MASTERING_H
#define MASTERING_H
#include <memory>
#include "AL/al.h"
#include "almalloc.h"
/* For BUFFERSIZE. */
#include "alMain.h"
struct SlidingHold;
/* General topology and basic automation was based on the following paper:
*
* D. Giannoulis, M. Massberg and J. D. Reiss,
* "Parameter Automation in a Dynamic Range Compressor,"
* Journal of the Audio Engineering Society, v61 (10), Oct. 2013
*
* Available (along with supplemental reading) at:
*
* http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/
*/
struct Compressor {
ALsizei mNumChans{0};
ALuint mSampleRate{0u};
struct {
bool Knee : 1;
bool Attack : 1;
bool Release : 1;
bool PostGain : 1;
bool Declip : 1;
} mAuto{};
ALsizei mLookAhead{0};
ALfloat mPreGain{0.0f};
ALfloat mPostGain{0.0f};
ALfloat mThreshold{0.0f};
ALfloat mSlope{0.0f};
ALfloat mKnee{0.0f};
ALfloat mAttack{0.0f};
ALfloat mRelease{0.0f};
alignas(16) ALfloat mSideChain[2*BUFFERSIZE]{};
alignas(16) ALfloat mCrestFactor[BUFFERSIZE]{};
SlidingHold *mHold{nullptr};
ALfloat (*mDelay)[BUFFERSIZE]{nullptr};
ALsizei mDelayIndex{0};
ALfloat mCrestCoeff{0.0f};
ALfloat mGainEstimate{0.0f};
ALfloat mAdaptCoeff{0.0f};
ALfloat mLastPeakSq{0.0f};
ALfloat mLastRmsSq{0.0f};
ALfloat mLastRelease{0.0f};
ALfloat mLastAttack{0.0f};
ALfloat mLastGainDev{0.0f};
~Compressor();
void process(const ALsizei SamplesToDo, ALfloat (*OutBuffer)[BUFFERSIZE]);
ALsizei getLookAhead() const noexcept { return mLookAhead; }
DEF_PLACE_NEWDEL()
};
/* The compressor is initialized with the following settings:
*
* NumChans - Number of channels to process.
* SampleRate - Sample rate to process.
* AutoKnee - Whether to automate the knee width parameter.
* AutoAttack - Whether to automate the attack time parameter.
* AutoRelease - Whether to automate the release time parameter.
* AutoPostGain - Whether to automate the make-up (post) gain parameter.
* AutoDeclip - Whether to automate clipping reduction. Ignored when
* not automating make-up gain.
* LookAheadTime - Look-ahead time (in seconds).
* HoldTime - Peak hold-time (in seconds).
* PreGainDb - Gain applied before detection (in dB).
* PostGainDb - Make-up gain applied after compression (in dB).
* ThresholdDb - Triggering threshold (in dB).
* Ratio - Compression ratio (x:1). Set to INFINIFTY for true
* limiting. Ignored when automating knee width.
* KneeDb - Knee width (in dB). Ignored when automating knee
* width.
* AttackTimeMin - Attack time (in seconds). Acts as a maximum when
* automating attack time.
* ReleaseTimeMin - Release time (in seconds). Acts as a maximum when
* automating release time.
*/
std::unique_ptr<Compressor> CompressorInit(const ALsizei NumChans, const ALuint SampleRate,
const ALboolean AutoKnee, const ALboolean AutoAttack,
const ALboolean AutoRelease, const ALboolean AutoPostGain,
const ALboolean AutoDeclip, const ALfloat LookAheadTime,
const ALfloat HoldTime, const ALfloat PreGainDb,
const ALfloat PostGainDb, const ALfloat ThresholdDb,
const ALfloat Ratio, const ALfloat KneeDb,
const ALfloat AttackTime, const ALfloat ReleaseTime);
#endif /* MASTERING_H */

+ 0
- 57
modules/openal-soft/Alc/mixer/defs.h View File

@ -1,57 +0,0 @@
#ifndef MIXER_DEFS_H
#define MIXER_DEFS_H
#include "AL/alc.h"
#include "AL/al.h"
#include "alMain.h"
#include "alu.h"
struct MixGains;
struct MixHrtfParams;
struct HrtfState;
struct DirectHrtfState;
struct CTag { };
struct SSETag { };
struct SSE2Tag { };
struct SSE3Tag { };
struct SSE4Tag { };
struct NEONTag { };
struct CopyTag { };
struct PointTag { };
struct LerpTag { };
struct CubicTag { };
struct BSincTag { };
template<typename TypeTag, typename InstTag>
const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen);
template<typename InstTag>
void Mix_(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE], ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, const ALsizei BufferSize);
template<typename InstTag>
void MixRow_(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE], const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize);
template<typename InstTag>
void MixHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, MixHrtfParams *hrtfparams, const ALsizei BufferSize);
template<typename InstTag>
void MixHrtfBlend_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize, const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize);
template<typename InstTag>
void MixDirectHrtf_(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State, const ALsizei NumChans, const ALsizei BufferSize);
/* Vectorized resampler helpers */
inline void InitiatePositionArrays(ALsizei frac, ALint increment, ALsizei *RESTRICT frac_arr, ALsizei *RESTRICT pos_arr, ALsizei size)
{
pos_arr[0] = 0;
frac_arr[0] = frac;
for(ALsizei i{1};i < size;i++)
{
ALint frac_tmp = frac_arr[i-1] + increment;
pos_arr[i] = pos_arr[i-1] + (frac_tmp>>FRACTIONBITS);
frac_arr[i] = frac_tmp&FRACTIONMASK;
}
}
#endif /* MIXER_DEFS_H */

+ 0
- 132
modules/openal-soft/Alc/mixer/hrtfbase.h View File

@ -1,132 +0,0 @@
#ifndef MIXER_HRTFBASE_H
#define MIXER_HRTFBASE_H
#include <algorithm>
#include "alu.h"
#include "../hrtf.h"
#include "opthelpers.h"
using ApplyCoeffsT = void(ALsizei Offset, float2 *RESTRICT Values, const ALsizei irSize,
const HrirArray<ALfloat> &Coeffs, const ALfloat left, const ALfloat right);
template<ApplyCoeffsT &ApplyCoeffs>
inline void MixHrtfBase(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data,
float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize,
MixHrtfParams *hrtfparams, const ALsizei BufferSize)
{
ASSUME(OutPos >= 0);
ASSUME(IrSize >= 4);
ASSUME(BufferSize > 0);
const auto &Coeffs = *hrtfparams->Coeffs;
const ALfloat gainstep{hrtfparams->GainStep};
const ALfloat gain{hrtfparams->Gain};
ALfloat stepcount{0.0f};
ALsizei Delay[2]{
HRTF_HISTORY_LENGTH - hrtfparams->Delay[0],
HRTF_HISTORY_LENGTH - hrtfparams->Delay[1] };
ASSUME(Delay[0] >= 0 && Delay[1] >= 0);
for(ALsizei i{0};i < BufferSize;++i)
{
const ALfloat g{gain + gainstep*stepcount};
const ALfloat left{data[Delay[0]++] * g};
const ALfloat right{data[Delay[1]++] * g};
ApplyCoeffs(i, AccumSamples+i, IrSize, Coeffs, left, right);
stepcount += 1.0f;
}
for(ALsizei i{0};i < BufferSize;++i)
LeftOut[OutPos+i] += AccumSamples[i][0];
for(ALsizei i{0};i < BufferSize;++i)
RightOut[OutPos+i] += AccumSamples[i][1];
hrtfparams->Gain = gain + gainstep*stepcount;
}
template<ApplyCoeffsT &ApplyCoeffs>
inline void MixHrtfBlendBase(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut,
const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize,
const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize)
{
const auto &OldCoeffs = oldparams->Coeffs;
const ALfloat oldGain{oldparams->Gain};
const ALfloat oldGainStep{-oldGain / static_cast<ALfloat>(BufferSize)};
const auto &NewCoeffs = *newparams->Coeffs;
const ALfloat newGainStep{newparams->GainStep};
ALfloat stepcount{0.0f};
ASSUME(OutPos >= 0);
ASSUME(IrSize >= 4);
ASSUME(BufferSize > 0);
ALsizei OldDelay[2]{
HRTF_HISTORY_LENGTH - oldparams->Delay[0],
HRTF_HISTORY_LENGTH - oldparams->Delay[1] };
ASSUME(OldDelay[0] >= 0 && OldDelay[1] >= 0);
ALsizei NewDelay[2]{
HRTF_HISTORY_LENGTH - newparams->Delay[0],
HRTF_HISTORY_LENGTH - newparams->Delay[1] };
ASSUME(NewDelay[0] >= 0 && NewDelay[1] >= 0);
for(ALsizei i{0};i < BufferSize;++i)
{
ALfloat g{oldGain + oldGainStep*stepcount};
ALfloat left{data[OldDelay[0]++] * g};
ALfloat right{data[OldDelay[1]++] * g};
ApplyCoeffs(i, AccumSamples+i, IrSize, OldCoeffs, left, right);
g = newGainStep*stepcount;
left = data[NewDelay[0]++] * g;
right = data[NewDelay[1]++] * g;
ApplyCoeffs(i, AccumSamples+i, IrSize, NewCoeffs, left, right);
stepcount += 1.0f;
}
for(ALsizei i{0};i < BufferSize;++i)
LeftOut[OutPos+i] += AccumSamples[i][0];
for(ALsizei i{0};i < BufferSize;++i)
RightOut[OutPos+i] += AccumSamples[i][1];
newparams->Gain = newGainStep*stepcount;
}
template<ApplyCoeffsT &ApplyCoeffs>
inline void MixDirectHrtfBase(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut,
const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State,
const ALsizei NumChans, const ALsizei BufferSize)
{
ASSUME(NumChans > 0);
ASSUME(BufferSize > 0);
const ALsizei IrSize{State->IrSize};
ASSUME(IrSize >= 4);
for(ALsizei c{0};c < NumChans;++c)
{
const ALfloat (&input)[BUFFERSIZE] = data[c];
const auto &Coeffs = State->Chan[c].Coeffs;
auto accum_iter = std::copy_n(State->Chan[c].Values.begin(),
State->Chan[c].Values.size(), AccumSamples);
std::fill_n(accum_iter, BufferSize, float2{});
for(ALsizei i{0};i < BufferSize;++i)
{
const ALfloat insample{input[i]};
ApplyCoeffs(i, AccumSamples+i, IrSize, Coeffs, insample, insample);
}
for(ALsizei i{0};i < BufferSize;++i)
LeftOut[i] += AccumSamples[i][0];
for(ALsizei i{0};i < BufferSize;++i)
RightOut[i] += AccumSamples[i][1];
std::copy_n(AccumSamples + BufferSize, State->Chan[c].Values.size(),
State->Chan[c].Values.begin());
}
}
#endif /* MIXER_HRTFBASE_H */

+ 0
- 208
modules/openal-soft/Alc/mixer/mixer_c.cpp View File

@ -1,208 +0,0 @@
#include "config.h"
#include <cassert>
#include <limits>
#include "alMain.h"
#include "alu.h"
#include "alSource.h"
#include "alAuxEffectSlot.h"
#include "defs.h"
#include "hrtfbase.h"
static inline ALfloat do_point(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei) noexcept
{ return vals[0]; }
static inline ALfloat do_lerp(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei frac) noexcept
{ return lerp(vals[0], vals[1], frac * (1.0f/FRACTIONONE)); }
static inline ALfloat do_cubic(const InterpState&, const ALfloat *RESTRICT vals, const ALsizei frac) noexcept
{ return cubic(vals[0], vals[1], vals[2], vals[3], frac * (1.0f/FRACTIONONE)); }
static inline ALfloat do_bsinc(const InterpState &istate, const ALfloat *RESTRICT vals, const ALsizei frac) noexcept
{
ASSUME(istate.bsinc.m > 0);
// Calculate the phase index and factor.
#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
const ALsizei pi{frac >> FRAC_PHASE_BITDIFF};
const ALfloat pf{(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * (1.0f/(1<<FRAC_PHASE_BITDIFF))};
#undef FRAC_PHASE_BITDIFF
const ALfloat *fil{istate.bsinc.filter + istate.bsinc.m*pi*4};
const ALfloat *scd{fil + istate.bsinc.m};
const ALfloat *phd{scd + istate.bsinc.m};
const ALfloat *spd{phd + istate.bsinc.m};
// Apply the scale and phase interpolated filter.
ALfloat r{0.0f};
for(ALsizei j_f{0};j_f < istate.bsinc.m;j_f++)
r += (fil[j_f] + istate.bsinc.sf*scd[j_f] + pf*(phd[j_f] + istate.bsinc.sf*spd[j_f])) * vals[j_f];
return r;
}
using SamplerT = ALfloat(const InterpState&, const ALfloat*RESTRICT, const ALsizei);
template<SamplerT &Sampler>
static const ALfloat *DoResample(const InterpState *state, const ALfloat *RESTRICT src,
ALsizei frac, ALint increment, ALfloat *RESTRICT dst,
ALsizei numsamples)
{
ASSUME(numsamples > 0);
ASSUME(increment > 0);
ASSUME(frac >= 0);
const InterpState istate{*state};
auto proc_sample = [&src,&frac,istate,increment]() -> ALfloat
{
const ALfloat ret{Sampler(istate, src, frac)};
frac += increment;
src += frac>>FRACTIONBITS;
frac &= FRACTIONMASK;
return ret;
};
std::generate_n<ALfloat*RESTRICT>(dst, numsamples, proc_sample);
return dst;
}
template<>
const ALfloat *Resample_<CopyTag,CTag>(const InterpState* UNUSED(state),
const ALfloat *RESTRICT src, ALsizei UNUSED(frac), ALint UNUSED(increment),
ALfloat *RESTRICT dst, ALsizei dstlen)
{
ASSUME(dstlen > 0);
#if defined(HAVE_SSE) || defined(HAVE_NEON)
/* Avoid copying the source data if it's aligned like the destination. */
if((reinterpret_cast<intptr_t>(src)&15) == (reinterpret_cast<intptr_t>(dst)&15))
return src;
#endif
std::copy_n(src, dstlen, dst);
return dst;
}
template<>
const ALfloat *Resample_<PointTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src,
ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen)
{ return DoResample<do_point>(state, src, frac, increment, dst, dstlen); }
template<>
const ALfloat *Resample_<LerpTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src,
ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen)
{ return DoResample<do_lerp>(state, src, frac, increment, dst, dstlen); }
template<>
const ALfloat *Resample_<CubicTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src,
ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen)
{ return DoResample<do_cubic>(state, src-1, frac, increment, dst, dstlen); }
template<>
const ALfloat *Resample_<BSincTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src,
ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen)
{ return DoResample<do_bsinc>(state, src-state->bsinc.l, frac, increment, dst, dstlen); }
static inline void ApplyCoeffs(ALsizei /*Offset*/, float2 *RESTRICT Values, const ALsizei IrSize,
const HrirArray<ALfloat> &Coeffs, const ALfloat left, const ALfloat right)
{
ASSUME(IrSize >= 2);
for(ALsizei c{0};c < IrSize;++c)
{
Values[c][0] += Coeffs[c][0] * left;
Values[c][1] += Coeffs[c][1] * right;
}
}
template<>
void MixHrtf_<CTag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data,
float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize,
MixHrtfParams *hrtfparams, const ALsizei BufferSize)
{
MixHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, hrtfparams,
BufferSize);
}
template<>
void MixHrtfBlend_<CTag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut,
const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize,
const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize)
{
MixHrtfBlendBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, oldparams,
newparams, BufferSize);
}
template<>
void MixDirectHrtf_<CTag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut,
const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State,
const ALsizei NumChans, const ALsizei BufferSize)
{
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, State, NumChans,
BufferSize);
}
template<>
void Mix_<CTag>(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE],
ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos,
const ALsizei BufferSize)
{
ASSUME(OutChans > 0);
ASSUME(BufferSize > 0);
const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f};
for(ALsizei c{0};c < OutChans;c++)
{
ALfloat *RESTRICT dst{&OutBuffer[c][OutPos]};
ALsizei pos{0};
ALfloat gain{CurrentGains[c]};
const ALfloat diff{TargetGains[c] - gain};
if(std::fabs(diff) > std::numeric_limits<float>::epsilon())
{
ALsizei minsize{mini(BufferSize, Counter)};
const ALfloat step{diff * delta};
ALfloat step_count{0.0f};
for(;pos < minsize;pos++)
{
dst[pos] += data[pos] * (gain + step*step_count);
step_count += 1.0f;
}
if(pos == Counter)
gain = TargetGains[c];
else
gain += step*step_count;
CurrentGains[c] = gain;
}
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
continue;
for(;pos < BufferSize;pos++)
dst[pos] += data[pos]*gain;
}
}
/* Basically the inverse of the above. Rather than one input going to multiple
* outputs (each with its own gain), it's multiple inputs (each with its own
* gain) going to one output. This applies one row (vs one column) of a matrix
* transform. And as the matrices are more or less static once set up, no
* stepping is necessary.
*/
template<>
void MixRow_<CTag>(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE],
const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize)
{
ASSUME(InChans > 0);
ASSUME(BufferSize > 0);
for(ALsizei c{0};c < InChans;c++)
{
const ALfloat *RESTRICT src{&data[c][InPos]};
const ALfloat gain{Gains[c]};
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
continue;
for(ALsizei i{0};i < BufferSize;i++)
OutBuffer[i] += src[i] * gain;
}
}

+ 0
- 309
modules/openal-soft/Alc/mixer/mixer_neon.cpp View File

@ -1,309 +0,0 @@
#include "config.h"
#include <arm_neon.h>
#include <limits>
#include "AL/al.h"
#include "AL/alc.h"
#include "alMain.h"
#include "alu.h"
#include "hrtf.h"
#include "defs.h"
#include "hrtfbase.h"
template<>
const ALfloat *Resample_<LerpTag,NEONTag>(const InterpState* UNUSED(state),
const ALfloat *RESTRICT src, ALsizei frac, ALint increment,
ALfloat *RESTRICT dst, ALsizei dstlen)
{
const int32x4_t increment4 = vdupq_n_s32(increment*4);
const float32x4_t fracOne4 = vdupq_n_f32(1.0f/FRACTIONONE);
const int32x4_t fracMask4 = vdupq_n_s32(FRACTIONMASK);
alignas(16) ALsizei pos_[4], frac_[4];
int32x4_t pos4, frac4;
ALsizei todo, pos, i;
ASSUME(frac >= 0);
ASSUME(increment > 0);
ASSUME(dstlen > 0);
InitiatePositionArrays(frac, increment, frac_, pos_, 4);
frac4 = vld1q_s32(frac_);
pos4 = vld1q_s32(pos_);
todo = dstlen & ~3;
for(i = 0;i < todo;i += 4)
{
const int pos0 = vgetq_lane_s32(pos4, 0);
const int pos1 = vgetq_lane_s32(pos4, 1);
const int pos2 = vgetq_lane_s32(pos4, 2);
const int pos3 = vgetq_lane_s32(pos4, 3);
const float32x4_t val1 = (float32x4_t){src[pos0], src[pos1], src[pos2], src[pos3]};
const float32x4_t val2 = (float32x4_t){src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1]};
/* val1 + (val2-val1)*mu */
const float32x4_t r0 = vsubq_f32(val2, val1);
const float32x4_t mu = vmulq_f32(vcvtq_f32_s32(frac4), fracOne4);
const float32x4_t out = vmlaq_f32(val1, mu, r0);
vst1q_f32(&dst[i], out);
frac4 = vaddq_s32(frac4, increment4);
pos4 = vaddq_s32(pos4, vshrq_n_s32(frac4, FRACTIONBITS));
frac4 = vandq_s32(frac4, fracMask4);
}
/* NOTE: These four elements represent the position *after* the last four
* samples, so the lowest element is the next position to resample.
*/
pos = vgetq_lane_s32(pos4, 0);
frac = vgetq_lane_s32(frac4, 0);
for(;i < dstlen;++i)
{
dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE));
frac += increment;
pos += frac>>FRACTIONBITS;
frac &= FRACTIONMASK;
}
return dst;
}
template<>
const ALfloat *Resample_<BSincTag,NEONTag>(const InterpState *state, const ALfloat *RESTRICT src,
ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen)
{
const ALfloat *const filter = state->bsinc.filter;
const float32x4_t sf4 = vdupq_n_f32(state->bsinc.sf);
const ALsizei m = state->bsinc.m;
const float32x4_t *fil, *scd, *phd, *spd;
ALsizei pi, i, j, offset;
float32x4_t r4;
ALfloat pf;
ASSUME(m > 0);
ASSUME(dstlen > 0);
ASSUME(increment > 0);
ASSUME(frac >= 0);
src -= state->bsinc.l;
for(i = 0;i < dstlen;i++)
{
// Calculate the phase index and factor.
#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
pi = frac >> FRAC_PHASE_BITDIFF;
pf = (frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * (1.0f/(1<<FRAC_PHASE_BITDIFF));
#undef FRAC_PHASE_BITDIFF
offset = m*pi*4;
fil = (const float32x4_t*)(filter + offset); offset += m;
scd = (const float32x4_t*)(filter + offset); offset += m;
phd = (const float32x4_t*)(filter + offset); offset += m;
spd = (const float32x4_t*)(filter + offset);
// Apply the scale and phase interpolated filter.
r4 = vdupq_n_f32(0.0f);
{
const ALsizei count = m >> 2;
const float32x4_t pf4 = vdupq_n_f32(pf);
ASSUME(count > 0);
for(j = 0;j < count;j++)
{
/* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */
const float32x4_t f4 = vmlaq_f32(
vmlaq_f32(fil[j], sf4, scd[j]),
pf4, vmlaq_f32(phd[j], sf4, spd[j])
);
/* r += f*src */
r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j*4]));
}
}
r4 = vaddq_f32(r4, vcombine_f32(vrev64_f32(vget_high_f32(r4)),
vrev64_f32(vget_low_f32(r4))));
dst[i] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
frac += increment;
src += frac>>FRACTIONBITS;
frac &= FRACTIONMASK;
}
return dst;
}
static inline void ApplyCoeffs(ALsizei /*Offset*/, float2 *RESTRICT Values, const ALsizei IrSize,
const HrirArray<ALfloat> &Coeffs, const ALfloat left, const ALfloat right)
{
ASSUME(IrSize >= 2);
float32x4_t leftright4;
{
float32x2_t leftright2 = vdup_n_f32(0.0);
leftright2 = vset_lane_f32(left, leftright2, 0);
leftright2 = vset_lane_f32(right, leftright2, 1);
leftright4 = vcombine_f32(leftright2, leftright2);
}
for(ALsizei c{0};c < IrSize;c += 2)
{
float32x4_t vals = vcombine_f32(vld1_f32((float32_t*)&Values[c ][0]),
vld1_f32((float32_t*)&Values[c+1][0]));
float32x4_t coefs = vld1q_f32((float32_t*)&Coeffs[c][0]);
vals = vmlaq_f32(vals, coefs, leftright4);
vst1_f32((float32_t*)&Values[c ][0], vget_low_f32(vals));
vst1_f32((float32_t*)&Values[c+1][0], vget_high_f32(vals));
}
}
template<>
void MixHrtf_<NEONTag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data,
float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize,
MixHrtfParams *hrtfparams, const ALsizei BufferSize)
{
MixHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, hrtfparams,
BufferSize);
}
template<>
void MixHrtfBlend_<NEONTag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut,
const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize,
const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize)
{
MixHrtfBlendBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, oldparams,
newparams, BufferSize);
}
template<>
void MixDirectHrtf_<NEONTag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut,
const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State,
const ALsizei NumChans, const ALsizei BufferSize)
{
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, State, NumChans,
BufferSize);
}
template<>
void Mix_<NEONTag>(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE],
ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos,
const ALsizei BufferSize)
{
ASSUME(OutChans > 0);
ASSUME(BufferSize > 0);
const ALfloat delta{(Counter > 0) ? 1.0f/(ALfloat)Counter : 0.0f};
for(ALsizei c{0};c < OutChans;c++)
{
ALfloat *RESTRICT dst{al::assume_aligned<16>(&OutBuffer[c][OutPos])};
ALsizei pos{0};
ALfloat gain{CurrentGains[c]};
const ALfloat diff{TargetGains[c] - gain};
if(std::fabs(diff) > std::numeric_limits<float>::epsilon())
{
ALsizei minsize{mini(BufferSize, Counter)};
const ALfloat step{diff * delta};
ALfloat step_count{0.0f};
/* Mix with applying gain steps in aligned multiples of 4. */
if(LIKELY(minsize > 3))
{
const float32x4_t four4{vdupq_n_f32(4.0f)};
const float32x4_t step4{vdupq_n_f32(step)};
const float32x4_t gain4{vdupq_n_f32(gain)};
float32x4_t step_count4{vsetq_lane_f32(0.0f,
vsetq_lane_f32(1.0f,
vsetq_lane_f32(2.0f,
vsetq_lane_f32(3.0f, vdupq_n_f32(0.0f), 3),
2), 1), 0
)};
ALsizei todo{minsize >> 2};
do {
const float32x4_t val4 = vld1q_f32(&data[pos]);
float32x4_t dry4 = vld1q_f32(&dst[pos]);
dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4));
step_count4 = vaddq_f32(step_count4, four4);
vst1q_f32(&dst[pos], dry4);
pos += 4;
} while(--todo);
/* NOTE: step_count4 now represents the next four counts after
* the last four mixed samples, so the lowest element
* represents the next step count to apply.
*/
step_count = vgetq_lane_f32(step_count4, 0);
}
/* Mix with applying left over gain steps that aren't aligned multiples of 4. */
for(;pos < minsize;pos++)
{
dst[pos] += data[pos]*(gain + step*step_count);
step_count += 1.0f;
}
if(pos == Counter)
gain = TargetGains[c];
else
gain += step*step_count;
CurrentGains[c] = gain;
/* Mix until pos is aligned with 4 or the mix is done. */
minsize = mini(BufferSize, (pos+3)&~3);
for(;pos < minsize;pos++)
dst[pos] += data[pos]*gain;
}
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
continue;
if(LIKELY(BufferSize-pos > 3))
{
ALsizei todo{(BufferSize-pos) >> 2};
const float32x4_t gain4 = vdupq_n_f32(gain);
do {
const float32x4_t val4 = vld1q_f32(&data[pos]);
float32x4_t dry4 = vld1q_f32(&dst[pos]);
dry4 = vmlaq_f32(dry4, val4, gain4);
vst1q_f32(&dst[pos], dry4);
pos += 4;
} while(--todo);
}
for(;pos < BufferSize;pos++)
dst[pos] += data[pos]*gain;
}
}
template<>
void MixRow_<NEONTag>(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE],
const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize)
{
ASSUME(InChans > 0);
ASSUME(BufferSize > 0);
for(ALsizei c{0};c < InChans;c++)
{
const ALfloat *RESTRICT src{al::assume_aligned<16>(&data[c][InPos])};
ALsizei pos{0};
const ALfloat gain{Gains[c]};
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
continue;
if(LIKELY(BufferSize > 3))
{
ALsizei todo{BufferSize >> 2};
float32x4_t gain4{vdupq_n_f32(gain)};
do {
const float32x4_t val4 = vld1q_f32(&src[pos]);
float32x4_t dry4 = vld1q_f32(&OutBuffer[pos]);
dry4 = vmlaq_f32(dry4, val4, gain4);
vst1q_f32(&OutBuffer[pos], dry4);
pos += 4;
} while(--todo);
}
for(;pos < BufferSize;pos++)
OutBuffer[pos] += src[pos]*gain;
}
}

+ 0
- 263
modules/openal-soft/Alc/mixer/mixer_sse.cpp View File

@ -1,263 +0,0 @@
#include "config.h"
#include <xmmintrin.h>
#include <limits>
#include "AL/al.h"
#include "AL/alc.h"
#include "alMain.h"
#include "alu.h"
#include "alSource.h"
#include "alAuxEffectSlot.h"
#include "defs.h"
#include "hrtfbase.h"
template<>
const ALfloat *Resample_<BSincTag,SSETag>(const InterpState *state, const ALfloat *RESTRICT src,
ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen)
{
const ALfloat *const filter{state->bsinc.filter};
const __m128 sf4{_mm_set1_ps(state->bsinc.sf)};
const ALsizei m{state->bsinc.m};
ASSUME(m > 0);
ASSUME(dstlen > 0);
ASSUME(increment > 0);
ASSUME(frac >= 0);
src -= state->bsinc.l;
for(ALsizei i{0};i < dstlen;i++)
{
// Calculate the phase index and factor.
#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
const ALsizei pi{frac >> FRAC_PHASE_BITDIFF};
const ALfloat pf{(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * (1.0f/(1<<FRAC_PHASE_BITDIFF))};
#undef FRAC_PHASE_BITDIFF
ALsizei offset{m*pi*4};
const __m128 *fil{reinterpret_cast<const __m128*>(filter + offset)}; offset += m;
const __m128 *scd{reinterpret_cast<const __m128*>(filter + offset)}; offset += m;
const __m128 *phd{reinterpret_cast<const __m128*>(filter + offset)}; offset += m;
const __m128 *spd{reinterpret_cast<const __m128*>(filter + offset)};
// Apply the scale and phase interpolated filter.
__m128 r4{_mm_setzero_ps()};
{
const ALsizei count{m >> 2};
const __m128 pf4{_mm_set1_ps(pf)};
ASSUME(count > 0);
#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z))
for(ALsizei j{0};j < count;j++)
{
/* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */
const __m128 f4 = MLA4(
MLA4(fil[j], sf4, scd[j]),
pf4, MLA4(phd[j], sf4, spd[j])
);
/* r += f*src */
r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j*4]));
}
#undef MLA4
}
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
dst[i] = _mm_cvtss_f32(r4);
frac += increment;
src += frac>>FRACTIONBITS;
frac &= FRACTIONMASK;
}
return dst;
}
static inline void ApplyCoeffs(ALsizei Offset, float2 *RESTRICT Values, const ALsizei IrSize,
const HrirArray<ALfloat> &Coeffs, const ALfloat left, const ALfloat right)
{
const __m128 lrlr{_mm_setr_ps(left, right, left, right)};
ASSUME(IrSize >= 2);
if((Offset&1))
{
__m128 imp0, imp1;
__m128 coeffs{_mm_load_ps(&Coeffs[0][0])};
__m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(&Values[0][0]))};
imp0 = _mm_mul_ps(lrlr, coeffs);
vals = _mm_add_ps(imp0, vals);
_mm_storel_pi(reinterpret_cast<__m64*>(&Values[0][0]), vals);
ALsizei i{1};
for(;i < IrSize-1;i += 2)
{
coeffs = _mm_load_ps(&Coeffs[i+1][0]);
vals = _mm_load_ps(&Values[i][0]);
imp1 = _mm_mul_ps(lrlr, coeffs);
imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2));
vals = _mm_add_ps(imp0, vals);
_mm_store_ps(&Values[i][0], vals);
imp0 = imp1;
}
vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(&Values[i][0]));
imp0 = _mm_movehl_ps(imp0, imp0);
vals = _mm_add_ps(imp0, vals);
_mm_storel_pi(reinterpret_cast<__m64*>(&Values[i][0]), vals);
}
else
{
for(ALsizei i{0};i < IrSize;i += 2)
{
__m128 coeffs{_mm_load_ps(&Coeffs[i][0])};
__m128 vals{_mm_load_ps(&Values[i][0])};
vals = _mm_add_ps(vals, _mm_mul_ps(lrlr, coeffs));
_mm_store_ps(&Values[i][0], vals);
}
}
}
template<>
void MixHrtf_<SSETag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut, const ALfloat *data,
float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize,
MixHrtfParams *hrtfparams, const ALsizei BufferSize)
{
MixHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, hrtfparams,
BufferSize);
}
template<>
void MixHrtfBlend_<SSETag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut,
const ALfloat *data, float2 *RESTRICT AccumSamples, const ALsizei OutPos, const ALsizei IrSize,
const HrtfParams *oldparams, MixHrtfParams *newparams, const ALsizei BufferSize)
{
MixHrtfBlendBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, OutPos, IrSize, oldparams,
newparams, BufferSize);
}
template<>
void MixDirectHrtf_<SSETag>(ALfloat *RESTRICT LeftOut, ALfloat *RESTRICT RightOut,
const ALfloat (*data)[BUFFERSIZE], float2 *RESTRICT AccumSamples, DirectHrtfState *State,
const ALsizei NumChans, const ALsizei BufferSize)
{
MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, data, AccumSamples, State, NumChans,
BufferSize);
}
template<>
void Mix_<SSETag>(const ALfloat *data, const ALsizei OutChans, ALfloat (*OutBuffer)[BUFFERSIZE],
ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos,
const ALsizei BufferSize)
{
ASSUME(OutChans > 0);
ASSUME(BufferSize > 0);
const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f};
for(ALsizei c{0};c < OutChans;c++)
{
ALfloat *RESTRICT dst{al::assume_aligned<16>(&OutBuffer[c][OutPos])};
ALsizei pos{0};
ALfloat gain{CurrentGains[c]};
const ALfloat diff{TargetGains[c] - gain};
if(std::fabs(diff) > std::numeric_limits<float>::epsilon())
{
ALsizei minsize{mini(BufferSize, Counter)};
const ALfloat step{diff * delta};
ALfloat step_count{0.0f};
/* Mix with applying gain steps in aligned multiples of 4. */
if(LIKELY(minsize > 3))
{
const __m128 four4{_mm_set1_ps(4.0f)};
const __m128 step4{_mm_set1_ps(step)};
const __m128 gain4{_mm_set1_ps(gain)};
__m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)};
ALsizei todo{minsize >> 2};
do {
const __m128 val4{_mm_load_ps(&data[pos])};
__m128 dry4{_mm_load_ps(&dst[pos])};
#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z))
/* dry += val * (gain + step*step_count) */
dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4));
#undef MLA4
_mm_store_ps(&dst[pos], dry4);
step_count4 = _mm_add_ps(step_count4, four4);
pos += 4;
} while(--todo);
/* NOTE: step_count4 now represents the next four counts after
* the last four mixed samples, so the lowest element
* represents the next step count to apply.
*/
step_count = _mm_cvtss_f32(step_count4);
}
/* Mix with applying left over gain steps that aren't aligned multiples of 4. */
for(;pos < minsize;pos++)
{
dst[pos] += data[pos]*(gain + step*step_count);
step_count += 1.0f;
}
if(pos == Counter)
gain = TargetGains[c];
else
gain += step*step_count;
CurrentGains[c] = gain;
/* Mix until pos is aligned with 4 or the mix is done. */
minsize = mini(BufferSize, (pos+3)&~3);
for(;pos < minsize;pos++)
dst[pos] += data[pos]*gain;
}
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
continue;
if(LIKELY(BufferSize-pos > 3))
{
ALsizei todo{(BufferSize-pos) >> 2};
const __m128 gain4{_mm_set1_ps(gain)};
do {
const __m128 val4{_mm_load_ps(&data[pos])};
__m128 dry4{_mm_load_ps(&dst[pos])};
dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
_mm_store_ps(&dst[pos], dry4);
pos += 4;
} while(--todo);
}
for(;pos < BufferSize;pos++)
dst[pos] += data[pos]*gain;
}
}
template<>
void MixRow_<SSETag>(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*data)[BUFFERSIZE],
const ALsizei InChans, const ALsizei InPos, const ALsizei BufferSize)
{
ASSUME(InChans > 0);
ASSUME(BufferSize > 0);
for(ALsizei c{0};c < InChans;c++)
{
const ALfloat *RESTRICT src{al::assume_aligned<16>(&data[c][InPos])};
const ALfloat gain{Gains[c]};
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
continue;
ALsizei pos{0};
if(LIKELY(BufferSize > 3))
{
ALsizei todo{BufferSize >> 2};
const __m128 gain4 = _mm_set1_ps(gain);
do {
const __m128 val4{_mm_load_ps(&src[pos])};
__m128 dry4{_mm_load_ps(&OutBuffer[pos])};
dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
_mm_store_ps(&OutBuffer[pos], dry4);
pos += 4;
} while(--todo);
}
for(;pos < BufferSize;pos++)
OutBuffer[pos] += src[pos]*gain;
}
}

+ 0
- 85
modules/openal-soft/Alc/mixer/mixer_sse2.cpp View File

@ -1,85 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2014 by Timothy Arceri <t_arceri@yahoo.com.au>.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <xmmintrin.h>
#include <emmintrin.h>
#include "alu.h"
#include "defs.h"
template<>
const ALfloat *Resample_<LerpTag,SSE2Tag>(const InterpState* UNUSED(state),
const ALfloat *RESTRICT src, ALsizei frac, ALint increment,
ALfloat *RESTRICT dst, ALsizei dstlen)
{
const __m128i increment4{_mm_set1_epi32(increment*4)};
const __m128 fracOne4{_mm_set1_ps(1.0f/FRACTIONONE)};
const __m128i fracMask4{_mm_set1_epi32(FRACTIONMASK)};
ASSUME(frac > 0);
ASSUME(increment > 0);
ASSUME(dstlen >= 0);
alignas(16) ALsizei pos_[4], frac_[4];
InitiatePositionArrays(frac, increment, frac_, pos_, 4);
__m128i frac4{_mm_setr_epi32(frac_[0], frac_[1], frac_[2], frac_[3])};
__m128i pos4{_mm_setr_epi32(pos_[0], pos_[1], pos_[2], pos_[3])};
const ALsizei todo{dstlen & ~3};
for(ALsizei i{0};i < todo;i += 4)
{
const int pos0{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(0, 0, 0, 0)))};
const int pos1{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(1, 1, 1, 1)))};
const int pos2{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(2, 2, 2, 2)))};
const int pos3{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(3, 3, 3, 3)))};
const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])};
const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])};
/* val1 + (val2-val1)*mu */
const __m128 r0{_mm_sub_ps(val2, val1)};
const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)};
const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))};
_mm_store_ps(&dst[i], out);
frac4 = _mm_add_epi32(frac4, increment4);
pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS));
frac4 = _mm_and_si128(frac4, fracMask4);
}
/* NOTE: These four elements represent the position *after* the last four
* samples, so the lowest element is the next position to resample.
*/
ALsizei pos{_mm_cvtsi128_si32(pos4)};
frac = _mm_cvtsi128_si32(frac4);
for(ALsizei i{todo};i < dstlen;++i)
{
dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE));
frac += increment;
pos += frac>>FRACTIONBITS;
frac &= FRACTIONMASK;
}
return dst;
}

+ 0
- 878
modules/openal-soft/Alc/mixvoice.cpp View File

@ -1,878 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cassert>
#include <numeric>
#include <algorithm>
#include "AL/al.h"
#include "AL/alc.h"
#include "alMain.h"
#include "alcontext.h"
#include "alSource.h"
#include "alBuffer.h"
#include "alListener.h"
#include "alAuxEffectSlot.h"
#include "sample_cvt.h"
#include "alu.h"
#include "alconfig.h"
#include "ringbuffer.h"
#include "cpu_caps.h"
#include "mixer/defs.h"
static_assert((INT_MAX>>FRACTIONBITS)/MAX_PITCH > BUFFERSIZE,
"MAX_PITCH and/or BUFFERSIZE are too large for FRACTIONBITS!");
/* BSinc24 requires up to 23 extra samples before the current position, and 24 after. */
static_assert(MAX_RESAMPLE_PADDING >= 24, "MAX_RESAMPLE_PADDING must be at least 24!");
Resampler ResamplerDefault = LinearResampler;
MixerFunc MixSamples = Mix_<CTag>;
RowMixerFunc MixRowSamples = MixRow_<CTag>;
static HrtfMixerFunc MixHrtfSamples = MixHrtf_<CTag>;
static HrtfMixerBlendFunc MixHrtfBlendSamples = MixHrtfBlend_<CTag>;
static MixerFunc SelectMixer()
{
#ifdef HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Mix_<NEONTag>;
#endif
#ifdef HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Mix_<SSETag>;
#endif
return Mix_<CTag>;
}
static RowMixerFunc SelectRowMixer()
{
#ifdef HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return MixRow_<NEONTag>;
#endif
#ifdef HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return MixRow_<SSETag>;
#endif
return MixRow_<CTag>;
}
static inline HrtfMixerFunc SelectHrtfMixer()
{
#ifdef HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return MixHrtf_<NEONTag>;
#endif
#ifdef HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return MixHrtf_<SSETag>;
#endif
return MixHrtf_<CTag>;
}
static inline HrtfMixerBlendFunc SelectHrtfBlendMixer()
{
#ifdef HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return MixHrtfBlend_<NEONTag>;
#endif
#ifdef HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return MixHrtfBlend_<SSETag>;
#endif
return MixHrtfBlend_<CTag>;
}
ResamplerFunc SelectResampler(Resampler resampler)
{
switch(resampler)
{
case PointResampler:
return Resample_<PointTag,CTag>;
case LinearResampler:
#ifdef HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<LerpTag,NEONTag>;
#endif
#ifdef HAVE_SSE4_1
if((CPUCapFlags&CPU_CAP_SSE4_1))
return Resample_<LerpTag,SSE4Tag>;
#endif
#ifdef HAVE_SSE2
if((CPUCapFlags&CPU_CAP_SSE2))
return Resample_<LerpTag,SSE2Tag>;
#endif
return Resample_<LerpTag,CTag>;
case FIR4Resampler:
return Resample_<CubicTag,CTag>;
case BSinc12Resampler:
case BSinc24Resampler:
#ifdef HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<BSincTag,NEONTag>;
#endif
#ifdef HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Resample_<BSincTag,SSETag>;
#endif
return Resample_<BSincTag,CTag>;
}
return Resample_<PointTag,CTag>;
}
void aluInitMixer()
{
const char *str;
if(ConfigValueStr(nullptr, nullptr, "resampler", &str))
{
if(strcasecmp(str, "point") == 0 || strcasecmp(str, "none") == 0)
ResamplerDefault = PointResampler;
else if(strcasecmp(str, "linear") == 0)
ResamplerDefault = LinearResampler;
else if(strcasecmp(str, "cubic") == 0)
ResamplerDefault = FIR4Resampler;
else if(strcasecmp(str, "bsinc12") == 0)
ResamplerDefault = BSinc12Resampler;
else if(strcasecmp(str, "bsinc24") == 0)
ResamplerDefault = BSinc24Resampler;
else if(strcasecmp(str, "bsinc") == 0)
{
WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str);
ResamplerDefault = BSinc12Resampler;
}
else if(strcasecmp(str, "sinc4") == 0 || strcasecmp(str, "sinc8") == 0)
{
WARN("Resampler option \"%s\" is deprecated, using cubic\n", str);
ResamplerDefault = FIR4Resampler;
}
else
{
char *end;
long n = strtol(str, &end, 0);
if(*end == '\0' && (n == PointResampler || n == LinearResampler || n == FIR4Resampler))
ResamplerDefault = static_cast<Resampler>(n);
else
WARN("Invalid resampler: %s\n", str);
}
}
MixHrtfBlendSamples = SelectHrtfBlendMixer();
MixHrtfSamples = SelectHrtfMixer();
MixSamples = SelectMixer();
MixRowSamples = SelectRowMixer();
}
namespace {
void SendSourceStoppedEvent(ALCcontext *context, ALuint id)
{
ALbitfieldSOFT enabledevt{context->EnabledEvts.load(std::memory_order_acquire)};
if(!(enabledevt&EventType_SourceStateChange)) return;
RingBuffer *ring{context->AsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len < 1) return;
AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}};
evt->u.srcstate.id = id;
evt->u.srcstate.state = AL_STOPPED;
ring->writeAdvance(1);
context->EventSem.post();
}
const ALfloat *DoFilters(BiquadFilter *lpfilter, BiquadFilter *hpfilter,
ALfloat *RESTRICT dst, const ALfloat *RESTRICT src, ALsizei numsamples, int type)
{
switch(type)
{
case AF_None:
lpfilter->passthru(numsamples);
hpfilter->passthru(numsamples);
break;
case AF_LowPass:
lpfilter->process(dst, src, numsamples);
hpfilter->passthru(numsamples);
return dst;
case AF_HighPass:
lpfilter->passthru(numsamples);
hpfilter->process(dst, src, numsamples);
return dst;
case AF_BandPass:
for(ALsizei i{0};i < numsamples;)
{
ALfloat temp[256];
ALsizei todo = mini(256, numsamples-i);
lpfilter->process(temp, src+i, todo);
hpfilter->process(dst+i, temp, todo);
i += todo;
}
return dst;
}
return src;
}
/* Base template left undefined. Should be marked =delete, but Clang 3.8.1
* chokes on that given the inline specializations.
*/
template<FmtType T>
inline ALfloat LoadSample(typename FmtTypeTraits<T>::Type val);
template<> inline ALfloat LoadSample<FmtUByte>(FmtTypeTraits<FmtUByte>::Type val)
{ return (val-128) * (1.0f/128.0f); }
template<> inline ALfloat LoadSample<FmtShort>(FmtTypeTraits<FmtShort>::Type val)
{ return val * (1.0f/32768.0f); }
template<> inline ALfloat LoadSample<FmtFloat>(FmtTypeTraits<FmtFloat>::Type val)
{ return val; }
template<> inline ALfloat LoadSample<FmtDouble>(FmtTypeTraits<FmtDouble>::Type val)
{ return static_cast<ALfloat>(val); }
template<> inline ALfloat LoadSample<FmtMulaw>(FmtTypeTraits<FmtMulaw>::Type val)
{ return muLawDecompressionTable[val] * (1.0f/32768.0f); }
template<> inline ALfloat LoadSample<FmtAlaw>(FmtTypeTraits<FmtAlaw>::Type val)
{ return aLawDecompressionTable[val] * (1.0f/32768.0f); }
template<FmtType T>
inline void LoadSampleArray(ALfloat *RESTRICT dst, const void *src, ALint srcstep,
const ptrdiff_t samples)
{
using SampleType = typename FmtTypeTraits<T>::Type;
const SampleType *ssrc = static_cast<const SampleType*>(src);
for(ALsizei i{0};i < samples;i++)
dst[i] += LoadSample<T>(ssrc[i*srcstep]);
}
void LoadSamples(ALfloat *RESTRICT dst, const ALvoid *RESTRICT src, ALint srcstep, FmtType srctype,
const ptrdiff_t samples)
{
#define HANDLE_FMT(T) case T: LoadSampleArray<T>(dst, src, srcstep, samples); break
switch(srctype)
{
HANDLE_FMT(FmtUByte);
HANDLE_FMT(FmtShort);
HANDLE_FMT(FmtFloat);
HANDLE_FMT(FmtDouble);
HANDLE_FMT(FmtMulaw);
HANDLE_FMT(FmtAlaw);
}
#undef HANDLE_FMT
}
ALfloat *LoadBufferStatic(ALbufferlistitem *BufferListItem, ALbufferlistitem *&BufferLoopItem,
const ALsizei NumChannels, const ALsizei SampleSize, const ALsizei chan, ALsizei DataPosInt,
ALfloat *SrcData, const ALfloat *const SrcDataEnd)
{
/* TODO: For static sources, loop points are taken from the first buffer
* (should be adjusted by any buffer offset, to possibly be added later).
*/
const ALbuffer *Buffer0{BufferListItem->buffers[0]};
const ALsizei LoopStart{Buffer0->LoopStart};
const ALsizei LoopEnd{Buffer0->LoopEnd};
ASSUME(LoopStart >= 0);
ASSUME(LoopEnd > LoopStart);
/* If current pos is beyond the loop range, do not loop */
if(!BufferLoopItem || DataPosInt >= LoopEnd)
{
const ptrdiff_t SizeToDo{SrcDataEnd - SrcData};
ASSUME(SizeToDo > 0);
BufferLoopItem = nullptr;
auto load_buffer = [DataPosInt,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t
{
if(DataPosInt >= buffer->SampleLen)
return CompLen;
/* Load what's left to play from the buffer */
const ptrdiff_t DataSize{std::min<ptrdiff_t>(SizeToDo, buffer->SampleLen-DataPosInt)};
CompLen = std::max<ptrdiff_t>(CompLen, DataSize);
const ALbyte *Data{buffer->mData.data()};
Data += (DataPosInt*NumChannels + chan)*SampleSize;
LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize);
return CompLen;
};
/* It's impossible to have a buffer list item with no entries. */
ASSUME(BufferListItem->num_buffers > 0);
auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers;
SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0},
load_buffer);
}
else
{
const ptrdiff_t SizeToDo{std::min<ptrdiff_t>(SrcDataEnd-SrcData, LoopEnd-DataPosInt)};
ASSUME(SizeToDo > 0);
auto load_buffer = [DataPosInt,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t
{
if(DataPosInt >= buffer->SampleLen)
return CompLen;
/* Load what's left of this loop iteration */
const ptrdiff_t DataSize{std::min<ptrdiff_t>(SizeToDo, buffer->SampleLen-DataPosInt)};
CompLen = std::max<ptrdiff_t>(CompLen, DataSize);
const ALbyte *Data{buffer->mData.data()};
Data += (DataPosInt*NumChannels + chan)*SampleSize;
LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize);
return CompLen;
};
ASSUME(BufferListItem->num_buffers > 0);
auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers;
SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0},
load_buffer);
const auto LoopSize = static_cast<ptrdiff_t>(LoopEnd - LoopStart);
while(SrcData != SrcDataEnd)
{
const ptrdiff_t SizeToDo{std::min<ptrdiff_t>(SrcDataEnd-SrcData, LoopSize)};
ASSUME(SizeToDo > 0);
auto load_buffer_loop = [LoopStart,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t
{
if(LoopStart >= buffer->SampleLen)
return CompLen;
const ptrdiff_t DataSize{std::min<ptrdiff_t>(SizeToDo,
buffer->SampleLen-LoopStart)};
CompLen = std::max<ptrdiff_t>(CompLen, DataSize);
const ALbyte *Data{buffer->mData.data()};
Data += (LoopStart*NumChannels + chan)*SampleSize;
LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize);
return CompLen;
};
SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0},
load_buffer_loop);
}
}
return SrcData;
}
ALfloat *LoadBufferQueue(ALbufferlistitem *BufferListItem, ALbufferlistitem *BufferLoopItem,
const ALsizei NumChannels, const ALsizei SampleSize, const ALsizei chan, ALsizei DataPosInt,
ALfloat *SrcData, const ALfloat *const SrcDataEnd)
{
/* Crawl the buffer queue to fill in the temp buffer */
while(BufferListItem && SrcData != SrcDataEnd)
{
if(DataPosInt >= BufferListItem->max_samples)
{
DataPosInt -= BufferListItem->max_samples;
BufferListItem = BufferListItem->next.load(std::memory_order_acquire);
if(!BufferListItem) BufferListItem = BufferLoopItem;
continue;
}
const ptrdiff_t SizeToDo{SrcDataEnd - SrcData};
ASSUME(SizeToDo > 0);
auto load_buffer = [DataPosInt,SrcData,NumChannels,SampleSize,chan,SizeToDo](ptrdiff_t CompLen, const ALbuffer *buffer) -> ptrdiff_t
{
if(!buffer) return CompLen;
if(DataPosInt >= buffer->SampleLen)
return CompLen;
const ptrdiff_t DataSize{std::min<ptrdiff_t>(SizeToDo, buffer->SampleLen-DataPosInt)};
CompLen = std::max<ptrdiff_t>(CompLen, DataSize);
const ALbyte *Data{buffer->mData.data()};
Data += (DataPosInt*NumChannels + chan)*SampleSize;
LoadSamples(SrcData, Data, NumChannels, buffer->mFmtType, DataSize);
return CompLen;
};
ASSUME(BufferListItem->num_buffers > 0);
auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers;
SrcData += std::accumulate(BufferListItem->buffers, buffers_end, ptrdiff_t{0u},
load_buffer);
if(SrcData == SrcDataEnd)
break;
DataPosInt = 0;
BufferListItem = BufferListItem->next.load(std::memory_order_acquire);
if(!BufferListItem) BufferListItem = BufferLoopItem;
}
return SrcData;
}
} // namespace
void MixVoice(ALvoice *voice, ALvoice::State vstate, const ALuint SourceID, ALCcontext *Context, const ALsizei SamplesToDo)
{
static constexpr ALfloat SilentTarget[MAX_OUTPUT_CHANNELS]{};
ASSUME(SamplesToDo > 0);
/* Get voice info */
const bool isstatic{(voice->mFlags&VOICE_IS_STATIC) != 0};
ALsizei DataPosInt{static_cast<ALsizei>(voice->mPosition.load(std::memory_order_relaxed))};
ALsizei DataPosFrac{voice->mPositionFrac.load(std::memory_order_relaxed)};
ALbufferlistitem *BufferListItem{voice->mCurrentBuffer.load(std::memory_order_relaxed)};
ALbufferlistitem *BufferLoopItem{voice->mLoopBuffer.load(std::memory_order_relaxed)};
const ALsizei NumChannels{voice->mNumChannels};
const ALsizei SampleSize{voice->mSampleSize};
const ALint increment{voice->mStep};
ASSUME(DataPosInt >= 0);
ASSUME(DataPosFrac >= 0);
ASSUME(NumChannels > 0);
ASSUME(SampleSize > 0);
ASSUME(increment > 0);
ALCdevice *Device{Context->Device};
const ALsizei IrSize{Device->mHrtf ? Device->mHrtf->irSize : 0};
ASSUME(IrSize >= 0);
ResamplerFunc Resample{(increment == FRACTIONONE && DataPosFrac == 0) ?
Resample_<CopyTag,CTag> : voice->mResampler};
ALsizei Counter{(voice->mFlags&VOICE_IS_FADING) ? SamplesToDo : 0};
if(!Counter)
{
/* No fading, just overwrite the old/current params. */
for(ALsizei chan{0};chan < NumChannels;chan++)
{
DirectParams &parms = voice->mDirect.Params[chan];
if(!(voice->mFlags&VOICE_HAS_HRTF))
std::copy(std::begin(parms.Gains.Target), std::end(parms.Gains.Target),
std::begin(parms.Gains.Current));
else
parms.Hrtf.Old = parms.Hrtf.Target;
auto set_current = [chan](ALvoice::SendData &send) -> void
{
if(!send.Buffer)
return;
SendParams &parms = send.Params[chan];
std::copy(std::begin(parms.Gains.Target), std::end(parms.Gains.Target),
std::begin(parms.Gains.Current));
};
std::for_each(voice->mSend.begin(), voice->mSend.end(), set_current);
}
}
else if((voice->mFlags&VOICE_HAS_HRTF))
{
for(ALsizei chan{0};chan < NumChannels;chan++)
{
DirectParams &parms = voice->mDirect.Params[chan];
if(!(parms.Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD))
{
/* The old HRTF params are silent, so overwrite the old
* coefficients with the new, and reset the old gain to 0. The
* future mix will then fade from silence.
*/
parms.Hrtf.Old = parms.Hrtf.Target;
parms.Hrtf.Old.Gain = 0.0f;
}
}
}
ALsizei buffers_done{0};
ALsizei OutPos{0};
do {
/* Figure out how many buffer samples will be needed */
ALsizei DstBufferSize{SamplesToDo - OutPos};
/* Calculate the last written dst sample pos. */
int64_t DataSize64{DstBufferSize - 1};
/* Calculate the last read src sample pos. */
DataSize64 = (DataSize64*increment + DataPosFrac) >> FRACTIONBITS;
/* +1 to get the src sample count, include padding. */
DataSize64 += 1 + MAX_RESAMPLE_PADDING*2;
auto SrcBufferSize = static_cast<ALsizei>(
mini64(DataSize64, BUFFERSIZE + MAX_RESAMPLE_PADDING*2 + 1));
if(SrcBufferSize > BUFFERSIZE + MAX_RESAMPLE_PADDING*2)
{
SrcBufferSize = BUFFERSIZE + MAX_RESAMPLE_PADDING*2;
/* If the source buffer got saturated, we can't fill the desired
* dst size. Figure out how many samples we can actually mix from
* this.
*/
DataSize64 = SrcBufferSize - MAX_RESAMPLE_PADDING*2;
DataSize64 = ((DataSize64<<FRACTIONBITS) - DataPosFrac + increment-1) / increment;
DstBufferSize = static_cast<ALsizei>(mini64(DataSize64, DstBufferSize));
/* Some mixers like having a multiple of 4, so try to give that
* unless this is the last update.
*/
if(DstBufferSize < SamplesToDo-OutPos)
DstBufferSize &= ~3;
}
for(ALsizei chan{0};chan < NumChannels;chan++)
{
auto &SrcData = Device->SourceData;
/* Load the previous samples into the source data first, and clear the rest. */
auto srciter = std::copy_n(voice->mResampleData[chan].mPrevSamples.begin(),
MAX_RESAMPLE_PADDING, std::begin(SrcData));
std::fill(srciter, std::end(SrcData), 0.0f);
auto srcdata_end = std::begin(SrcData) + SrcBufferSize;
if(UNLIKELY(!BufferListItem))
srciter = std::copy(
voice->mResampleData[chan].mPrevSamples.begin()+MAX_RESAMPLE_PADDING,
voice->mResampleData[chan].mPrevSamples.end(), srciter);
else if(isstatic)
srciter = LoadBufferStatic(BufferListItem, BufferLoopItem, NumChannels,
SampleSize, chan, DataPosInt, srciter, srcdata_end);
else
srciter = LoadBufferQueue(BufferListItem, BufferLoopItem, NumChannels,
SampleSize, chan, DataPosInt, srciter, srcdata_end);
if(UNLIKELY(srciter != srcdata_end))
{
/* If the source buffer wasn't filled, copy the last sample for
* the remaining buffer. Ideally it should have ended with
* silence, but if not the gain fading should help avoid clicks
* from sudden amplitude changes.
*/
const ALfloat sample{*(srciter-1)};
std::fill(srciter, srcdata_end, sample);
}
/* Store the last source samples used for next time. */
std::copy_n(&SrcData[(increment*DstBufferSize + DataPosFrac)>>FRACTIONBITS],
voice->mResampleData[chan].mPrevSamples.size(),
voice->mResampleData[chan].mPrevSamples.begin());
/* Resample, then apply ambisonic upsampling as needed. */
const ALfloat *ResampledData{Resample(&voice->mResampleState,
&SrcData[MAX_RESAMPLE_PADDING], DataPosFrac, increment,
Device->ResampledData, DstBufferSize)};
if((voice->mFlags&VOICE_IS_AMBISONIC))
{
const ALfloat hfscale{voice->mResampleData[chan].mAmbiScale};
/* Beware the evil const_cast. It's safe since it's pointing to
* either SrcData or Device->ResampledData (both non-const),
* but the resample method takes its input as const float* and
* may return it without copying to output, making it currently
* unavoidable.
*/
voice->mResampleData[chan].mAmbiSplitter.applyHfScale(
const_cast<ALfloat*>(ResampledData), hfscale, DstBufferSize);
}
/* Now filter and mix to the appropriate outputs. */
{
DirectParams &parms = voice->mDirect.Params[chan];
const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass,
Device->FilteredData, ResampledData, DstBufferSize,
voice->mDirect.FilterType)};
if((voice->mFlags&VOICE_HAS_HRTF))
{
const int OutLIdx{GetChannelIdxByName(Device->RealOut, FrontLeft)};
const int OutRIdx{GetChannelIdxByName(Device->RealOut, FrontRight)};
ASSUME(OutLIdx >= 0 && OutRIdx >= 0);
auto &HrtfSamples = Device->HrtfSourceData;
auto &AccumSamples = Device->HrtfAccumData;
const ALfloat TargetGain{UNLIKELY(vstate == ALvoice::Stopping) ? 0.0f :
parms.Hrtf.Target.Gain};
ALsizei fademix{0};
/* Copy the HRTF history and new input samples into a temp
* buffer.
*/
auto src_iter = std::copy(parms.Hrtf.State.History.begin(),
parms.Hrtf.State.History.end(), std::begin(HrtfSamples));
std::copy_n(samples, DstBufferSize, src_iter);
/* Copy the last used samples back into the history buffer
* for later.
*/
std::copy_n(std::begin(HrtfSamples) + DstBufferSize,
parms.Hrtf.State.History.size(), parms.Hrtf.State.History.begin());
/* Copy the current filtered values being accumulated into
* the temp buffer.
*/
auto accum_iter = std::copy_n(parms.Hrtf.State.Values.begin(),
parms.Hrtf.State.Values.size(), std::begin(AccumSamples));
/* Clear the accumulation buffer that will start getting
* filled in.
*/
std::fill_n(accum_iter, DstBufferSize, float2{});
/* If fading, the old gain is not silence, and this is the
* first mixing pass, fade between the IRs.
*/
if(Counter && (parms.Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD) && OutPos == 0)
{
fademix = mini(DstBufferSize, 128);
ALfloat gain{TargetGain};
/* The new coefficients need to fade in completely
* since they're replacing the old ones. To keep the
* gain fading consistent, interpolate between the old
* and new target gains given how much of the fade time
* this mix handles.
*/
if(LIKELY(Counter > fademix))
{
const ALfloat a{static_cast<ALfloat>(fademix) /
static_cast<ALfloat>(Counter)};
gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a);
}
MixHrtfParams hrtfparams;
hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs;
hrtfparams.Delay[0] = parms.Hrtf.Target.Delay[0];
hrtfparams.Delay[1] = parms.Hrtf.Target.Delay[1];
hrtfparams.Gain = 0.0f;
hrtfparams.GainStep = gain / static_cast<ALfloat>(fademix);
MixHrtfBlendSamples(
voice->mDirect.Buffer[OutLIdx], voice->mDirect.Buffer[OutRIdx],
HrtfSamples, AccumSamples, OutPos, IrSize, &parms.Hrtf.Old,
&hrtfparams, fademix);
/* Update the old parameters with the result. */
parms.Hrtf.Old = parms.Hrtf.Target;
if(fademix < Counter)
parms.Hrtf.Old.Gain = hrtfparams.Gain;
else
parms.Hrtf.Old.Gain = TargetGain;
}
if(LIKELY(fademix < DstBufferSize))
{
const ALsizei todo{DstBufferSize - fademix};
ALfloat gain{TargetGain};
/* Interpolate the target gain if the gain fading lasts
* longer than this mix.
*/
if(Counter > DstBufferSize)
{
const ALfloat a{static_cast<ALfloat>(todo) /
static_cast<ALfloat>(Counter-fademix)};
gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a);
}
MixHrtfParams hrtfparams;
hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs;
hrtfparams.Delay[0] = parms.Hrtf.Target.Delay[0];
hrtfparams.Delay[1] = parms.Hrtf.Target.Delay[1];
hrtfparams.Gain = parms.Hrtf.Old.Gain;
hrtfparams.GainStep = (gain - parms.Hrtf.Old.Gain) /
static_cast<ALfloat>(todo);
MixHrtfSamples(
voice->mDirect.Buffer[OutLIdx], voice->mDirect.Buffer[OutRIdx],
HrtfSamples+fademix, AccumSamples+fademix, OutPos+fademix, IrSize,
&hrtfparams, todo);
/* Store the interpolated gain or the final target gain
* depending if the fade is done.
*/
if(DstBufferSize < Counter)
parms.Hrtf.Old.Gain = gain;
else
parms.Hrtf.Old.Gain = TargetGain;
}
/* Copy the new in-progress accumulation values back for
* the next mix.
*/
std::copy_n(std::begin(AccumSamples) + DstBufferSize,
parms.Hrtf.State.Values.size(), parms.Hrtf.State.Values.begin());
}
else if((voice->mFlags&VOICE_HAS_NFC))
{
const ALfloat *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ?
SilentTarget : parms.Gains.Target};
MixSamples(samples, voice->mDirect.ChannelsPerOrder[0],
voice->mDirect.Buffer, parms.Gains.Current, TargetGains, Counter,
OutPos, DstBufferSize);
ALfloat (&nfcsamples)[BUFFERSIZE] = Device->NfcSampleData;
ALsizei chanoffset{voice->mDirect.ChannelsPerOrder[0]};
using FilterProc = void (NfcFilter::*)(float*,const float*,int);
auto apply_nfc = [voice,&parms,samples,TargetGains,DstBufferSize,Counter,OutPos,&chanoffset,&nfcsamples](FilterProc process, ALsizei order) -> void
{
if(voice->mDirect.ChannelsPerOrder[order] < 1)
return;
(parms.NFCtrlFilter.*process)(nfcsamples, samples, DstBufferSize);
MixSamples(nfcsamples, voice->mDirect.ChannelsPerOrder[order],
voice->mDirect.Buffer+chanoffset, parms.Gains.Current+chanoffset,
TargetGains+chanoffset, Counter, OutPos, DstBufferSize);
chanoffset += voice->mDirect.ChannelsPerOrder[order];
};
apply_nfc(&NfcFilter::process1, 1);
apply_nfc(&NfcFilter::process2, 2);
apply_nfc(&NfcFilter::process3, 3);
}
else
{
const ALfloat *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ?
SilentTarget : parms.Gains.Target};
MixSamples(samples, voice->mDirect.Channels, voice->mDirect.Buffer,
parms.Gains.Current, TargetGains, Counter, OutPos, DstBufferSize);
}
}
ALfloat (&FilterBuf)[BUFFERSIZE] = Device->FilteredData;
auto mix_send = [vstate,Counter,OutPos,DstBufferSize,chan,ResampledData,&FilterBuf](ALvoice::SendData &send) -> void
{
if(!send.Buffer)
return;
SendParams &parms = send.Params[chan];
const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass,
FilterBuf, ResampledData, DstBufferSize, send.FilterType)};
const ALfloat *TargetGains{UNLIKELY(vstate==ALvoice::Stopping) ? SilentTarget :
parms.Gains.Target};
MixSamples(samples, send.Channels, send.Buffer, parms.Gains.Current,
TargetGains, Counter, OutPos, DstBufferSize);
};
std::for_each(voice->mSend.begin(), voice->mSend.end(), mix_send);
}
/* Update positions */
DataPosFrac += increment*DstBufferSize;
DataPosInt += DataPosFrac>>FRACTIONBITS;
DataPosFrac &= FRACTIONMASK;
OutPos += DstBufferSize;
Counter = maxi(DstBufferSize, Counter) - DstBufferSize;
if(UNLIKELY(!BufferListItem))
{
/* Do nothing extra when there's no buffers. */
}
else if(isstatic)
{
if(BufferLoopItem)
{
/* Handle looping static source */
const ALbuffer *Buffer{BufferListItem->buffers[0]};
const ALsizei LoopStart{Buffer->LoopStart};
const ALsizei LoopEnd{Buffer->LoopEnd};
if(DataPosInt >= LoopEnd)
{
assert(LoopEnd > LoopStart);
DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart;
}
}
else
{
/* Handle non-looping static source */
if(DataPosInt >= BufferListItem->max_samples)
{
if(LIKELY(vstate == ALvoice::Playing))
vstate = ALvoice::Stopped;
BufferListItem = nullptr;
break;
}
}
}
else while(1)
{
/* Handle streaming source */
if(BufferListItem->max_samples > DataPosInt)
break;
DataPosInt -= BufferListItem->max_samples;
buffers_done += BufferListItem->num_buffers;
BufferListItem = BufferListItem->next.load(std::memory_order_relaxed);
if(!BufferListItem && !(BufferListItem=BufferLoopItem))
{
if(LIKELY(vstate == ALvoice::Playing))
vstate = ALvoice::Stopped;
break;
}
}
} while(OutPos < SamplesToDo);
voice->mFlags |= VOICE_IS_FADING;
/* Don't update positions and buffers if we were stopping. */
if(UNLIKELY(vstate == ALvoice::Stopping))
{
voice->mPlayState.store(ALvoice::Stopped, std::memory_order_release);
return;
}
/* Update voice info */
voice->mPosition.store(DataPosInt, std::memory_order_relaxed);
voice->mPositionFrac.store(DataPosFrac, std::memory_order_relaxed);
voice->mCurrentBuffer.store(BufferListItem, std::memory_order_relaxed);
if(vstate == ALvoice::Stopped)
{
voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
voice->mSourceID.store(0u, std::memory_order_relaxed);
}
std::atomic_thread_fence(std::memory_order_release);
/* Send any events now, after the position/buffer info was updated. */
ALbitfieldSOFT enabledevt{Context->EnabledEvts.load(std::memory_order_acquire)};
if(buffers_done > 0 && (enabledevt&EventType_BufferCompleted))
{
RingBuffer *ring{Context->AsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len > 0)
{
AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_BufferCompleted}};
evt->u.bufcomp.id = SourceID;
evt->u.bufcomp.count = buffers_done;
ring->writeAdvance(1);
Context->EventSem.post();
}
}
if(vstate == ALvoice::Stopped)
{
/* If the voice just ended, set it to Stopping so the next render
* ensures any residual noise fades to 0 amplitude.
*/
voice->mPlayState.store(ALvoice::Stopping, std::memory_order_release);
SendSourceStoppedEvent(Context, SourceID);
}
}

+ 880
- 742
modules/openal-soft/Alc/panning.cpp
File diff suppressed because it is too large
View File


+ 0
- 126
modules/openal-soft/Alc/uhjfilter.cpp View File

@ -1,126 +0,0 @@
#include "config.h"
#include "uhjfilter.h"
#include <algorithm>
#include "alu.h"
namespace {
/* This is the maximum number of samples processed for each inner loop
* iteration. */
#define MAX_UPDATE_SAMPLES 128
constexpr ALfloat Filter1CoeffSqr[4] = {
0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f
};
constexpr ALfloat Filter2CoeffSqr[4] = {
0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156685f
};
void allpass_process(AllPassState *state, ALfloat *dst, const ALfloat *src, const ALfloat aa, ALsizei todo)
{
ALfloat z1{state->z[0]};
ALfloat z2{state->z[1]};
auto proc_sample = [aa,&z1,&z2](ALfloat input) noexcept -> ALfloat
{
ALfloat output = input*aa + z1;
z1 = z2; z2 = output*aa - input;
return output;
};
std::transform(src, src+todo, dst, proc_sample);
state->z[0] = z1;
state->z[1] = z2;
}
} // namespace
/* NOTE: There seems to be a bit of an inconsistency in how this encoding is
* supposed to work. Some references, such as
*
* http://members.tripod.com/martin_leese/Ambisonic/UHJ_file_format.html
*
* specify a pre-scaling of sqrt(2) on the W channel input, while other
* references, such as
*
* https://en.wikipedia.org/wiki/Ambisonic_UHJ_format#Encoding.5B1.5D
* and
* https://wiki.xiph.org/Ambisonics#UHJ_format
*
* do not. The sqrt(2) scaling is in line with B-Format decoder coefficients
* which include such a scaling for the W channel input, however the original
* source for this equation is a 1985 paper by Michael Gerzon, which does not
* apparently include the scaling. Applying the extra scaling creates a louder
* result with a narrower stereo image compared to not scaling, and I don't
* know which is the intended result.
*/
void Uhj2Encoder::encode(ALfloat *LeftOut, ALfloat *RightOut, ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo)
{
alignas(16) ALfloat D[MAX_UPDATE_SAMPLES], S[MAX_UPDATE_SAMPLES];
alignas(16) ALfloat temp[MAX_UPDATE_SAMPLES];
ASSUME(SamplesToDo > 0);
for(ALsizei base{0};base < SamplesToDo;)
{
ALsizei todo = mini(SamplesToDo - base, MAX_UPDATE_SAMPLES);
ASSUME(todo > 0);
/* D = 0.6554516*Y */
const ALfloat *RESTRICT input{al::assume_aligned<16>(InSamples[2]+base)};
for(ALsizei i{0};i < todo;i++)
temp[i] = 0.6554516f*input[i];
allpass_process(&mFilter1_Y[0], temp, temp, Filter1CoeffSqr[0], todo);
allpass_process(&mFilter1_Y[1], temp, temp, Filter1CoeffSqr[1], todo);
allpass_process(&mFilter1_Y[2], temp, temp, Filter1CoeffSqr[2], todo);
allpass_process(&mFilter1_Y[3], temp, temp, Filter1CoeffSqr[3], todo);
/* NOTE: Filter1 requires a 1 sample delay for the final output, so
* take the last processed sample from the previous run as the first
* output sample.
*/
D[0] = mLastY;
for(ALsizei i{1};i < todo;i++)
D[i] = temp[i-1];
mLastY = temp[todo-1];
/* D += j(-0.3420201*W + 0.5098604*X) */
const ALfloat *RESTRICT input0{al::assume_aligned<16>(InSamples[0]+base)};
const ALfloat *RESTRICT input1{al::assume_aligned<16>(InSamples[1]+base)};
for(ALsizei i{0};i < todo;i++)
temp[i] = -0.3420201f*input0[i] + 0.5098604f*input1[i];
allpass_process(&mFilter2_WX[0], temp, temp, Filter2CoeffSqr[0], todo);
allpass_process(&mFilter2_WX[1], temp, temp, Filter2CoeffSqr[1], todo);
allpass_process(&mFilter2_WX[2], temp, temp, Filter2CoeffSqr[2], todo);
allpass_process(&mFilter2_WX[3], temp, temp, Filter2CoeffSqr[3], todo);
for(ALsizei i{0};i < todo;i++)
D[i] += temp[i];
/* S = 0.9396926*W + 0.1855740*X */
for(ALsizei i{0};i < todo;i++)
temp[i] = 0.9396926f*input0[i] + 0.1855740f*input1[i];
allpass_process(&mFilter1_WX[0], temp, temp, Filter1CoeffSqr[0], todo);
allpass_process(&mFilter1_WX[1], temp, temp, Filter1CoeffSqr[1], todo);
allpass_process(&mFilter1_WX[2], temp, temp, Filter1CoeffSqr[2], todo);
allpass_process(&mFilter1_WX[3], temp, temp, Filter1CoeffSqr[3], todo);
S[0] = mLastWX;
for(ALsizei i{1};i < todo;i++)
S[i] = temp[i-1];
mLastWX = temp[todo-1];
/* Left = (S + D)/2.0 */
ALfloat *RESTRICT left = al::assume_aligned<16>(LeftOut+base);
for(ALsizei i{0};i < todo;i++)
left[i] += (S[i] + D[i]) * 0.5f;
/* Right = (S - D)/2.0 */
ALfloat *RESTRICT right = al::assume_aligned<16>(RightOut+base);
for(ALsizei i{0};i < todo;i++)
right[i] += (S[i] - D[i]) * 0.5f;
base += todo;
}
}

+ 0
- 53
modules/openal-soft/Alc/uhjfilter.h View File

@ -1,53 +0,0 @@
#ifndef UHJFILTER_H
#define UHJFILTER_H
#include "AL/al.h"
#include "alMain.h"
#include "almalloc.h"
struct AllPassState {
ALfloat z[2]{0.0f, 0.0f};
};
/* Encoding 2-channel UHJ from B-Format is done as:
*
* S = 0.9396926*W + 0.1855740*X
* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y
*
* Left = (S + D)/2.0
* Right = (S - D)/2.0
*
* where j is a wide-band +90 degree phase shift.
*
* The phase shift is done using a Hilbert transform, described here:
* https://web.archive.org/web/20060708031958/http://www.biochem.oulu.fi/~oniemita/dsp/hilbert/
* It works using 2 sets of 4 chained filters. The first filter chain produces
* a phase shift of varying magnitude over a wide range of frequencies, while
* the second filter chain produces a phase shift 90 degrees ahead of the
* first over the same range.
*
* Combining these two stages requires the use of three filter chains. S-
* channel output uses a Filter1 chain on the W and X channel mix, while the D-
* channel output uses a Filter1 chain on the Y channel plus a Filter2 chain on
* the W and X channel mix. This results in the W and X input mix on the D-
* channel output having the required +90 degree phase shift relative to the
* other inputs.
*/
struct Uhj2Encoder {
AllPassState mFilter1_Y[4];
AllPassState mFilter2_WX[4];
AllPassState mFilter1_WX[4];
ALfloat mLastY{0.0f}, mLastWX{0.0f};
/* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
* signal. The input must use FuMa channel ordering and scaling.
*/
void encode(ALfloat *LeftOut, ALfloat *RightOut, ALfloat (*InSamples)[BUFFERSIZE], const ALsizei SamplesToDo);
DEF_NEWDEL(Uhj2Encoder)
};
#endif /* UHJFILTER_H */

+ 31
- 0
modules/openal-soft/BSD-3Clause View File

@ -0,0 +1,31 @@
Portions of this software are licensed under the BSD 3-Clause license.
Copyright (c) 2015, Archontis Politis
Copyright (c) 2019, Anis A. Hireche
Copyright (c) 2019, Christopher Robinson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Spherical-Harmonic-Transform nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 1455
- 1406
modules/openal-soft/CMakeLists.txt
File diff suppressed because it is too large
View File


+ 253
- 0
modules/openal-soft/ChangeLog View File

@ -1,3 +1,256 @@
openal-soft-1.22.0:
Implemented the ALC_SOFT_reopen_device extension. This allows for moving
devices to different outputs without losing object state.
Implemented the ALC_SOFT_output_mode extension.
Implemented the AL_SOFT_callback_buffer extension.
Implemented the AL_SOFT_UHJ extension. This supports native UHJ buffer
formats and Super Stereo processing.
Implemented the legacy EAX extensions. Enabled by default only on Windows.
Improved sound positioning stability when a source is near the listener.
Improved the default 5.1 output decoder.
Improved the high frequency response for the HRTF second-order ambisonic
decoder.
Improved SoundIO capture behavior.
Fixed UHJ output on NEON-capable CPUs.
Fixed redundant effect updates when setting an effect property to the
current value.
Fixed WASAPI capture using really low sample rates, and sources with very
high pitch shifts when using a bsinc resampler.
Added a PipeWire backend.
Added enumeration for the JACK and CoreAudio backends.
Added optional support for RTKit to get real-time priority. Only used as a
backup when pthread_setschedparam fails.
Added an option for JACK playback to render directly in the real-time
processing callback. For lower playback latency, on by default.
Added an option for custom JACK devices.
Added utilities to encode and decode UHJ audio files. Files are decoded to
the .amb format, and are encoded from libsndfile-compatible formats.
Added an in-progress extension to hold sources in a playing state when a
device disconnects. Allows devices to be reset or reopened and have sources
resume from where they left off.
Lowered the priority of the JACK backend. To avoid it getting picked when
PipeWire is providing JACK compatibility, since the JACK backend is less
robust with auto-configuration.
openal-soft-1.21.1:
Improved alext.h's detection of standard types.
Improved slightly the local source position when the listener and source
are near each other.
Improved click/pop prevention for sounds that stop prematurely.
Fixed compilation for Windows ARM targets with MSVC.
Fixed ARM NEON detection on Windows.
Fixed CoreAudio capture when the requested sample rate doesn't match the
system configuration.
Fixed OpenSL capture desyncing from the internal capture buffer.
Fixed sources missing a batch update when applied after quickly restarting
the source.
Fixed missing source stop events when stopping a paused source.
Added capture support to the experimental Oboe backend.
openal-soft-1.21.0:
Updated library codebase to C++14.
Implemented the AL_SOFT_effect_target extension.
Implemented the AL_SOFT_events extension.
Implemented the ALC_SOFT_loopback_bformat extension.
Improved memory use for mixing voices.
Improved detection of NEON capabilities.
Improved handling of PulseAudio devices that lack manual start control.
Improved mixing performance with PulseAudio.
Improved high-frequency scaling quality for the HRTF B-Format decoder.
Improved makemhr's HRIR delay calculation.
Improved WASAPI capture of mono formats with multichannel input.
Reimplemented the modulation stage for reverb.
Enabled real-time mixing priority by default, for backends that use the
setting. It can still be disabled in the config file.
Enabled dual-band processing for the built-in quad and 7.1 output decoders.
Fixed a potential crash when deleting an effect slot immediately after the
last source using it stops.
Fixed building with the static runtime on MSVC.
Fixed using source stereo angles outside of -pi...+pi.
Fixed the buffer processed event count for sources that start with empty
buffers.
Fixed trying to open an unopenable WASAPI device causing all devices to
stop working.
Fixed stale devices when re-enumerating WASAPI devices.
Fixed using unicode paths with the log file on Windows.
Fixed DirectSound capture reporting bad sample counts or erroring when
reading samples.
Added an in-progress extension for a callback-driven buffer type.
Added an in-progress extension for higher-order B-Format buffers.
Added an in-progress extension for convolution reverb.
Added an experimental Oboe backend for Android playback. This requires the
Oboe sources at build time, so that it's built as a static library included
in libopenal.
Added an option for auto-connecting JACK ports.
Added greater-than-stereo support to the SoundIO backend.
Modified the mixer to be fully asynchronous with the external API, and
should now be real-time safe. Although alcRenderSamplesSOFT is not due to
locking to check the device handle validity.
Modified the UHJ encoder to use an all-pass FIR filter that's less harmful
to non-filtered signal phase.
Converted examples from SDL_sound to libsndfile. To avoid issues when
combining SDL2 and SDL_sound.
Worked around a 32-bit GCC/MinGW bug with TLS destructors. See:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83562
Reduced the maximum number of source sends from 16 to 6.
Removed the QSA backend. It's been broken for who knows how long.
Got rid of the compile-time native-tools targets, using cmake and global
initialization instead. This should make cross-compiling less troublesome.
openal-soft-1.20.1:
Implemented the AL_SOFT_direct_channels_remix extension. This extends
AL_DIRECT_CHANNELS_SOFT to optionally remix input channels that don't have
a matching output channel.
Implemented the AL_SOFT_bformat_ex extension. This extends B-Format buffer
support for N3D or SN3D scaling, or ACN channel ordering.
Fixed a potential voice leak when a source is started and stopped or
restarted in quick succession.
Fixed a potential device reset failure with JACK.
Improved handling of unsupported channel configurations with WASAPI. Such
setups will now try to output at least a stereo mix.
Improved clarity a bit for the HRTF second-order ambisonic decoder.
Improved detection of compatible layouts for SOFA files in makemhr and
sofa-info.
Added the ability to resample HRTFs on load. MHR files no longer need to
match the device sample rate to be usable.
Added an option to limit the HRTF's filter length.
openal-soft-1.20.0:
Converted the library codebase to C++11. A lot of hacks and custom
structures have been replaced with standard or cleaner implementations.
Partially implemented the Vocal Morpher effect.
Fixed the bsinc SSE resamplers on non-GCC compilers.
Fixed OpenSL capture.
Fixed support for extended capture formats with OpenSL.
Fixed handling of WASAPI not reporting a default device.
Fixed performance problems relating to semaphores on macOS.
Modified the bsinc12 resampler's transition band to better avoid aliasing
noise.
Modified alcResetDeviceSOFT to attempt recovery of disconnected devices.
Modified the virtual speaker layout for HRTF B-Format decoding.
Modified the PulseAudio backend to use a custom processing loop.
Renamed the makehrtf utility to makemhr.
Improved the efficiency of the bsinc resamplers when up-sampling.
Improved the quality of the bsinc resamplers slightly.
Improved the efficiency of the HRTF filters.
Improved the HRTF B-Format decoder coefficient generation.
Improved reverb feedback fading to be more consistent with pan fading.
Improved handling of sources that end prematurely, avoiding loud clicks.
Improved the performance of some reverb processing loops.
Added fast_bsinc12 and 24 resamplers that improve efficiency at the cost of
some quality. Notably, down-sampling has less smooth pitch ramping.
Added support for SOFA input files with makemhr.
Added a build option to use pre-built native tools. For cross-compiling,
use with caution and ensure the native tools' binaries are kept up-to-date.
Added an adjust-latency config option for the PulseAudio backend.
Added basic support for multi-field HRTFs.
Added an option for mixing first- or second-order B-Format with HRTF
output. This can improve HRTF performance given a number of sources.
Added an RC file for proper DLL version information.
Disabled some old KDE workarounds by default. Specifically, PulseAudio
streams can now be moved (KDE may try to move them after opening).
openal-soft-1.19.1:
Implemented capture support for the SoundIO backend.

+ 0
- 100
modules/openal-soft/OpenAL32/Include/alAuxEffectSlot.h View File

@ -1,100 +0,0 @@
#ifndef _AL_AUXEFFECTSLOT_H_
#define _AL_AUXEFFECTSLOT_H_
#include <array>
#include "alMain.h"
#include "alEffect.h"
#include "ambidefs.h"
#include "effects/base.h"
#include "almalloc.h"
#include "atomic.h"
struct ALeffectslot;
using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
struct ALeffectslotProps {
ALfloat Gain;
ALboolean AuxSendAuto;
ALeffectslot *Target;
ALenum Type;
EffectProps Props;
EffectState *State;
std::atomic<ALeffectslotProps*> next;
};
struct ALeffectslot {
ALfloat Gain{1.0f};
ALboolean AuxSendAuto{AL_TRUE};
ALeffectslot *Target{nullptr};
struct {
ALenum Type{AL_EFFECT_NULL};
EffectProps Props{};
EffectState *State{nullptr};
} Effect;
std::atomic_flag PropsClean;
RefCount ref{0u};
std::atomic<ALeffectslotProps*> Update{nullptr};
struct {
ALfloat Gain{1.0f};
ALboolean AuxSendAuto{AL_TRUE};
ALeffectslot *Target{nullptr};
ALenum EffectType{AL_EFFECT_NULL};
EffectProps mEffectProps{};
EffectState *mEffectState{nullptr};
ALfloat RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */
ALfloat DecayTime{0.0f};
ALfloat DecayLFRatio{0.0f};
ALfloat DecayHFRatio{0.0f};
ALboolean DecayHFLimit{AL_FALSE};
ALfloat AirAbsorptionGainHF{1.0f};
} Params;
/* Self ID */
ALuint id{};
/* Mixing buffer used by the Wet mix. */
al::vector<std::array<ALfloat,BUFFERSIZE>,16> MixBuffer;
/* Wet buffer configuration is ACN channel order with N3D scaling.
* Consequently, effects that only want to work with mono input can use
* channel 0 by itself. Effects that want multichannel can process the
* ambisonics signal and make a B-Format source pan.
*/
MixParams Wet;
ALeffectslot() { PropsClean.test_and_set(std::memory_order_relaxed); }
ALeffectslot(const ALeffectslot&) = delete;
ALeffectslot& operator=(const ALeffectslot&) = delete;
~ALeffectslot();
static ALeffectslotArray *CreatePtrArray(size_t count) noexcept;
DEF_PLACE_NEWDEL()
};
ALenum InitEffectSlot(ALeffectslot *slot);
void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context);
void UpdateAllEffectSlotProps(ALCcontext *context);
ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect);
#endif

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

Loading…
Cancel
Save