Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1226aec2fc | |||
| 99a55c4452 | |||
| 14cf721094 | |||
| 515bc4ba53 | |||
| 25dfe4b57a | |||
| 9a4a3d3977 | |||
| 4fd0cd3151 |
+4
-8
@@ -1,9 +1,5 @@
|
||||
build*/
|
||||
winbuild
|
||||
win64build
|
||||
|
||||
## kdevelop
|
||||
*.kdev4
|
||||
|
||||
## qt-creator
|
||||
CMakeLists.txt.user*
|
||||
winbuild/
|
||||
win64build/
|
||||
openal-soft.kdev4
|
||||
.kdev4/
|
||||
|
||||
+15
-73
@@ -1,19 +1,13 @@
|
||||
language: cpp
|
||||
language: c
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: xenial
|
||||
dist: trusty
|
||||
- os: linux
|
||||
dist: trusty
|
||||
env:
|
||||
- BUILD_ANDROID=true
|
||||
- os: freebsd
|
||||
compiler: clang
|
||||
- os: osx
|
||||
- os: osx
|
||||
osx_image: xcode11
|
||||
env:
|
||||
- BUILD_IOS=true
|
||||
sudo: required
|
||||
install:
|
||||
- >
|
||||
@@ -30,44 +24,19 @@ install:
|
||||
fi
|
||||
- >
|
||||
if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
|
||||
curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip
|
||||
curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r15-linux-x86_64.zip
|
||||
unzip -q ~/android-ndk.zip -d ~ \
|
||||
'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
|
||||
'android-ndk-r15/build/cmake/*' \
|
||||
'android-ndk-r15/build/core/toolchains/arm-linux-androideabi-*/*' \
|
||||
'android-ndk-r15/platforms/android-14/arch-arm/*' \
|
||||
'android-ndk-r15/source.properties' \
|
||||
'android-ndk-r15/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/*' \
|
||||
'android-ndk-r15/sources/cxx-stl/gnu-libstdc++/4.9/include/*' \
|
||||
'android-ndk-r15/sysroot/*' \
|
||||
'android-ndk-r15/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \
|
||||
'android-ndk-r15/toolchains/llvm/prebuilt/linux-x86_64/*'
|
||||
fi
|
||||
script:
|
||||
- cmake --version
|
||||
- >
|
||||
if [[ "${TRAVIS_OS_NAME}" == "linux" && -z "${BUILD_ANDROID}" ]]; then
|
||||
cmake \
|
||||
@@ -82,43 +51,16 @@ script:
|
||||
- >
|
||||
if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
|
||||
cmake \
|
||||
-DANDROID_STL=c++_shared \
|
||||
-DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r21/build/cmake/android.toolchain.cmake \
|
||||
-DOBOE_SOURCE="$OBOE_LOC" \
|
||||
-DALSOFT_REQUIRE_OBOE=ON \
|
||||
-DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r15/build/cmake/android.toolchain.cmake \
|
||||
-DALSOFT_REQUIRE_OPENSL=ON \
|
||||
-DALSOFT_EMBED_HRTF_DATA=YES \
|
||||
.
|
||||
fi
|
||||
- >
|
||||
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
|
||||
if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; 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
|
||||
- cmake --build . --clean-first
|
||||
- make -j2
|
||||
|
||||
+698
@@ -0,0 +1,698 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef __MINGW32__
|
||||
#define _WIN32_IE 0x501
|
||||
#else
|
||||
#define _WIN32_IE 0x400
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32_IE
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#endif
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alconfig.h"
|
||||
#include "compat.h"
|
||||
#include "bool.h"
|
||||
|
||||
|
||||
typedef struct ConfigEntry {
|
||||
char *key;
|
||||
char *value;
|
||||
} ConfigEntry;
|
||||
|
||||
typedef struct ConfigBlock {
|
||||
ConfigEntry *entries;
|
||||
unsigned int entryCount;
|
||||
} ConfigBlock;
|
||||
static ConfigBlock cfgBlock;
|
||||
|
||||
|
||||
static char *lstrip(char *line)
|
||||
{
|
||||
while(isspace(line[0]))
|
||||
line++;
|
||||
return line;
|
||||
}
|
||||
|
||||
static char *rstrip(char *line)
|
||||
{
|
||||
size_t len = strlen(line);
|
||||
while(len > 0 && isspace(line[len-1]))
|
||||
len--;
|
||||
line[len] = 0;
|
||||
return line;
|
||||
}
|
||||
|
||||
static int readline(FILE *f, char **output, size_t *maxlen)
|
||||
{
|
||||
size_t len = 0;
|
||||
int c;
|
||||
|
||||
while((c=fgetc(f)) != EOF && (c == '\r' || c == '\n'))
|
||||
;
|
||||
if(c == EOF)
|
||||
return 0;
|
||||
|
||||
do {
|
||||
if(len+1 >= *maxlen)
|
||||
{
|
||||
void *temp = NULL;
|
||||
size_t newmax;
|
||||
|
||||
newmax = (*maxlen ? (*maxlen)<<1 : 32);
|
||||
if(newmax > *maxlen)
|
||||
temp = realloc(*output, newmax);
|
||||
if(!temp)
|
||||
{
|
||||
ERR("Failed to realloc "SZFMT" bytes from "SZFMT"!\n", newmax, *maxlen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*output = temp;
|
||||
*maxlen = newmax;
|
||||
}
|
||||
(*output)[len++] = c;
|
||||
(*output)[len] = '\0';
|
||||
} while((c=fgetc(f)) != EOF && c != '\r' && c != '\n');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static char *expdup(const char *str)
|
||||
{
|
||||
char *output = NULL;
|
||||
size_t maxlen = 0;
|
||||
size_t len = 0;
|
||||
|
||||
while(*str != '\0')
|
||||
{
|
||||
const char *addstr;
|
||||
size_t addstrlen;
|
||||
size_t i;
|
||||
|
||||
if(str[0] != '$')
|
||||
{
|
||||
const char *next = strchr(str, '$');
|
||||
addstr = str;
|
||||
addstrlen = next ? (size_t)(next-str) : strlen(str);
|
||||
|
||||
str += addstrlen;
|
||||
}
|
||||
else
|
||||
{
|
||||
str++;
|
||||
if(*str == '$')
|
||||
{
|
||||
const char *next = strchr(str+1, '$');
|
||||
addstr = str;
|
||||
addstrlen = next ? (size_t)(next-str) : strlen(str);
|
||||
|
||||
str += addstrlen;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool hasbraces;
|
||||
char envname[1024];
|
||||
size_t k = 0;
|
||||
|
||||
hasbraces = (*str == '{');
|
||||
if(hasbraces) str++;
|
||||
|
||||
while((isalnum(*str) || *str == '_') && k < sizeof(envname)-1)
|
||||
envname[k++] = *(str++);
|
||||
envname[k++] = '\0';
|
||||
|
||||
if(hasbraces && *str != '}')
|
||||
continue;
|
||||
|
||||
if(hasbraces) str++;
|
||||
if((addstr=getenv(envname)) == NULL)
|
||||
continue;
|
||||
addstrlen = strlen(addstr);
|
||||
}
|
||||
}
|
||||
if(addstrlen == 0)
|
||||
continue;
|
||||
|
||||
if(addstrlen >= maxlen-len)
|
||||
{
|
||||
void *temp = NULL;
|
||||
size_t newmax;
|
||||
|
||||
newmax = len+addstrlen+1;
|
||||
if(newmax > maxlen)
|
||||
temp = realloc(output, newmax);
|
||||
if(!temp)
|
||||
{
|
||||
ERR("Failed to realloc "SZFMT" bytes from "SZFMT"!\n", newmax, maxlen);
|
||||
return output;
|
||||
}
|
||||
|
||||
output = temp;
|
||||
maxlen = newmax;
|
||||
}
|
||||
|
||||
for(i = 0;i < addstrlen;i++)
|
||||
output[len++] = addstr[i];
|
||||
output[len] = '\0';
|
||||
}
|
||||
|
||||
return output ? output : calloc(1, 1);
|
||||
}
|
||||
|
||||
|
||||
static void LoadConfigFromFile(FILE *f)
|
||||
{
|
||||
char curSection[128] = "";
|
||||
char *buffer = NULL;
|
||||
size_t maxlen = 0;
|
||||
ConfigEntry *ent;
|
||||
|
||||
while(readline(f, &buffer, &maxlen))
|
||||
{
|
||||
char *line, *comment;
|
||||
char key[256] = "";
|
||||
char value[256] = "";
|
||||
|
||||
line = rstrip(lstrip(buffer));
|
||||
if(!line[0]) continue;
|
||||
|
||||
if(line[0] == '[')
|
||||
{
|
||||
char *section = line+1;
|
||||
char *endsection;
|
||||
|
||||
endsection = strchr(section, ']');
|
||||
if(!endsection || section == endsection)
|
||||
{
|
||||
ERR("config parse error: bad line \"%s\"\n", line);
|
||||
continue;
|
||||
}
|
||||
if(endsection[1] != 0)
|
||||
{
|
||||
char *end = endsection+1;
|
||||
while(isspace(*end))
|
||||
++end;
|
||||
if(*end != 0 && *end != '#')
|
||||
{
|
||||
ERR("config parse error: bad line \"%s\"\n", line);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*endsection = 0;
|
||||
|
||||
if(strcasecmp(section, "general") == 0)
|
||||
curSection[0] = 0;
|
||||
else
|
||||
{
|
||||
size_t len, p = 0;
|
||||
do {
|
||||
char *nextp = strchr(section, '%');
|
||||
if(!nextp)
|
||||
{
|
||||
strncpy(curSection+p, section, sizeof(curSection)-1-p);
|
||||
break;
|
||||
}
|
||||
|
||||
len = nextp - section;
|
||||
if(len > sizeof(curSection)-1-p)
|
||||
len = sizeof(curSection)-1-p;
|
||||
strncpy(curSection+p, section, len);
|
||||
p += len;
|
||||
section = nextp;
|
||||
|
||||
if(((section[1] >= '0' && section[1] <= '9') ||
|
||||
(section[1] >= 'a' && section[1] <= 'f') ||
|
||||
(section[1] >= 'A' && section[1] <= 'F')) &&
|
||||
((section[2] >= '0' && section[2] <= '9') ||
|
||||
(section[2] >= 'a' && section[2] <= 'f') ||
|
||||
(section[2] >= 'A' && section[2] <= 'F')))
|
||||
{
|
||||
unsigned char b = 0;
|
||||
if(section[1] >= '0' && section[1] <= '9')
|
||||
b = (section[1]-'0') << 4;
|
||||
else if(section[1] >= 'a' && section[1] <= 'f')
|
||||
b = (section[1]-'a'+0xa) << 4;
|
||||
else if(section[1] >= 'A' && section[1] <= 'F')
|
||||
b = (section[1]-'A'+0x0a) << 4;
|
||||
if(section[2] >= '0' && section[2] <= '9')
|
||||
b |= (section[2]-'0');
|
||||
else if(section[2] >= 'a' && section[2] <= 'f')
|
||||
b |= (section[2]-'a'+0xa);
|
||||
else if(section[2] >= 'A' && section[2] <= 'F')
|
||||
b |= (section[2]-'A'+0x0a);
|
||||
if(p < sizeof(curSection)-1)
|
||||
curSection[p++] = b;
|
||||
section += 3;
|
||||
}
|
||||
else if(section[1] == '%')
|
||||
{
|
||||
if(p < sizeof(curSection)-1)
|
||||
curSection[p++] = '%';
|
||||
section += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(p < sizeof(curSection)-1)
|
||||
curSection[p++] = '%';
|
||||
section += 1;
|
||||
}
|
||||
if(p < sizeof(curSection)-1)
|
||||
curSection[p] = 0;
|
||||
} while(p < sizeof(curSection)-1 && *section != 0);
|
||||
curSection[sizeof(curSection)-1] = 0;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
comment = strchr(line, '#');
|
||||
if(comment) *(comment++) = 0;
|
||||
if(!line[0]) continue;
|
||||
|
||||
if(sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 ||
|
||||
sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 ||
|
||||
sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2)
|
||||
{
|
||||
/* sscanf doesn't handle '' or "" as empty values, so clip it
|
||||
* manually. */
|
||||
if(strcmp(value, "\"\"") == 0 || strcmp(value, "''") == 0)
|
||||
value[0] = 0;
|
||||
}
|
||||
else if(sscanf(line, "%255[^=] %255[=]", key, value) == 2)
|
||||
{
|
||||
/* Special case for 'key =' */
|
||||
value[0] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("config parse error: malformed option line: \"%s\"\n\n", line);
|
||||
continue;
|
||||
}
|
||||
rstrip(key);
|
||||
|
||||
if(curSection[0] != 0)
|
||||
{
|
||||
size_t len = strlen(curSection);
|
||||
memmove(&key[len+1], key, sizeof(key)-1-len);
|
||||
key[len] = '/';
|
||||
memcpy(key, curSection, len);
|
||||
}
|
||||
|
||||
/* Check if we already have this option set */
|
||||
ent = cfgBlock.entries;
|
||||
while((unsigned int)(ent-cfgBlock.entries) < cfgBlock.entryCount)
|
||||
{
|
||||
if(strcasecmp(ent->key, key) == 0)
|
||||
break;
|
||||
ent++;
|
||||
}
|
||||
|
||||
if((unsigned int)(ent-cfgBlock.entries) >= cfgBlock.entryCount)
|
||||
{
|
||||
/* Allocate a new option entry */
|
||||
ent = realloc(cfgBlock.entries, (cfgBlock.entryCount+1)*sizeof(ConfigEntry));
|
||||
if(!ent)
|
||||
{
|
||||
ERR("config parse error: error reallocating config entries\n");
|
||||
continue;
|
||||
}
|
||||
cfgBlock.entries = ent;
|
||||
ent = cfgBlock.entries + cfgBlock.entryCount;
|
||||
cfgBlock.entryCount++;
|
||||
|
||||
ent->key = strdup(key);
|
||||
ent->value = NULL;
|
||||
}
|
||||
|
||||
free(ent->value);
|
||||
ent->value = expdup(value);
|
||||
|
||||
TRACE("found '%s' = '%s'\n", ent->key, ent->value);
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void ReadALConfig(void)
|
||||
{
|
||||
al_string ppath = AL_STRING_INIT_STATIC();
|
||||
WCHAR buffer[MAX_PATH];
|
||||
const WCHAR *str;
|
||||
FILE *f;
|
||||
|
||||
if(SHGetSpecialFolderPathW(NULL, buffer, CSIDL_APPDATA, FALSE) != FALSE)
|
||||
{
|
||||
al_string filepath = AL_STRING_INIT_STATIC();
|
||||
alstr_copy_wcstr(&filepath, buffer);
|
||||
alstr_append_cstr(&filepath, "\\alsoft.ini");
|
||||
|
||||
TRACE("Loading config %s...\n", alstr_get_cstr(filepath));
|
||||
f = al_fopen(alstr_get_cstr(filepath), "rt");
|
||||
if(f)
|
||||
{
|
||||
LoadConfigFromFile(f);
|
||||
fclose(f);
|
||||
}
|
||||
alstr_reset(&filepath);
|
||||
}
|
||||
|
||||
GetProcBinary(&ppath, NULL);
|
||||
if(!alstr_empty(ppath))
|
||||
{
|
||||
alstr_append_cstr(&ppath, "\\alsoft.ini");
|
||||
TRACE("Loading config %s...\n", alstr_get_cstr(ppath));
|
||||
f = al_fopen(alstr_get_cstr(ppath), "r");
|
||||
if(f)
|
||||
{
|
||||
LoadConfigFromFile(f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
if((str=_wgetenv(L"ALSOFT_CONF")) != NULL && *str)
|
||||
{
|
||||
al_string filepath = AL_STRING_INIT_STATIC();
|
||||
alstr_copy_wcstr(&filepath, str);
|
||||
|
||||
TRACE("Loading config %s...\n", alstr_get_cstr(filepath));
|
||||
f = al_fopen(alstr_get_cstr(filepath), "rt");
|
||||
if(f)
|
||||
{
|
||||
LoadConfigFromFile(f);
|
||||
fclose(f);
|
||||
}
|
||||
alstr_reset(&filepath);
|
||||
}
|
||||
|
||||
alstr_reset(&ppath);
|
||||
}
|
||||
#else
|
||||
void ReadALConfig(void)
|
||||
{
|
||||
al_string confpaths = AL_STRING_INIT_STATIC();
|
||||
al_string fname = AL_STRING_INIT_STATIC();
|
||||
const char *str;
|
||||
FILE *f;
|
||||
|
||||
str = "/etc/openal/alsoft.conf";
|
||||
|
||||
TRACE("Loading config %s...\n", str);
|
||||
f = al_fopen(str, "r");
|
||||
if(f)
|
||||
{
|
||||
LoadConfigFromFile(f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
if(!(str=getenv("XDG_CONFIG_DIRS")) || str[0] == 0)
|
||||
str = "/etc/xdg";
|
||||
alstr_copy_cstr(&confpaths, str);
|
||||
/* 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
|
||||
* first so that the settings in the earlier dirs override them.
|
||||
*/
|
||||
while(!alstr_empty(confpaths))
|
||||
{
|
||||
char *next = strrchr(alstr_get_cstr(confpaths), ':');
|
||||
if(next)
|
||||
{
|
||||
size_t len = next - alstr_get_cstr(confpaths);
|
||||
alstr_copy_cstr(&fname, next+1);
|
||||
VECTOR_RESIZE(confpaths, len, len+1);
|
||||
VECTOR_ELEM(confpaths, len) = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
alstr_reset(&fname);
|
||||
fname = confpaths;
|
||||
AL_STRING_INIT(confpaths);
|
||||
}
|
||||
|
||||
if(alstr_empty(fname) || VECTOR_FRONT(fname) != '/')
|
||||
WARN("Ignoring XDG config dir: %s\n", alstr_get_cstr(fname));
|
||||
else
|
||||
{
|
||||
if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/alsoft.conf");
|
||||
else alstr_append_cstr(&fname, "alsoft.conf");
|
||||
|
||||
TRACE("Loading config %s...\n", alstr_get_cstr(fname));
|
||||
f = al_fopen(alstr_get_cstr(fname), "r");
|
||||
if(f)
|
||||
{
|
||||
LoadConfigFromFile(f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
alstr_clear(&fname);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
CFBundleRef mainBundle = CFBundleGetMainBundle();
|
||||
if(mainBundle)
|
||||
{
|
||||
unsigned char fileName[PATH_MAX];
|
||||
CFURLRef configURL;
|
||||
|
||||
if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), NULL)) &&
|
||||
CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
|
||||
{
|
||||
f = al_fopen((const char*)fileName, "r");
|
||||
if(f)
|
||||
{
|
||||
LoadConfigFromFile(f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if((str=getenv("HOME")) != NULL && *str)
|
||||
{
|
||||
alstr_copy_cstr(&fname, str);
|
||||
if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/.alsoftrc");
|
||||
else alstr_append_cstr(&fname, ".alsoftrc");
|
||||
|
||||
TRACE("Loading config %s...\n", alstr_get_cstr(fname));
|
||||
f = al_fopen(alstr_get_cstr(fname), "r");
|
||||
if(f)
|
||||
{
|
||||
LoadConfigFromFile(f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
if((str=getenv("XDG_CONFIG_HOME")) != NULL && str[0] != 0)
|
||||
{
|
||||
alstr_copy_cstr(&fname, str);
|
||||
if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/alsoft.conf");
|
||||
else alstr_append_cstr(&fname, "alsoft.conf");
|
||||
}
|
||||
else
|
||||
{
|
||||
alstr_clear(&fname);
|
||||
if((str=getenv("HOME")) != NULL && str[0] != 0)
|
||||
{
|
||||
alstr_copy_cstr(&fname, str);
|
||||
if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/.config/alsoft.conf");
|
||||
else alstr_append_cstr(&fname, ".config/alsoft.conf");
|
||||
}
|
||||
}
|
||||
if(!alstr_empty(fname))
|
||||
{
|
||||
TRACE("Loading config %s...\n", alstr_get_cstr(fname));
|
||||
f = al_fopen(alstr_get_cstr(fname), "r");
|
||||
if(f)
|
||||
{
|
||||
LoadConfigFromFile(f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
alstr_clear(&fname);
|
||||
GetProcBinary(&fname, NULL);
|
||||
if(!alstr_empty(fname))
|
||||
{
|
||||
if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/alsoft.conf");
|
||||
else alstr_append_cstr(&fname, "alsoft.conf");
|
||||
|
||||
TRACE("Loading config %s...\n", alstr_get_cstr(fname));
|
||||
f = al_fopen(alstr_get_cstr(fname), "r");
|
||||
if(f)
|
||||
{
|
||||
LoadConfigFromFile(f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
if((str=getenv("ALSOFT_CONF")) != NULL && *str)
|
||||
{
|
||||
TRACE("Loading config %s...\n", str);
|
||||
f = al_fopen(str, "r");
|
||||
if(f)
|
||||
{
|
||||
LoadConfigFromFile(f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
alstr_reset(&fname);
|
||||
alstr_reset(&confpaths);
|
||||
}
|
||||
#endif
|
||||
|
||||
void FreeALConfig(void)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for(i = 0;i < cfgBlock.entryCount;i++)
|
||||
{
|
||||
free(cfgBlock.entries[i].key);
|
||||
free(cfgBlock.entries[i].value);
|
||||
}
|
||||
free(cfgBlock.entries);
|
||||
}
|
||||
|
||||
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
|
||||
{
|
||||
unsigned int i;
|
||||
char key[256];
|
||||
|
||||
if(!keyName)
|
||||
return def;
|
||||
|
||||
if(blockName && strcasecmp(blockName, "general") != 0)
|
||||
{
|
||||
if(devName)
|
||||
snprintf(key, sizeof(key), "%s/%s/%s", blockName, devName, keyName);
|
||||
else
|
||||
snprintf(key, sizeof(key), "%s/%s", blockName, keyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(devName)
|
||||
snprintf(key, sizeof(key), "%s/%s", devName, keyName);
|
||||
else
|
||||
{
|
||||
strncpy(key, keyName, sizeof(key)-1);
|
||||
key[sizeof(key)-1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0;i < cfgBlock.entryCount;i++)
|
||||
{
|
||||
if(strcmp(cfgBlock.entries[i].key, key) == 0)
|
||||
{
|
||||
TRACE("Found %s = \"%s\"\n", key, cfgBlock.entries[i].value);
|
||||
if(cfgBlock.entries[i].value[0])
|
||||
return cfgBlock.entries[i].value;
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
if(!devName)
|
||||
{
|
||||
TRACE("Key %s not found\n", key);
|
||||
return def;
|
||||
}
|
||||
return GetConfigValue(NULL, blockName, keyName, def);
|
||||
}
|
||||
|
||||
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
return !!val[0];
|
||||
}
|
||||
|
||||
int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return 0;
|
||||
|
||||
*ret = val;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return 0;
|
||||
|
||||
*ret = strtol(val, NULL, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return 0;
|
||||
|
||||
*ret = strtoul(val, NULL, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return 0;
|
||||
|
||||
#ifdef HAVE_STRTOF
|
||||
*ret = strtof(val, NULL);
|
||||
#else
|
||||
*ret = (float)strtod(val, NULL);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret)
|
||||
{
|
||||
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;
|
||||
return (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
|
||||
strcasecmp(val, "on") == 0 || atoi(val) != 0);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef ALCONFIG_H
|
||||
#define ALCONFIG_H
|
||||
|
||||
void ReadALConfig(void);
|
||||
void FreeALConfig(void);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
#endif /* ALCONFIG_H */
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef ALSTRING_H
|
||||
#define ALSTRING_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef char al_string_char_type;
|
||||
TYPEDEF_VECTOR(al_string_char_type, al_string)
|
||||
TYPEDEF_VECTOR(al_string, vector_al_string)
|
||||
|
||||
inline void alstr_reset(al_string *str)
|
||||
{ VECTOR_DEINIT(*str); }
|
||||
#define AL_STRING_INIT(_x) do { (_x) = (al_string)NULL; } while(0)
|
||||
#define AL_STRING_INIT_STATIC() ((al_string)NULL)
|
||||
#define AL_STRING_DEINIT(_x) alstr_reset(&(_x))
|
||||
|
||||
inline size_t alstr_length(const_al_string str)
|
||||
{ return VECTOR_SIZE(str); }
|
||||
|
||||
inline ALboolean alstr_empty(const_al_string str)
|
||||
{ return alstr_length(str) == 0; }
|
||||
|
||||
inline const al_string_char_type *alstr_get_cstr(const_al_string str)
|
||||
{ return str ? &VECTOR_FRONT(str) : ""; }
|
||||
|
||||
void alstr_clear(al_string *str);
|
||||
|
||||
int alstr_cmp(const_al_string str1, const_al_string str2);
|
||||
int alstr_cmp_cstr(const_al_string str1, const al_string_char_type *str2);
|
||||
|
||||
void alstr_copy(al_string *str, const_al_string from);
|
||||
void alstr_copy_cstr(al_string *str, const al_string_char_type *from);
|
||||
void alstr_copy_range(al_string *str, const al_string_char_type *from, const al_string_char_type *to);
|
||||
|
||||
void alstr_append_char(al_string *str, const al_string_char_type c);
|
||||
void alstr_append_cstr(al_string *str, const al_string_char_type *from);
|
||||
void alstr_append_range(al_string *str, const al_string_char_type *from, const al_string_char_type *to);
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <wchar.h>
|
||||
/* Windows-only methods to deal with WideChar strings. */
|
||||
void alstr_copy_wcstr(al_string *str, const wchar_t *from);
|
||||
void alstr_append_wcstr(al_string *str, const wchar_t *from);
|
||||
void alstr_copy_wrange(al_string *str, const wchar_t *from, const wchar_t *to);
|
||||
void alstr_append_wrange(al_string *str, const wchar_t *from, const wchar_t *to);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* ALSTRING_H */
|
||||
+566
@@ -0,0 +1,566 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ambdec.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
|
||||
static char *lstrip(char *line)
|
||||
{
|
||||
while(isspace(line[0]))
|
||||
line++;
|
||||
return line;
|
||||
}
|
||||
|
||||
static char *rstrip(char *line)
|
||||
{
|
||||
size_t len = strlen(line);
|
||||
while(len > 0 && isspace(line[len-1]))
|
||||
len--;
|
||||
line[len] = 0;
|
||||
return line;
|
||||
}
|
||||
|
||||
static int readline(FILE *f, char **output, size_t *maxlen)
|
||||
{
|
||||
size_t len = 0;
|
||||
int c;
|
||||
|
||||
while((c=fgetc(f)) != EOF && (c == '\r' || c == '\n'))
|
||||
;
|
||||
if(c == EOF)
|
||||
return 0;
|
||||
|
||||
do {
|
||||
if(len+1 >= *maxlen)
|
||||
{
|
||||
void *temp = NULL;
|
||||
size_t newmax;
|
||||
|
||||
newmax = (*maxlen ? (*maxlen)<<1 : 32);
|
||||
if(newmax > *maxlen)
|
||||
temp = realloc(*output, newmax);
|
||||
if(!temp)
|
||||
{
|
||||
ERR("Failed to realloc "SZFMT" bytes from "SZFMT"!\n", newmax, *maxlen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*output = temp;
|
||||
*maxlen = newmax;
|
||||
}
|
||||
(*output)[len++] = c;
|
||||
(*output)[len] = '\0';
|
||||
} while((c=fgetc(f)) != EOF && c != '\r' && c != '\n');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Custom strtok_r, since we can't rely on it existing. */
|
||||
static char *my_strtok_r(char *str, const char *delim, char **saveptr)
|
||||
{
|
||||
/* Sanity check and update internal pointer. */
|
||||
if(!saveptr || !delim) return NULL;
|
||||
if(str) *saveptr = str;
|
||||
str = *saveptr;
|
||||
|
||||
/* Nothing more to do with this string. */
|
||||
if(!str) return NULL;
|
||||
|
||||
/* Find the first non-delimiter character. */
|
||||
while(*str != '\0' && strchr(delim, *str) != NULL)
|
||||
str++;
|
||||
if(*str == '\0')
|
||||
{
|
||||
/* End of string. */
|
||||
*saveptr = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find the next delimiter character. */
|
||||
*saveptr = strpbrk(str, delim);
|
||||
if(*saveptr) *((*saveptr)++) = '\0';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *read_int(ALint *num, const char *line, int base)
|
||||
{
|
||||
char *end;
|
||||
*num = strtol(line, &end, base);
|
||||
if(end && *end != '\0')
|
||||
end = lstrip(end);
|
||||
return end;
|
||||
}
|
||||
|
||||
static char *read_uint(ALuint *num, const char *line, int base)
|
||||
{
|
||||
char *end;
|
||||
*num = strtoul(line, &end, base);
|
||||
if(end && *end != '\0')
|
||||
end = lstrip(end);
|
||||
return end;
|
||||
}
|
||||
|
||||
static char *read_float(ALfloat *num, const char *line)
|
||||
{
|
||||
char *end;
|
||||
#ifdef HAVE_STRTOF
|
||||
*num = strtof(line, &end);
|
||||
#else
|
||||
*num = (ALfloat)strtod(line, &end);
|
||||
#endif
|
||||
if(end && *end != '\0')
|
||||
end = lstrip(end);
|
||||
return end;
|
||||
}
|
||||
|
||||
|
||||
char *read_clipped_line(FILE *f, char **buffer, size_t *maxlen)
|
||||
{
|
||||
while(readline(f, buffer, maxlen))
|
||||
{
|
||||
char *line, *comment;
|
||||
|
||||
line = lstrip(*buffer);
|
||||
comment = strchr(line, '#');
|
||||
if(comment) *(comment++) = 0;
|
||||
|
||||
line = rstrip(line);
|
||||
if(line[0]) return line;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int load_ambdec_speakers(AmbDecConf *conf, FILE *f, char **buffer, size_t *maxlen, char **saveptr)
|
||||
{
|
||||
ALsizei cur = 0;
|
||||
while(cur < conf->NumSpeakers)
|
||||
{
|
||||
const char *cmd = my_strtok_r(NULL, " \t", saveptr);
|
||||
if(!cmd)
|
||||
{
|
||||
char *line = read_clipped_line(f, buffer, maxlen);
|
||||
if(!line)
|
||||
{
|
||||
ERR("Unexpected end of file\n");
|
||||
return 0;
|
||||
}
|
||||
cmd = my_strtok_r(line, " \t", saveptr);
|
||||
}
|
||||
|
||||
if(strcmp(cmd, "add_spkr") == 0)
|
||||
{
|
||||
const char *name = my_strtok_r(NULL, " \t", saveptr);
|
||||
const char *dist = my_strtok_r(NULL, " \t", saveptr);
|
||||
const char *az = my_strtok_r(NULL, " \t", saveptr);
|
||||
const char *elev = my_strtok_r(NULL, " \t", saveptr);
|
||||
const char *conn = my_strtok_r(NULL, " \t", saveptr);
|
||||
|
||||
if(!name) WARN("Name not specified for speaker %u\n", cur+1);
|
||||
else alstr_copy_cstr(&conf->Speakers[cur].Name, name);
|
||||
if(!dist) WARN("Distance not specified for speaker %u\n", cur+1);
|
||||
else read_float(&conf->Speakers[cur].Distance, dist);
|
||||
if(!az) WARN("Azimuth not specified for speaker %u\n", cur+1);
|
||||
else read_float(&conf->Speakers[cur].Azimuth, az);
|
||||
if(!elev) WARN("Elevation not specified for speaker %u\n", cur+1);
|
||||
else read_float(&conf->Speakers[cur].Elevation, elev);
|
||||
if(!conn) TRACE("Connection not specified for speaker %u\n", cur+1);
|
||||
else alstr_copy_cstr(&conf->Speakers[cur].Connection, conn);
|
||||
|
||||
cur++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unexpected speakers command: %s\n", cmd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cmd = my_strtok_r(NULL, " \t", saveptr);
|
||||
if(cmd)
|
||||
{
|
||||
ERR("Unexpected junk on line: %s\n", cmd);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int load_ambdec_matrix(ALfloat *gains, ALfloat (*matrix)[MAX_AMBI_COEFFS], ALsizei maxrow, FILE *f, char **buffer, size_t *maxlen, char **saveptr)
|
||||
{
|
||||
int gotgains = 0;
|
||||
ALsizei cur = 0;
|
||||
while(cur < maxrow)
|
||||
{
|
||||
const char *cmd = my_strtok_r(NULL, " \t", saveptr);
|
||||
if(!cmd)
|
||||
{
|
||||
char *line = read_clipped_line(f, buffer, maxlen);
|
||||
if(!line)
|
||||
{
|
||||
ERR("Unexpected end of file\n");
|
||||
return 0;
|
||||
}
|
||||
cmd = my_strtok_r(line, " \t", saveptr);
|
||||
}
|
||||
|
||||
if(strcmp(cmd, "order_gain") == 0)
|
||||
{
|
||||
ALuint curgain = 0;
|
||||
char *line;
|
||||
while((line=my_strtok_r(NULL, " \t", saveptr)) != NULL)
|
||||
{
|
||||
ALfloat value;
|
||||
line = read_float(&value, line);
|
||||
if(line && *line != '\0')
|
||||
{
|
||||
ERR("Extra junk on gain %u: %s\n", curgain+1, line);
|
||||
return 0;
|
||||
}
|
||||
if(curgain < MAX_AMBI_ORDER+1)
|
||||
gains[curgain] = value;
|
||||
curgain++;
|
||||
}
|
||||
while(curgain < MAX_AMBI_ORDER+1)
|
||||
gains[curgain++] = 0.0f;
|
||||
gotgains = 1;
|
||||
}
|
||||
else if(strcmp(cmd, "add_row") == 0)
|
||||
{
|
||||
ALuint curidx = 0;
|
||||
char *line;
|
||||
while((line=my_strtok_r(NULL, " \t", saveptr)) != NULL)
|
||||
{
|
||||
ALfloat value;
|
||||
line = read_float(&value, line);
|
||||
if(line && *line != '\0')
|
||||
{
|
||||
ERR("Extra junk on matrix element %ux%u: %s\n", cur, curidx, line);
|
||||
return 0;
|
||||
}
|
||||
if(curidx < MAX_AMBI_COEFFS)
|
||||
matrix[cur][curidx] = value;
|
||||
curidx++;
|
||||
}
|
||||
while(curidx < MAX_AMBI_COEFFS)
|
||||
matrix[cur][curidx++] = 0.0f;
|
||||
cur++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unexpected speakers command: %s\n", cmd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cmd = my_strtok_r(NULL, " \t", saveptr);
|
||||
if(cmd)
|
||||
{
|
||||
ERR("Unexpected junk on line: %s\n", cmd);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(!gotgains)
|
||||
{
|
||||
ERR("Matrix order_gain not specified\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ambdec_init(AmbDecConf *conf)
|
||||
{
|
||||
ALsizei i;
|
||||
|
||||
memset(conf, 0, sizeof(*conf));
|
||||
AL_STRING_INIT(conf->Description);
|
||||
for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
|
||||
{
|
||||
AL_STRING_INIT(conf->Speakers[i].Name);
|
||||
AL_STRING_INIT(conf->Speakers[i].Connection);
|
||||
}
|
||||
}
|
||||
|
||||
void ambdec_deinit(AmbDecConf *conf)
|
||||
{
|
||||
ALsizei i;
|
||||
|
||||
alstr_reset(&conf->Description);
|
||||
for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
|
||||
{
|
||||
alstr_reset(&conf->Speakers[i].Name);
|
||||
alstr_reset(&conf->Speakers[i].Connection);
|
||||
}
|
||||
memset(conf, 0, sizeof(*conf));
|
||||
}
|
||||
|
||||
int ambdec_load(AmbDecConf *conf, const char *fname)
|
||||
{
|
||||
char *buffer = NULL;
|
||||
size_t maxlen = 0;
|
||||
char *line;
|
||||
FILE *f;
|
||||
|
||||
f = al_fopen(fname, "r");
|
||||
if(!f)
|
||||
{
|
||||
ERR("Failed to open: %s\n", fname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
while((line=read_clipped_line(f, &buffer, &maxlen)) != NULL)
|
||||
{
|
||||
char *saveptr;
|
||||
char *command;
|
||||
|
||||
command = my_strtok_r(line, "/ \t", &saveptr);
|
||||
if(!command)
|
||||
{
|
||||
ERR("Malformed line: %s\n", line);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if(strcmp(command, "description") == 0)
|
||||
{
|
||||
char *value = my_strtok_r(NULL, "", &saveptr);
|
||||
alstr_copy_cstr(&conf->Description, lstrip(value));
|
||||
}
|
||||
else if(strcmp(command, "version") == 0)
|
||||
{
|
||||
line = my_strtok_r(NULL, "", &saveptr);
|
||||
line = read_uint(&conf->Version, line, 10);
|
||||
if(line && *line != '\0')
|
||||
{
|
||||
ERR("Extra junk after version: %s\n", line);
|
||||
goto fail;
|
||||
}
|
||||
if(conf->Version != 3)
|
||||
{
|
||||
ERR("Unsupported version: %u\n", conf->Version);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(strcmp(command, "dec") == 0)
|
||||
{
|
||||
const char *dec = my_strtok_r(NULL, "/ \t", &saveptr);
|
||||
if(strcmp(dec, "chan_mask") == 0)
|
||||
{
|
||||
line = my_strtok_r(NULL, "", &saveptr);
|
||||
line = read_uint(&conf->ChanMask, line, 16);
|
||||
if(line && *line != '\0')
|
||||
{
|
||||
ERR("Extra junk after mask: %s\n", line);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(strcmp(dec, "freq_bands") == 0)
|
||||
{
|
||||
line = my_strtok_r(NULL, "", &saveptr);
|
||||
line = read_uint(&conf->FreqBands, line, 10);
|
||||
if(line && *line != '\0')
|
||||
{
|
||||
ERR("Extra junk after freq_bands: %s\n", line);
|
||||
goto fail;
|
||||
}
|
||||
if(conf->FreqBands != 1 && conf->FreqBands != 2)
|
||||
{
|
||||
ERR("Invalid freq_bands value: %u\n", conf->FreqBands);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(strcmp(dec, "speakers") == 0)
|
||||
{
|
||||
line = my_strtok_r(NULL, "", &saveptr);
|
||||
line = read_int(&conf->NumSpeakers, line, 10);
|
||||
if(line && *line != '\0')
|
||||
{
|
||||
ERR("Extra junk after speakers: %s\n", line);
|
||||
goto fail;
|
||||
}
|
||||
if(conf->NumSpeakers > MAX_OUTPUT_CHANNELS)
|
||||
{
|
||||
ERR("Unsupported speaker count: %u\n", conf->NumSpeakers);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(strcmp(dec, "coeff_scale") == 0)
|
||||
{
|
||||
line = my_strtok_r(NULL, " \t", &saveptr);
|
||||
if(strcmp(line, "n3d") == 0)
|
||||
conf->CoeffScale = ADS_N3D;
|
||||
else if(strcmp(line, "sn3d") == 0)
|
||||
conf->CoeffScale = ADS_SN3D;
|
||||
else if(strcmp(line, "fuma") == 0)
|
||||
conf->CoeffScale = ADS_FuMa;
|
||||
else
|
||||
{
|
||||
ERR("Unsupported coeff scale: %s\n", line);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unexpected /dec option: %s\n", dec);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(strcmp(command, "opt") == 0)
|
||||
{
|
||||
const char *opt = my_strtok_r(NULL, "/ \t", &saveptr);
|
||||
if(strcmp(opt, "xover_freq") == 0)
|
||||
{
|
||||
line = my_strtok_r(NULL, "", &saveptr);
|
||||
line = read_float(&conf->XOverFreq, line);
|
||||
if(line && *line != '\0')
|
||||
{
|
||||
ERR("Extra junk after xover_freq: %s\n", line);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(strcmp(opt, "xover_ratio") == 0)
|
||||
{
|
||||
line = my_strtok_r(NULL, "", &saveptr);
|
||||
line = read_float(&conf->XOverRatio, line);
|
||||
if(line && *line != '\0')
|
||||
{
|
||||
ERR("Extra junk after xover_ratio: %s\n", line);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(strcmp(opt, "input_scale") == 0 || strcmp(opt, "nfeff_comp") == 0 ||
|
||||
strcmp(opt, "delay_comp") == 0 || strcmp(opt, "level_comp") == 0)
|
||||
{
|
||||
/* Unused */
|
||||
my_strtok_r(NULL, " \t", &saveptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unexpected /opt option: %s\n", opt);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(strcmp(command, "speakers") == 0)
|
||||
{
|
||||
const char *value = my_strtok_r(NULL, "/ \t", &saveptr);
|
||||
if(strcmp(value, "{") != 0)
|
||||
{
|
||||
ERR("Expected { after %s command, got %s\n", command, value);
|
||||
goto fail;
|
||||
}
|
||||
if(!load_ambdec_speakers(conf, f, &buffer, &maxlen, &saveptr))
|
||||
goto fail;
|
||||
value = my_strtok_r(NULL, "/ \t", &saveptr);
|
||||
if(!value)
|
||||
{
|
||||
line = read_clipped_line(f, &buffer, &maxlen);
|
||||
if(!line)
|
||||
{
|
||||
ERR("Unexpected end of file\n");
|
||||
goto fail;
|
||||
}
|
||||
value = my_strtok_r(line, "/ \t", &saveptr);
|
||||
}
|
||||
if(strcmp(value, "}") != 0)
|
||||
{
|
||||
ERR("Expected } after speaker definitions, got %s\n", value);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(strcmp(command, "lfmatrix") == 0 || strcmp(command, "hfmatrix") == 0 ||
|
||||
strcmp(command, "matrix") == 0)
|
||||
{
|
||||
const char *value = my_strtok_r(NULL, "/ \t", &saveptr);
|
||||
if(strcmp(value, "{") != 0)
|
||||
{
|
||||
ERR("Expected { after %s command, got %s\n", command, value);
|
||||
goto fail;
|
||||
}
|
||||
if(conf->FreqBands == 1)
|
||||
{
|
||||
if(strcmp(command, "matrix") != 0)
|
||||
{
|
||||
ERR("Unexpected \"%s\" type for a single-band decoder\n", command);
|
||||
goto fail;
|
||||
}
|
||||
if(!load_ambdec_matrix(conf->HFOrderGain, conf->HFMatrix, conf->NumSpeakers,
|
||||
f, &buffer, &maxlen, &saveptr))
|
||||
goto fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(strcmp(command, "lfmatrix") == 0)
|
||||
{
|
||||
if(!load_ambdec_matrix(conf->LFOrderGain, conf->LFMatrix, conf->NumSpeakers,
|
||||
f, &buffer, &maxlen, &saveptr))
|
||||
goto fail;
|
||||
}
|
||||
else if(strcmp(command, "hfmatrix") == 0)
|
||||
{
|
||||
if(!load_ambdec_matrix(conf->HFOrderGain, conf->HFMatrix, conf->NumSpeakers,
|
||||
f, &buffer, &maxlen, &saveptr))
|
||||
goto fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unexpected \"%s\" type for a dual-band decoder\n", command);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
value = my_strtok_r(NULL, "/ \t", &saveptr);
|
||||
if(!value)
|
||||
{
|
||||
line = read_clipped_line(f, &buffer, &maxlen);
|
||||
if(!line)
|
||||
{
|
||||
ERR("Unexpected end of file\n");
|
||||
goto fail;
|
||||
}
|
||||
value = my_strtok_r(line, "/ \t", &saveptr);
|
||||
}
|
||||
if(strcmp(value, "}") != 0)
|
||||
{
|
||||
ERR("Expected } after matrix definitions, got %s\n", value);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(strcmp(command, "end") == 0)
|
||||
{
|
||||
line = my_strtok_r(NULL, "/ \t", &saveptr);
|
||||
if(line)
|
||||
{
|
||||
ERR("Unexpected junk on end: %s\n", line);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
free(buffer);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unexpected command: %s\n", command);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
line = my_strtok_r(NULL, "/ \t", &saveptr);
|
||||
if(line)
|
||||
{
|
||||
ERR("Unexpected junk on line: %s\n", line);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
ERR("Unexpected end of file\n");
|
||||
|
||||
fail:
|
||||
fclose(f);
|
||||
free(buffer);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef AMBDEC_H
|
||||
#define AMBDEC_H
|
||||
|
||||
#include "alstring.h"
|
||||
#include "alMain.h"
|
||||
|
||||
/* Helpers to read .ambdec configuration files. */
|
||||
|
||||
enum AmbDecScaleType {
|
||||
ADS_N3D,
|
||||
ADS_SN3D,
|
||||
ADS_FuMa,
|
||||
};
|
||||
typedef struct AmbDecConf {
|
||||
al_string Description;
|
||||
ALuint Version; /* Must be 3 */
|
||||
|
||||
ALuint ChanMask;
|
||||
ALuint FreqBands; /* Must be 1 or 2 */
|
||||
ALsizei NumSpeakers;
|
||||
enum AmbDecScaleType CoeffScale;
|
||||
|
||||
ALfloat XOverFreq;
|
||||
ALfloat XOverRatio;
|
||||
|
||||
struct {
|
||||
al_string Name;
|
||||
ALfloat Distance;
|
||||
ALfloat Azimuth;
|
||||
ALfloat Elevation;
|
||||
al_string Connection;
|
||||
} Speakers[MAX_OUTPUT_CHANNELS];
|
||||
|
||||
/* Unused when FreqBands == 1 */
|
||||
ALfloat LFOrderGain[MAX_AMBI_ORDER+1];
|
||||
ALfloat LFMatrix[MAX_OUTPUT_CHANNELS][MAX_AMBI_COEFFS];
|
||||
|
||||
ALfloat HFOrderGain[MAX_AMBI_ORDER+1];
|
||||
ALfloat HFMatrix[MAX_OUTPUT_CHANNELS][MAX_AMBI_COEFFS];
|
||||
} AmbDecConf;
|
||||
|
||||
void ambdec_init(AmbDecConf *conf);
|
||||
void ambdec_deinit(AmbDecConf *conf);
|
||||
int ambdec_load(AmbDecConf *conf, const char *fname);
|
||||
|
||||
#endif /* AMBDEC_H */
|
||||
+1464
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
|
||||
extern inline ALuint64 GetDeviceClockTime(ALCdevice *device);
|
||||
extern inline void ALCdevice_Lock(ALCdevice *device);
|
||||
extern inline void ALCdevice_Unlock(ALCdevice *device);
|
||||
extern inline ClockLatency GetClockLatency(ALCdevice *device);
|
||||
|
||||
/* Base ALCbackend method implementations. */
|
||||
void ALCbackend_Construct(ALCbackend *self, ALCdevice *device)
|
||||
{
|
||||
int ret = almtx_init(&self->mMutex, almtx_recursive);
|
||||
assert(ret == althrd_success);
|
||||
self->mDevice = device;
|
||||
}
|
||||
|
||||
void ALCbackend_Destruct(ALCbackend *self)
|
||||
{
|
||||
almtx_destroy(&self->mMutex);
|
||||
}
|
||||
|
||||
ALCboolean ALCbackend_reset(ALCbackend* UNUSED(self))
|
||||
{
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
ALCenum ALCbackend_captureSamples(ALCbackend* UNUSED(self), void* UNUSED(buffer), ALCuint UNUSED(samples))
|
||||
{
|
||||
return ALC_INVALID_DEVICE;
|
||||
}
|
||||
|
||||
ALCuint ALCbackend_availableSamples(ALCbackend* UNUSED(self))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ClockLatency ALCbackend_getClockLatency(ALCbackend *self)
|
||||
{
|
||||
ALCdevice *device = self->mDevice;
|
||||
ALuint refcount;
|
||||
ClockLatency ret;
|
||||
|
||||
do {
|
||||
while(((refcount=ATOMIC_LOAD(&device->MixCount, almemory_order_acquire))&1))
|
||||
althrd_yield();
|
||||
ret.ClockTime = GetDeviceClockTime(device);
|
||||
ATOMIC_THREAD_FENCE(almemory_order_acquire);
|
||||
} while(refcount != ATOMIC_LOAD(&device->MixCount, almemory_order_relaxed));
|
||||
|
||||
/* NOTE: The device will generally have about all but one periods filled at
|
||||
* any given time during playback. Without a more accurate measurement from
|
||||
* the output, this is an okay approximation.
|
||||
*/
|
||||
ret.Latency = device->UpdateSize * DEVICE_CLOCK_RES / device->Frequency *
|
||||
maxu(device->NumUpdates-1, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ALCbackend_lock(ALCbackend *self)
|
||||
{
|
||||
int ret = almtx_lock(&self->mMutex);
|
||||
assert(ret == althrd_success);
|
||||
}
|
||||
|
||||
void ALCbackend_unlock(ALCbackend *self)
|
||||
{
|
||||
int ret = almtx_unlock(&self->mMutex);
|
||||
assert(ret == althrd_success);
|
||||
}
|
||||
|
||||
|
||||
/* Base ALCbackendFactory method implementations. */
|
||||
void ALCbackendFactory_deinit(ALCbackendFactory* UNUSED(self))
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
#ifndef AL_BACKENDS_BASE_H
|
||||
#define AL_BACKENDS_BASE_H
|
||||
|
||||
#include "alMain.h"
|
||||
#include "threads.h"
|
||||
#include "alstring.h"
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct ClockLatency {
|
||||
ALint64 ClockTime;
|
||||
ALint64 Latency;
|
||||
} ClockLatency;
|
||||
|
||||
/* Helper to get the current clock time from the device's ClockBase, and
|
||||
* SamplesDone converted from the sample rate.
|
||||
*/
|
||||
inline ALuint64 GetDeviceClockTime(ALCdevice *device)
|
||||
{
|
||||
return device->ClockBase + (device->SamplesDone * DEVICE_CLOCK_RES /
|
||||
device->Frequency);
|
||||
}
|
||||
|
||||
|
||||
struct ALCbackendVtable;
|
||||
|
||||
typedef struct ALCbackend {
|
||||
const struct ALCbackendVtable *vtbl;
|
||||
|
||||
ALCdevice *mDevice;
|
||||
|
||||
almtx_t mMutex;
|
||||
} ALCbackend;
|
||||
|
||||
void ALCbackend_Construct(ALCbackend *self, ALCdevice *device);
|
||||
void ALCbackend_Destruct(ALCbackend *self);
|
||||
ALCboolean ALCbackend_reset(ALCbackend *self);
|
||||
ALCenum ALCbackend_captureSamples(ALCbackend *self, void *buffer, ALCuint samples);
|
||||
ALCuint ALCbackend_availableSamples(ALCbackend *self);
|
||||
ClockLatency ALCbackend_getClockLatency(ALCbackend *self);
|
||||
void ALCbackend_lock(ALCbackend *self);
|
||||
void ALCbackend_unlock(ALCbackend *self);
|
||||
|
||||
struct ALCbackendVtable {
|
||||
void (*const Destruct)(ALCbackend*);
|
||||
|
||||
ALCenum (*const open)(ALCbackend*, const ALCchar*);
|
||||
|
||||
ALCboolean (*const reset)(ALCbackend*);
|
||||
ALCboolean (*const start)(ALCbackend*);
|
||||
void (*const stop)(ALCbackend*);
|
||||
|
||||
ALCenum (*const captureSamples)(ALCbackend*, void*, ALCuint);
|
||||
ALCuint (*const availableSamples)(ALCbackend*);
|
||||
|
||||
ClockLatency (*const getClockLatency)(ALCbackend*);
|
||||
|
||||
void (*const lock)(ALCbackend*);
|
||||
void (*const unlock)(ALCbackend*);
|
||||
|
||||
void (*const Delete)(void*);
|
||||
};
|
||||
|
||||
#define DEFINE_ALCBACKEND_VTABLE(T) \
|
||||
DECLARE_THUNK(T, ALCbackend, void, Destruct) \
|
||||
DECLARE_THUNK1(T, ALCbackend, ALCenum, open, const ALCchar*) \
|
||||
DECLARE_THUNK(T, ALCbackend, ALCboolean, reset) \
|
||||
DECLARE_THUNK(T, ALCbackend, ALCboolean, start) \
|
||||
DECLARE_THUNK(T, ALCbackend, void, stop) \
|
||||
DECLARE_THUNK2(T, ALCbackend, ALCenum, captureSamples, void*, ALCuint) \
|
||||
DECLARE_THUNK(T, ALCbackend, ALCuint, availableSamples) \
|
||||
DECLARE_THUNK(T, ALCbackend, ClockLatency, getClockLatency) \
|
||||
DECLARE_THUNK(T, ALCbackend, void, lock) \
|
||||
DECLARE_THUNK(T, ALCbackend, void, unlock) \
|
||||
static void T##_ALCbackend_Delete(void *ptr) \
|
||||
{ T##_Delete(STATIC_UPCAST(T, ALCbackend, (ALCbackend*)ptr)); } \
|
||||
\
|
||||
static const struct ALCbackendVtable T##_ALCbackend_vtable = { \
|
||||
T##_ALCbackend_Destruct, \
|
||||
\
|
||||
T##_ALCbackend_open, \
|
||||
T##_ALCbackend_reset, \
|
||||
T##_ALCbackend_start, \
|
||||
T##_ALCbackend_stop, \
|
||||
T##_ALCbackend_captureSamples, \
|
||||
T##_ALCbackend_availableSamples, \
|
||||
T##_ALCbackend_getClockLatency, \
|
||||
T##_ALCbackend_lock, \
|
||||
T##_ALCbackend_unlock, \
|
||||
\
|
||||
T##_ALCbackend_Delete, \
|
||||
}
|
||||
|
||||
|
||||
typedef enum ALCbackend_Type {
|
||||
ALCbackend_Playback,
|
||||
ALCbackend_Capture,
|
||||
ALCbackend_Loopback
|
||||
} ALCbackend_Type;
|
||||
|
||||
|
||||
struct ALCbackendFactoryVtable;
|
||||
|
||||
typedef struct ALCbackendFactory {
|
||||
const struct ALCbackendFactoryVtable *vtbl;
|
||||
} ALCbackendFactory;
|
||||
|
||||
void ALCbackendFactory_deinit(ALCbackendFactory *self);
|
||||
|
||||
struct ALCbackendFactoryVtable {
|
||||
ALCboolean (*const init)(ALCbackendFactory *self);
|
||||
void (*const deinit)(ALCbackendFactory *self);
|
||||
|
||||
ALCboolean (*const querySupport)(ALCbackendFactory *self, ALCbackend_Type type);
|
||||
|
||||
void (*const probe)(ALCbackendFactory *self, enum DevProbe type, al_string *outnames);
|
||||
|
||||
ALCbackend* (*const createBackend)(ALCbackendFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
};
|
||||
|
||||
#define DEFINE_ALCBACKENDFACTORY_VTABLE(T) \
|
||||
DECLARE_THUNK(T, ALCbackendFactory, ALCboolean, init) \
|
||||
DECLARE_THUNK(T, ALCbackendFactory, void, deinit) \
|
||||
DECLARE_THUNK1(T, ALCbackendFactory, ALCboolean, querySupport, ALCbackend_Type) \
|
||||
DECLARE_THUNK2(T, ALCbackendFactory, void, probe, enum DevProbe, al_string*) \
|
||||
DECLARE_THUNK2(T, ALCbackendFactory, ALCbackend*, createBackend, ALCdevice*, ALCbackend_Type) \
|
||||
\
|
||||
static const struct ALCbackendFactoryVtable T##_ALCbackendFactory_vtable = { \
|
||||
T##_ALCbackendFactory_init, \
|
||||
T##_ALCbackendFactory_deinit, \
|
||||
T##_ALCbackendFactory_querySupport, \
|
||||
T##_ALCbackendFactory_probe, \
|
||||
T##_ALCbackendFactory_createBackend, \
|
||||
}
|
||||
|
||||
|
||||
ALCbackendFactory *ALCpulseBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCalsaBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCcoreAudioBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCossBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCjackBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCsolarisBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *SndioBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCqsaBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCwasapiBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCdsoundBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCwinmmBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCportBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCopenslBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCnullBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCwaveBackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCsdl2BackendFactory_getFactory(void);
|
||||
ALCbackendFactory *ALCloopbackFactory_getFactory(void);
|
||||
|
||||
|
||||
inline void ALCdevice_Lock(ALCdevice *device)
|
||||
{ V0(device->Backend,lock)(); }
|
||||
|
||||
inline void ALCdevice_Unlock(ALCdevice *device)
|
||||
{ V0(device->Backend,unlock)(); }
|
||||
|
||||
|
||||
inline ClockLatency GetClockLatency(ALCdevice *device)
|
||||
{
|
||||
ClockLatency ret = V0(device->Backend,getClockLatency)();
|
||||
ret.Latency += device->FixedLatency;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* AL_BACKENDS_BASE_H */
|
||||
@@ -0,0 +1,816 @@
|
||||
/**
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
|
||||
static const ALCchar ca_device[] = "CoreAudio Default";
|
||||
|
||||
|
||||
typedef struct ALCcoreAudioPlayback {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
AudioUnit audioUnit;
|
||||
|
||||
ALuint frameSize;
|
||||
AudioStreamBasicDescription format; // This is the OpenAL format as a CoreAudio ASBD
|
||||
} ALCcoreAudioPlayback;
|
||||
|
||||
static void ALCcoreAudioPlayback_Construct(ALCcoreAudioPlayback *self, ALCdevice *device);
|
||||
static void ALCcoreAudioPlayback_Destruct(ALCcoreAudioPlayback *self);
|
||||
static ALCenum ALCcoreAudioPlayback_open(ALCcoreAudioPlayback *self, const ALCchar *name);
|
||||
static ALCboolean ALCcoreAudioPlayback_reset(ALCcoreAudioPlayback *self);
|
||||
static ALCboolean ALCcoreAudioPlayback_start(ALCcoreAudioPlayback *self);
|
||||
static void ALCcoreAudioPlayback_stop(ALCcoreAudioPlayback *self);
|
||||
static DECLARE_FORWARD2(ALCcoreAudioPlayback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
|
||||
static DECLARE_FORWARD(ALCcoreAudioPlayback, ALCbackend, ALCuint, availableSamples)
|
||||
static DECLARE_FORWARD(ALCcoreAudioPlayback, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCcoreAudioPlayback, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCcoreAudioPlayback, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCcoreAudioPlayback)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCcoreAudioPlayback);
|
||||
|
||||
|
||||
static void ALCcoreAudioPlayback_Construct(ALCcoreAudioPlayback *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCcoreAudioPlayback, ALCbackend, self);
|
||||
|
||||
self->frameSize = 0;
|
||||
memset(&self->format, 0, sizeof(self->format));
|
||||
}
|
||||
|
||||
static void ALCcoreAudioPlayback_Destruct(ALCcoreAudioPlayback *self)
|
||||
{
|
||||
AudioUnitUninitialize(self->audioUnit);
|
||||
AudioComponentInstanceDispose(self->audioUnit);
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
static OSStatus ALCcoreAudioPlayback_MixerProc(void *inRefCon,
|
||||
AudioUnitRenderActionFlags* UNUSED(ioActionFlags), const AudioTimeStamp* UNUSED(inTimeStamp),
|
||||
UInt32 UNUSED(inBusNumber), UInt32 UNUSED(inNumberFrames), AudioBufferList *ioData)
|
||||
{
|
||||
ALCcoreAudioPlayback *self = inRefCon;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
|
||||
ALCcoreAudioPlayback_lock(self);
|
||||
aluMixData(device, ioData->mBuffers[0].mData,
|
||||
ioData->mBuffers[0].mDataByteSize / self->frameSize);
|
||||
ALCcoreAudioPlayback_unlock(self);
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCcoreAudioPlayback_open(ALCcoreAudioPlayback *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
AudioComponentDescription desc;
|
||||
AudioComponent comp;
|
||||
OSStatus err;
|
||||
|
||||
if(!name)
|
||||
name = ca_device;
|
||||
else if(strcmp(name, ca_device) != 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
/* open the default output unit */
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
#if TARGET_OS_IOS
|
||||
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
#else
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
#endif
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
comp = AudioComponentFindNext(NULL, &desc);
|
||||
if(comp == NULL)
|
||||
{
|
||||
ERR("AudioComponentFindNext failed\n");
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
err = AudioComponentInstanceNew(comp, &self->audioUnit);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioComponentInstanceNew failed\n");
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
/* init and start the default audio unit... */
|
||||
err = AudioUnitInitialize(self->audioUnit);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitInitialize failed\n");
|
||||
AudioComponentInstanceDispose(self->audioUnit);
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean ALCcoreAudioPlayback_reset(ALCcoreAudioPlayback *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
AudioStreamBasicDescription streamFormat;
|
||||
AURenderCallbackStruct input;
|
||||
OSStatus err;
|
||||
UInt32 size;
|
||||
|
||||
err = AudioUnitUninitialize(self->audioUnit);
|
||||
if(err != noErr)
|
||||
ERR("-- AudioUnitUninitialize failed.\n");
|
||||
|
||||
/* retrieve default output unit's properties (output side) */
|
||||
size = sizeof(AudioStreamBasicDescription);
|
||||
err = AudioUnitGetProperty(self->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, &size);
|
||||
if(err != noErr || size != sizeof(AudioStreamBasicDescription))
|
||||
{
|
||||
ERR("AudioUnitGetProperty failed\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
#if 0
|
||||
TRACE("Output streamFormat of default output unit -\n");
|
||||
TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
|
||||
TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
|
||||
TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
|
||||
TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
|
||||
TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
|
||||
TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
|
||||
#endif
|
||||
|
||||
/* set default output unit's input side to match output side */
|
||||
err = AudioUnitSetProperty(self->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, size);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
if(device->Frequency != streamFormat.mSampleRate)
|
||||
{
|
||||
device->NumUpdates = (ALuint)((ALuint64)device->NumUpdates *
|
||||
streamFormat.mSampleRate /
|
||||
device->Frequency);
|
||||
device->Frequency = streamFormat.mSampleRate;
|
||||
}
|
||||
|
||||
/* FIXME: How to tell what channels are what in the output device, and how
|
||||
* to specify what we're giving? eg, 6.0 vs 5.1 */
|
||||
switch(streamFormat.mChannelsPerFrame)
|
||||
{
|
||||
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:
|
||||
ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame);
|
||||
device->FmtChans = DevFmtStereo;
|
||||
streamFormat.mChannelsPerFrame = 2;
|
||||
break;
|
||||
}
|
||||
SetDefaultWFXChannelOrder(device);
|
||||
|
||||
/* use channel count and sample rate from the default output unit's current
|
||||
* parameters, but reset everything else */
|
||||
streamFormat.mFramesPerPacket = 1;
|
||||
streamFormat.mFormatFlags = 0;
|
||||
switch(device->FmtType)
|
||||
{
|
||||
case DevFmtUByte:
|
||||
device->FmtType = DevFmtByte;
|
||||
/* fall-through */
|
||||
case DevFmtByte:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
streamFormat.mBitsPerChannel = 8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
device->FmtType = DevFmtShort;
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
streamFormat.mBitsPerChannel = 16;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
device->FmtType = DevFmtInt;
|
||||
/* fall-through */
|
||||
case DevFmtInt:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
streamFormat.mBitsPerChannel = 32;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat;
|
||||
streamFormat.mBitsPerChannel = 32;
|
||||
break;
|
||||
}
|
||||
streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame *
|
||||
streamFormat.mBitsPerChannel / 8;
|
||||
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
|
||||
streamFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
|
||||
kLinearPCMFormatFlagIsPacked;
|
||||
|
||||
err = AudioUnitSetProperty(self->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof(AudioStreamBasicDescription));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
/* setup callback */
|
||||
self->frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
input.inputProc = ALCcoreAudioPlayback_MixerProc;
|
||||
input.inputProcRefCon = self;
|
||||
|
||||
err = AudioUnitSetProperty(self->audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
/* init the default audio unit... */
|
||||
err = AudioUnitInitialize(self->audioUnit);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitInitialize failed\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCcoreAudioPlayback_start(ALCcoreAudioPlayback *self)
|
||||
{
|
||||
OSStatus err = AudioOutputUnitStart(self->audioUnit);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioOutputUnitStart failed\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCcoreAudioPlayback_stop(ALCcoreAudioPlayback *self)
|
||||
{
|
||||
OSStatus err = AudioOutputUnitStop(self->audioUnit);
|
||||
if(err != noErr)
|
||||
ERR("AudioOutputUnitStop failed\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
typedef struct ALCcoreAudioCapture {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
AudioUnit audioUnit;
|
||||
|
||||
ALuint frameSize;
|
||||
ALdouble sampleRateRatio; // Ratio of hardware sample rate / requested sample rate
|
||||
AudioStreamBasicDescription format; // This is the OpenAL format as a CoreAudio ASBD
|
||||
|
||||
AudioConverterRef audioConverter; // Sample rate converter if needed
|
||||
AudioBufferList *bufferList; // Buffer for data coming from the input device
|
||||
ALCvoid *resampleBuffer; // Buffer for returned RingBuffer data when resampling
|
||||
|
||||
ll_ringbuffer_t *ring;
|
||||
} ALCcoreAudioCapture;
|
||||
|
||||
static void ALCcoreAudioCapture_Construct(ALCcoreAudioCapture *self, ALCdevice *device);
|
||||
static void ALCcoreAudioCapture_Destruct(ALCcoreAudioCapture *self);
|
||||
static ALCenum ALCcoreAudioCapture_open(ALCcoreAudioCapture *self, const ALCchar *name);
|
||||
static DECLARE_FORWARD(ALCcoreAudioCapture, ALCbackend, ALCboolean, reset)
|
||||
static ALCboolean ALCcoreAudioCapture_start(ALCcoreAudioCapture *self);
|
||||
static void ALCcoreAudioCapture_stop(ALCcoreAudioCapture *self);
|
||||
static ALCenum ALCcoreAudioCapture_captureSamples(ALCcoreAudioCapture *self, ALCvoid *buffer, ALCuint samples);
|
||||
static ALCuint ALCcoreAudioCapture_availableSamples(ALCcoreAudioCapture *self);
|
||||
static DECLARE_FORWARD(ALCcoreAudioCapture, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCcoreAudioCapture, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCcoreAudioCapture, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCcoreAudioCapture)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCcoreAudioCapture);
|
||||
|
||||
|
||||
static AudioBufferList *allocate_buffer_list(UInt32 channelCount, UInt32 byteSize)
|
||||
{
|
||||
AudioBufferList *list;
|
||||
|
||||
list = calloc(1, FAM_SIZE(AudioBufferList, mBuffers, 1) + byteSize);
|
||||
if(list)
|
||||
{
|
||||
list->mNumberBuffers = 1;
|
||||
|
||||
list->mBuffers[0].mNumberChannels = channelCount;
|
||||
list->mBuffers[0].mDataByteSize = byteSize;
|
||||
list->mBuffers[0].mData = &list->mBuffers[1];
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
static void destroy_buffer_list(AudioBufferList *list)
|
||||
{
|
||||
free(list);
|
||||
}
|
||||
|
||||
|
||||
static void ALCcoreAudioCapture_Construct(ALCcoreAudioCapture *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCcoreAudioCapture, ALCbackend, self);
|
||||
|
||||
self->audioUnit = 0;
|
||||
self->audioConverter = NULL;
|
||||
self->bufferList = NULL;
|
||||
self->resampleBuffer = NULL;
|
||||
self->ring = NULL;
|
||||
}
|
||||
|
||||
static void ALCcoreAudioCapture_Destruct(ALCcoreAudioCapture *self)
|
||||
{
|
||||
ll_ringbuffer_free(self->ring);
|
||||
self->ring = NULL;
|
||||
|
||||
free(self->resampleBuffer);
|
||||
self->resampleBuffer = NULL;
|
||||
|
||||
destroy_buffer_list(self->bufferList);
|
||||
self->bufferList = NULL;
|
||||
|
||||
if(self->audioConverter)
|
||||
AudioConverterDispose(self->audioConverter);
|
||||
self->audioConverter = NULL;
|
||||
|
||||
if(self->audioUnit)
|
||||
AudioComponentInstanceDispose(self->audioUnit);
|
||||
self->audioUnit = 0;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
static OSStatus ALCcoreAudioCapture_RecordProc(void *inRefCon,
|
||||
AudioUnitRenderActionFlags* UNUSED(ioActionFlags),
|
||||
const AudioTimeStamp *inTimeStamp, UInt32 UNUSED(inBusNumber),
|
||||
UInt32 inNumberFrames, AudioBufferList* UNUSED(ioData))
|
||||
{
|
||||
ALCcoreAudioCapture *self = inRefCon;
|
||||
AudioUnitRenderActionFlags flags = 0;
|
||||
OSStatus err;
|
||||
|
||||
// fill the bufferList with data from the input device
|
||||
err = AudioUnitRender(self->audioUnit, &flags, inTimeStamp, 1, inNumberFrames, self->bufferList);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitRender error: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ll_ringbuffer_write(self->ring, self->bufferList->mBuffers[0].mData, inNumberFrames);
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ALCcoreAudioCapture_ConvertCallback(AudioConverterRef UNUSED(inAudioConverter),
|
||||
UInt32 *ioNumberDataPackets, AudioBufferList *ioData,
|
||||
AudioStreamPacketDescription** UNUSED(outDataPacketDescription),
|
||||
void *inUserData)
|
||||
{
|
||||
ALCcoreAudioCapture *self = inUserData;
|
||||
|
||||
// Read from the ring buffer and store temporarily in a large buffer
|
||||
ll_ringbuffer_read(self->ring, self->resampleBuffer, *ioNumberDataPackets);
|
||||
|
||||
// Set the input data
|
||||
ioData->mNumberBuffers = 1;
|
||||
ioData->mBuffers[0].mNumberChannels = self->format.mChannelsPerFrame;
|
||||
ioData->mBuffers[0].mData = self->resampleBuffer;
|
||||
ioData->mBuffers[0].mDataByteSize = (*ioNumberDataPackets) * self->format.mBytesPerFrame;
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCcoreAudioCapture_open(ALCcoreAudioCapture *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
AudioStreamBasicDescription requestedFormat; // The application requested format
|
||||
AudioStreamBasicDescription hardwareFormat; // The hardware format
|
||||
AudioStreamBasicDescription outputFormat; // The AudioUnit output format
|
||||
AURenderCallbackStruct input;
|
||||
AudioComponentDescription desc;
|
||||
UInt32 outputFrameCount;
|
||||
UInt32 propertySize;
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
UInt32 enableIO;
|
||||
AudioComponent comp;
|
||||
OSStatus err;
|
||||
|
||||
if(!name)
|
||||
name = ca_device;
|
||||
else if(strcmp(name, ca_device) != 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
#if TARGET_OS_IOS
|
||||
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
#else
|
||||
desc.componentSubType = kAudioUnitSubType_HALOutput;
|
||||
#endif
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
// Search for component with given description
|
||||
comp = AudioComponentFindNext(NULL, &desc);
|
||||
if(comp == NULL)
|
||||
{
|
||||
ERR("AudioComponentFindNext failed\n");
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
// Open the component
|
||||
err = AudioComponentInstanceNew(comp, &self->audioUnit);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioComponentInstanceNew failed\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Turn off AudioUnit output
|
||||
enableIO = 0;
|
||||
err = AudioUnitSetProperty(self->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Turn on AudioUnit input
|
||||
enableIO = 1;
|
||||
err = AudioUnitSetProperty(self->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
#if !TARGET_OS_IOS
|
||||
// Get the default input device
|
||||
AudioDeviceID inputDevice = kAudioDeviceUnknown;
|
||||
|
||||
propertySize = sizeof(AudioDeviceID);
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
|
||||
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize, &inputDevice);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioObjectGetPropertyData failed\n");
|
||||
goto error;
|
||||
}
|
||||
if(inputDevice == kAudioDeviceUnknown)
|
||||
{
|
||||
ERR("No input device found\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Track the input device
|
||||
err = AudioUnitSetProperty(self->audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
// set capture callback
|
||||
input.inputProc = ALCcoreAudioCapture_RecordProc;
|
||||
input.inputProcRefCon = self;
|
||||
|
||||
err = AudioUnitSetProperty(self->audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Initialize the device
|
||||
err = AudioUnitInitialize(self->audioUnit);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitInitialize failed\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Get the hardware format
|
||||
propertySize = sizeof(AudioStreamBasicDescription);
|
||||
err = AudioUnitGetProperty(self->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &hardwareFormat, &propertySize);
|
||||
if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription))
|
||||
{
|
||||
ERR("AudioUnitGetProperty failed\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Set up the requested format description
|
||||
switch(device->FmtType)
|
||||
{
|
||||
case DevFmtUByte:
|
||||
requestedFormat.mBitsPerChannel = 8;
|
||||
requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
|
||||
break;
|
||||
case DevFmtShort:
|
||||
requestedFormat.mBitsPerChannel = 16;
|
||||
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
|
||||
break;
|
||||
case DevFmtInt:
|
||||
requestedFormat.mBitsPerChannel = 32;
|
||||
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
requestedFormat.mBitsPerChannel = 32;
|
||||
requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
|
||||
break;
|
||||
case DevFmtByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
ERR("%s samples not supported\n", DevFmtTypeString(device->FmtType));
|
||||
goto error;
|
||||
}
|
||||
|
||||
switch(device->FmtChans)
|
||||
{
|
||||
case DevFmtMono:
|
||||
requestedFormat.mChannelsPerFrame = 1;
|
||||
break;
|
||||
case DevFmtStereo:
|
||||
requestedFormat.mChannelsPerFrame = 2;
|
||||
break;
|
||||
|
||||
case DevFmtQuad:
|
||||
case DevFmtX51:
|
||||
case DevFmtX51Rear:
|
||||
case DevFmtX61:
|
||||
case DevFmtX71:
|
||||
case DevFmtAmbi3D:
|
||||
ERR("%s not supported\n", DevFmtChannelsString(device->FmtChans));
|
||||
goto error;
|
||||
}
|
||||
|
||||
requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
|
||||
requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
|
||||
requestedFormat.mSampleRate = device->Frequency;
|
||||
requestedFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
requestedFormat.mReserved = 0;
|
||||
requestedFormat.mFramesPerPacket = 1;
|
||||
|
||||
// save requested format description for later use
|
||||
self->format = requestedFormat;
|
||||
self->frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
|
||||
// Use intermediate format for sample rate conversion (outputFormat)
|
||||
// Set sample rate to the same as hardware for resampling later
|
||||
outputFormat = requestedFormat;
|
||||
outputFormat.mSampleRate = hardwareFormat.mSampleRate;
|
||||
|
||||
// Determine sample rate ratio for resampling
|
||||
self->sampleRateRatio = outputFormat.mSampleRate / device->Frequency;
|
||||
|
||||
// The output format should be the requested format, but using the hardware sample rate
|
||||
// This is because the AudioUnit will automatically scale other properties, except for sample rate
|
||||
err = AudioUnitSetProperty(self->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, (void *)&outputFormat, sizeof(outputFormat));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Set the AudioUnit output format frame count
|
||||
outputFrameCount = device->UpdateSize * self->sampleRateRatio;
|
||||
err = AudioUnitSetProperty(self->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed: %d\n", err);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Set up sample converter
|
||||
err = AudioConverterNew(&outputFormat, &requestedFormat, &self->audioConverter);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioConverterNew failed: %d\n", err);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Create a buffer for use in the resample callback
|
||||
self->resampleBuffer = malloc(device->UpdateSize * self->frameSize * self->sampleRateRatio);
|
||||
|
||||
// Allocate buffer for the AudioUnit output
|
||||
self->bufferList = allocate_buffer_list(outputFormat.mChannelsPerFrame, device->UpdateSize * self->frameSize * self->sampleRateRatio);
|
||||
if(self->bufferList == NULL)
|
||||
goto error;
|
||||
|
||||
self->ring = ll_ringbuffer_create(
|
||||
(size_t)ceil(device->UpdateSize*self->sampleRateRatio*device->NumUpdates),
|
||||
self->frameSize, false
|
||||
);
|
||||
if(!self->ring) goto error;
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
|
||||
error:
|
||||
ll_ringbuffer_free(self->ring);
|
||||
self->ring = NULL;
|
||||
free(self->resampleBuffer);
|
||||
self->resampleBuffer = NULL;
|
||||
destroy_buffer_list(self->bufferList);
|
||||
self->bufferList = NULL;
|
||||
|
||||
if(self->audioConverter)
|
||||
AudioConverterDispose(self->audioConverter);
|
||||
self->audioConverter = NULL;
|
||||
if(self->audioUnit)
|
||||
AudioComponentInstanceDispose(self->audioUnit);
|
||||
self->audioUnit = 0;
|
||||
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
|
||||
static ALCboolean ALCcoreAudioCapture_start(ALCcoreAudioCapture *self)
|
||||
{
|
||||
OSStatus err = AudioOutputUnitStart(self->audioUnit);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioOutputUnitStart failed\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCcoreAudioCapture_stop(ALCcoreAudioCapture *self)
|
||||
{
|
||||
OSStatus err = AudioOutputUnitStop(self->audioUnit);
|
||||
if(err != noErr)
|
||||
ERR("AudioOutputUnitStop failed\n");
|
||||
}
|
||||
|
||||
static ALCenum ALCcoreAudioCapture_captureSamples(ALCcoreAudioCapture *self, ALCvoid *buffer, ALCuint samples)
|
||||
{
|
||||
union {
|
||||
ALbyte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)];
|
||||
AudioBufferList list;
|
||||
} audiobuf = { { 0 } };
|
||||
UInt32 frameCount;
|
||||
OSStatus err;
|
||||
|
||||
// If no samples are requested, just return
|
||||
if(samples == 0) return ALC_NO_ERROR;
|
||||
|
||||
// Point the resampling buffer to the capture buffer
|
||||
audiobuf.list.mNumberBuffers = 1;
|
||||
audiobuf.list.mBuffers[0].mNumberChannels = self->format.mChannelsPerFrame;
|
||||
audiobuf.list.mBuffers[0].mDataByteSize = samples * self->frameSize;
|
||||
audiobuf.list.mBuffers[0].mData = buffer;
|
||||
|
||||
// Resample into another AudioBufferList
|
||||
frameCount = samples;
|
||||
err = AudioConverterFillComplexBuffer(self->audioConverter,
|
||||
ALCcoreAudioCapture_ConvertCallback, self, &frameCount, &audiobuf.list, NULL
|
||||
);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioConverterFillComplexBuffer error: %d\n", err);
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCuint ALCcoreAudioCapture_availableSamples(ALCcoreAudioCapture *self)
|
||||
{
|
||||
return ll_ringbuffer_read_space(self->ring) / self->sampleRateRatio;
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCcoreAudioBackendFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} ALCcoreAudioBackendFactory;
|
||||
#define ALCCOREAUDIOBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCcoreAudioBackendFactory, ALCbackendFactory) } }
|
||||
|
||||
ALCbackendFactory *ALCcoreAudioBackendFactory_getFactory(void);
|
||||
|
||||
static ALCboolean ALCcoreAudioBackendFactory_init(ALCcoreAudioBackendFactory *self);
|
||||
static DECLARE_FORWARD(ALCcoreAudioBackendFactory, ALCbackendFactory, void, deinit)
|
||||
static ALCboolean ALCcoreAudioBackendFactory_querySupport(ALCcoreAudioBackendFactory *self, ALCbackend_Type type);
|
||||
static void ALCcoreAudioBackendFactory_probe(ALCcoreAudioBackendFactory *self, enum DevProbe type, al_string *outnames);
|
||||
static ALCbackend* ALCcoreAudioBackendFactory_createBackend(ALCcoreAudioBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(ALCcoreAudioBackendFactory);
|
||||
|
||||
|
||||
ALCbackendFactory *ALCcoreAudioBackendFactory_getFactory(void)
|
||||
{
|
||||
static ALCcoreAudioBackendFactory factory = ALCCOREAUDIOBACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
|
||||
|
||||
static ALCboolean ALCcoreAudioBackendFactory_init(ALCcoreAudioBackendFactory* UNUSED(self))
|
||||
{
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCcoreAudioBackendFactory_querySupport(ALCcoreAudioBackendFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback || ALCbackend_Capture)
|
||||
return ALC_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void ALCcoreAudioBackendFactory_probe(ALCcoreAudioBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case ALL_DEVICE_PROBE:
|
||||
case CAPTURE_DEVICE_PROBE:
|
||||
alstr_append_range(outnames, ca_device, ca_device+sizeof(ca_device));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static ALCbackend* ALCcoreAudioBackendFactory_createBackend(ALCcoreAudioBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
{
|
||||
ALCcoreAudioPlayback *backend;
|
||||
NEW_OBJ(backend, ALCcoreAudioPlayback)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
if(type == ALCbackend_Capture)
|
||||
{
|
||||
ALCcoreAudioCapture *backend;
|
||||
NEW_OBJ(backend, ALCcoreAudioCapture)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,607 @@
|
||||
/**
|
||||
* 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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "threads.h"
|
||||
#include "compat.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
#include <jack/jack.h>
|
||||
#include <jack/ringbuffer.h>
|
||||
|
||||
|
||||
static const ALCchar jackDevice[] = "JACK Default";
|
||||
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#define JACK_FUNCS(MAGIC) \
|
||||
MAGIC(jack_client_open); \
|
||||
MAGIC(jack_client_close); \
|
||||
MAGIC(jack_client_name_size); \
|
||||
MAGIC(jack_get_client_name); \
|
||||
MAGIC(jack_connect); \
|
||||
MAGIC(jack_activate); \
|
||||
MAGIC(jack_deactivate); \
|
||||
MAGIC(jack_port_register); \
|
||||
MAGIC(jack_port_unregister); \
|
||||
MAGIC(jack_port_get_buffer); \
|
||||
MAGIC(jack_port_name); \
|
||||
MAGIC(jack_get_ports); \
|
||||
MAGIC(jack_free); \
|
||||
MAGIC(jack_get_sample_rate); \
|
||||
MAGIC(jack_set_error_function); \
|
||||
MAGIC(jack_set_process_callback); \
|
||||
MAGIC(jack_set_buffer_size_callback); \
|
||||
MAGIC(jack_set_buffer_size); \
|
||||
MAGIC(jack_get_buffer_size);
|
||||
|
||||
static void *jack_handle;
|
||||
#define MAKE_FUNC(f) static __typeof(f) * p##f
|
||||
JACK_FUNCS(MAKE_FUNC);
|
||||
static __typeof(jack_error_callback) * pjack_error_callback;
|
||||
#undef MAKE_FUNC
|
||||
|
||||
#define jack_client_open pjack_client_open
|
||||
#define jack_client_close pjack_client_close
|
||||
#define jack_client_name_size pjack_client_name_size
|
||||
#define jack_get_client_name pjack_get_client_name
|
||||
#define jack_connect pjack_connect
|
||||
#define jack_activate pjack_activate
|
||||
#define jack_deactivate pjack_deactivate
|
||||
#define jack_port_register pjack_port_register
|
||||
#define jack_port_unregister pjack_port_unregister
|
||||
#define jack_port_get_buffer pjack_port_get_buffer
|
||||
#define jack_port_name pjack_port_name
|
||||
#define jack_get_ports pjack_get_ports
|
||||
#define jack_free pjack_free
|
||||
#define jack_get_sample_rate pjack_get_sample_rate
|
||||
#define jack_set_error_function pjack_set_error_function
|
||||
#define jack_set_process_callback pjack_set_process_callback
|
||||
#define jack_set_buffer_size_callback pjack_set_buffer_size_callback
|
||||
#define jack_set_buffer_size pjack_set_buffer_size
|
||||
#define jack_get_buffer_size pjack_get_buffer_size
|
||||
#define jack_error_callback (*pjack_error_callback)
|
||||
#endif
|
||||
|
||||
|
||||
static jack_options_t ClientOptions = JackNullOption;
|
||||
|
||||
static ALCboolean jack_load(void)
|
||||
{
|
||||
ALCboolean error = ALC_FALSE;
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
if(!jack_handle)
|
||||
{
|
||||
al_string missing_funcs = AL_STRING_INIT_STATIC();
|
||||
|
||||
#ifdef _WIN32
|
||||
#define JACKLIB "libjack.dll"
|
||||
#else
|
||||
#define JACKLIB "libjack.so.0"
|
||||
#endif
|
||||
jack_handle = LoadLib(JACKLIB);
|
||||
if(!jack_handle)
|
||||
{
|
||||
WARN("Failed to load %s\n", JACKLIB);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
error = ALC_FALSE;
|
||||
#define LOAD_FUNC(f) do { \
|
||||
p##f = GetSymbol(jack_handle, #f); \
|
||||
if(p##f == NULL) { \
|
||||
error = ALC_TRUE; \
|
||||
alstr_append_cstr(&missing_funcs, "\n" #f); \
|
||||
} \
|
||||
} while(0)
|
||||
JACK_FUNCS(LOAD_FUNC);
|
||||
#undef LOAD_FUNC
|
||||
/* Optional symbols. These don't exist in all versions of JACK. */
|
||||
#define LOAD_SYM(f) p##f = GetSymbol(jack_handle, #f)
|
||||
LOAD_SYM(jack_error_callback);
|
||||
#undef LOAD_SYM
|
||||
|
||||
if(error)
|
||||
{
|
||||
WARN("Missing expected functions:%s\n", alstr_get_cstr(missing_funcs));
|
||||
CloseLib(jack_handle);
|
||||
jack_handle = NULL;
|
||||
}
|
||||
alstr_reset(&missing_funcs);
|
||||
}
|
||||
#endif
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCjackPlayback {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
jack_client_t *Client;
|
||||
jack_port_t *Port[MAX_OUTPUT_CHANNELS];
|
||||
|
||||
ll_ringbuffer_t *Ring;
|
||||
alsem_t Sem;
|
||||
|
||||
ATOMIC(ALenum) killNow;
|
||||
althrd_t thread;
|
||||
} ALCjackPlayback;
|
||||
|
||||
static int ALCjackPlayback_bufferSizeNotify(jack_nframes_t numframes, void *arg);
|
||||
|
||||
static int ALCjackPlayback_process(jack_nframes_t numframes, void *arg);
|
||||
static int ALCjackPlayback_mixerProc(void *arg);
|
||||
|
||||
static void ALCjackPlayback_Construct(ALCjackPlayback *self, ALCdevice *device);
|
||||
static void ALCjackPlayback_Destruct(ALCjackPlayback *self);
|
||||
static ALCenum ALCjackPlayback_open(ALCjackPlayback *self, const ALCchar *name);
|
||||
static ALCboolean ALCjackPlayback_reset(ALCjackPlayback *self);
|
||||
static ALCboolean ALCjackPlayback_start(ALCjackPlayback *self);
|
||||
static void ALCjackPlayback_stop(ALCjackPlayback *self);
|
||||
static DECLARE_FORWARD2(ALCjackPlayback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
|
||||
static DECLARE_FORWARD(ALCjackPlayback, ALCbackend, ALCuint, availableSamples)
|
||||
static ClockLatency ALCjackPlayback_getClockLatency(ALCjackPlayback *self);
|
||||
static DECLARE_FORWARD(ALCjackPlayback, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCjackPlayback, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCjackPlayback)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCjackPlayback);
|
||||
|
||||
|
||||
static void ALCjackPlayback_Construct(ALCjackPlayback *self, ALCdevice *device)
|
||||
{
|
||||
ALuint i;
|
||||
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCjackPlayback, ALCbackend, self);
|
||||
|
||||
alsem_init(&self->Sem, 0);
|
||||
|
||||
self->Client = NULL;
|
||||
for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
|
||||
self->Port[i] = NULL;
|
||||
self->Ring = NULL;
|
||||
|
||||
ATOMIC_INIT(&self->killNow, AL_TRUE);
|
||||
}
|
||||
|
||||
static void ALCjackPlayback_Destruct(ALCjackPlayback *self)
|
||||
{
|
||||
ALuint i;
|
||||
|
||||
if(self->Client)
|
||||
{
|
||||
for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
|
||||
{
|
||||
if(self->Port[i])
|
||||
jack_port_unregister(self->Client, self->Port[i]);
|
||||
self->Port[i] = NULL;
|
||||
}
|
||||
jack_client_close(self->Client);
|
||||
self->Client = NULL;
|
||||
}
|
||||
|
||||
alsem_destroy(&self->Sem);
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
static int ALCjackPlayback_bufferSizeNotify(jack_nframes_t numframes, void *arg)
|
||||
{
|
||||
ALCjackPlayback *self = arg;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
ALuint bufsize;
|
||||
|
||||
ALCjackPlayback_lock(self);
|
||||
device->UpdateSize = numframes;
|
||||
device->NumUpdates = 2;
|
||||
|
||||
bufsize = device->UpdateSize;
|
||||
if(ConfigValueUInt(alstr_get_cstr(device->DeviceName), "jack", "buffer-size", &bufsize))
|
||||
bufsize = maxu(NextPowerOf2(bufsize), device->UpdateSize);
|
||||
device->NumUpdates = (bufsize+device->UpdateSize) / device->UpdateSize;
|
||||
|
||||
TRACE("%u update size x%u\n", device->UpdateSize, device->NumUpdates);
|
||||
|
||||
ll_ringbuffer_free(self->Ring);
|
||||
self->Ring = ll_ringbuffer_create(bufsize,
|
||||
FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder),
|
||||
true
|
||||
);
|
||||
if(!self->Ring)
|
||||
{
|
||||
ERR("Failed to reallocate ringbuffer\n");
|
||||
aluHandleDisconnect(device, "Failed to reallocate %u-sample buffer", bufsize);
|
||||
}
|
||||
ALCjackPlayback_unlock(self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int ALCjackPlayback_process(jack_nframes_t numframes, void *arg)
|
||||
{
|
||||
ALCjackPlayback *self = arg;
|
||||
jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS];
|
||||
ll_ringbuffer_data_t data[2];
|
||||
jack_nframes_t total = 0;
|
||||
jack_nframes_t todo;
|
||||
ALsizei i, c, numchans;
|
||||
|
||||
ll_ringbuffer_get_read_vector(self->Ring, data);
|
||||
|
||||
for(c = 0;c < MAX_OUTPUT_CHANNELS && self->Port[c];c++)
|
||||
out[c] = jack_port_get_buffer(self->Port[c], numframes);
|
||||
numchans = c;
|
||||
|
||||
todo = minu(numframes, data[0].len);
|
||||
for(c = 0;c < numchans;c++)
|
||||
{
|
||||
const ALfloat *restrict in = ((ALfloat*)data[0].buf) + c;
|
||||
for(i = 0;(jack_nframes_t)i < todo;i++)
|
||||
out[c][i] = in[i*numchans];
|
||||
out[c] += todo;
|
||||
}
|
||||
total += todo;
|
||||
|
||||
todo = minu(numframes-total, data[1].len);
|
||||
if(todo > 0)
|
||||
{
|
||||
for(c = 0;c < numchans;c++)
|
||||
{
|
||||
const ALfloat *restrict in = ((ALfloat*)data[1].buf) + c;
|
||||
for(i = 0;(jack_nframes_t)i < todo;i++)
|
||||
out[c][i] = in[i*numchans];
|
||||
out[c] += todo;
|
||||
}
|
||||
total += todo;
|
||||
}
|
||||
|
||||
ll_ringbuffer_read_advance(self->Ring, total);
|
||||
alsem_post(&self->Sem);
|
||||
|
||||
if(numframes > total)
|
||||
{
|
||||
todo = numframes-total;
|
||||
for(c = 0;c < numchans;c++)
|
||||
{
|
||||
for(i = 0;(jack_nframes_t)i < todo;i++)
|
||||
out[c][i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ALCjackPlayback_mixerProc(void *arg)
|
||||
{
|
||||
ALCjackPlayback *self = arg;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
ll_ringbuffer_data_t data[2];
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(althrd_current(), MIXER_THREAD_NAME);
|
||||
|
||||
ALCjackPlayback_lock(self);
|
||||
while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
|
||||
ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
|
||||
{
|
||||
ALuint todo, len1, len2;
|
||||
|
||||
if(ll_ringbuffer_write_space(self->Ring) < device->UpdateSize)
|
||||
{
|
||||
ALCjackPlayback_unlock(self);
|
||||
alsem_wait(&self->Sem);
|
||||
ALCjackPlayback_lock(self);
|
||||
continue;
|
||||
}
|
||||
|
||||
ll_ringbuffer_get_write_vector(self->Ring, data);
|
||||
todo = data[0].len + data[1].len;
|
||||
todo -= todo%device->UpdateSize;
|
||||
|
||||
len1 = minu(data[0].len, todo);
|
||||
len2 = minu(data[1].len, todo-len1);
|
||||
|
||||
aluMixData(device, data[0].buf, len1);
|
||||
if(len2 > 0)
|
||||
aluMixData(device, data[1].buf, len2);
|
||||
ll_ringbuffer_write_advance(self->Ring, todo);
|
||||
}
|
||||
ALCjackPlayback_unlock(self);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCjackPlayback_open(ALCjackPlayback *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
const char *client_name = "alsoft";
|
||||
jack_status_t status;
|
||||
|
||||
if(!name)
|
||||
name = jackDevice;
|
||||
else if(strcmp(name, jackDevice) != 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
self->Client = jack_client_open(client_name, ClientOptions, &status, NULL);
|
||||
if(self->Client == NULL)
|
||||
{
|
||||
ERR("jack_client_open() failed, status = 0x%02x\n", status);
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
if((status&JackServerStarted))
|
||||
TRACE("JACK server started\n");
|
||||
if((status&JackNameNotUnique))
|
||||
{
|
||||
client_name = jack_get_client_name(self->Client);
|
||||
TRACE("Client name not unique, got `%s' instead\n", client_name);
|
||||
}
|
||||
|
||||
jack_set_process_callback(self->Client, ALCjackPlayback_process, self);
|
||||
jack_set_buffer_size_callback(self->Client, ALCjackPlayback_bufferSizeNotify, self);
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean ALCjackPlayback_reset(ALCjackPlayback *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
ALsizei numchans, i;
|
||||
ALuint bufsize;
|
||||
|
||||
for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
|
||||
{
|
||||
if(self->Port[i])
|
||||
jack_port_unregister(self->Client, self->Port[i]);
|
||||
self->Port[i] = NULL;
|
||||
}
|
||||
|
||||
/* Ignore the requested buffer metrics and just keep one JACK-sized buffer
|
||||
* ready for when requested.
|
||||
*/
|
||||
device->Frequency = jack_get_sample_rate(self->Client);
|
||||
device->UpdateSize = jack_get_buffer_size(self->Client);
|
||||
device->NumUpdates = 2;
|
||||
|
||||
bufsize = device->UpdateSize;
|
||||
if(ConfigValueUInt(alstr_get_cstr(device->DeviceName), "jack", "buffer-size", &bufsize))
|
||||
bufsize = maxu(NextPowerOf2(bufsize), device->UpdateSize);
|
||||
device->NumUpdates = (bufsize+device->UpdateSize) / device->UpdateSize;
|
||||
|
||||
/* Force 32-bit float output. */
|
||||
device->FmtType = DevFmtFloat;
|
||||
|
||||
numchans = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
|
||||
for(i = 0;i < numchans;i++)
|
||||
{
|
||||
char name[64];
|
||||
snprintf(name, sizeof(name), "channel_%d", i+1);
|
||||
self->Port[i] = jack_port_register(self->Client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
||||
if(self->Port[i] == NULL)
|
||||
{
|
||||
ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(device->FmtChans));
|
||||
if(i == 0) return ALC_FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i < numchans)
|
||||
{
|
||||
if(i == 1)
|
||||
device->FmtChans = DevFmtMono;
|
||||
else
|
||||
{
|
||||
for(--i;i >= 2;i--)
|
||||
{
|
||||
jack_port_unregister(self->Client, self->Port[i]);
|
||||
self->Port[i] = NULL;
|
||||
}
|
||||
device->FmtChans = DevFmtStereo;
|
||||
}
|
||||
}
|
||||
|
||||
ll_ringbuffer_free(self->Ring);
|
||||
self->Ring = ll_ringbuffer_create(bufsize,
|
||||
FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder),
|
||||
true
|
||||
);
|
||||
if(!self->Ring)
|
||||
{
|
||||
ERR("Failed to allocate ringbuffer\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
SetDefaultChannelOrder(device);
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCjackPlayback_start(ALCjackPlayback *self)
|
||||
{
|
||||
const char **ports;
|
||||
ALsizei i;
|
||||
|
||||
if(jack_activate(self->Client))
|
||||
{
|
||||
ERR("Failed to activate client\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
ports = jack_get_ports(self->Client, NULL, NULL, JackPortIsPhysical|JackPortIsInput);
|
||||
if(ports == NULL)
|
||||
{
|
||||
ERR("No physical playback ports found\n");
|
||||
jack_deactivate(self->Client);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
for(i = 0;i < MAX_OUTPUT_CHANNELS && self->Port[i];i++)
|
||||
{
|
||||
if(!ports[i])
|
||||
{
|
||||
ERR("No physical playback port for \"%s\"\n", jack_port_name(self->Port[i]));
|
||||
break;
|
||||
}
|
||||
if(jack_connect(self->Client, jack_port_name(self->Port[i]), ports[i]))
|
||||
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(self->Port[i]), ports[i]);
|
||||
}
|
||||
jack_free(ports);
|
||||
|
||||
ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
|
||||
if(althrd_create(&self->thread, ALCjackPlayback_mixerProc, self) != althrd_success)
|
||||
{
|
||||
jack_deactivate(self->Client);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCjackPlayback_stop(ALCjackPlayback *self)
|
||||
{
|
||||
int res;
|
||||
|
||||
if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
|
||||
return;
|
||||
|
||||
alsem_post(&self->Sem);
|
||||
althrd_join(self->thread, &res);
|
||||
|
||||
jack_deactivate(self->Client);
|
||||
}
|
||||
|
||||
|
||||
static ClockLatency ALCjackPlayback_getClockLatency(ALCjackPlayback *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
ClockLatency ret;
|
||||
|
||||
ALCjackPlayback_lock(self);
|
||||
ret.ClockTime = GetDeviceClockTime(device);
|
||||
ret.Latency = ll_ringbuffer_read_space(self->Ring) * DEVICE_CLOCK_RES /
|
||||
device->Frequency;
|
||||
ALCjackPlayback_unlock(self);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void jack_msg_handler(const char *message)
|
||||
{
|
||||
WARN("%s\n", message);
|
||||
}
|
||||
|
||||
typedef struct ALCjackBackendFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} ALCjackBackendFactory;
|
||||
#define ALCJACKBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCjackBackendFactory, ALCbackendFactory) } }
|
||||
|
||||
static ALCboolean ALCjackBackendFactory_init(ALCjackBackendFactory* UNUSED(self))
|
||||
{
|
||||
void (*old_error_cb)(const char*);
|
||||
jack_client_t *client;
|
||||
jack_status_t status;
|
||||
|
||||
if(!jack_load())
|
||||
return ALC_FALSE;
|
||||
|
||||
if(!GetConfigValueBool(NULL, "jack", "spawn-server", 0))
|
||||
ClientOptions |= JackNoStartServer;
|
||||
|
||||
old_error_cb = (&jack_error_callback ? jack_error_callback : NULL);
|
||||
jack_set_error_function(jack_msg_handler);
|
||||
client = jack_client_open("alsoft", ClientOptions, &status, NULL);
|
||||
jack_set_error_function(old_error_cb);
|
||||
if(client == NULL)
|
||||
{
|
||||
WARN("jack_client_open() failed, 0x%02x\n", status);
|
||||
if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
|
||||
ERR("Unable to connect to JACK server\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
jack_client_close(client);
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCjackBackendFactory_deinit(ALCjackBackendFactory* UNUSED(self))
|
||||
{
|
||||
#ifdef HAVE_DYNLOAD
|
||||
if(jack_handle)
|
||||
CloseLib(jack_handle);
|
||||
jack_handle = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static ALCboolean ALCjackBackendFactory_querySupport(ALCjackBackendFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
return ALC_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void ALCjackBackendFactory_probe(ALCjackBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case ALL_DEVICE_PROBE:
|
||||
alstr_append_range(outnames, jackDevice, jackDevice+sizeof(jackDevice));
|
||||
break;
|
||||
|
||||
case CAPTURE_DEVICE_PROBE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static ALCbackend* ALCjackBackendFactory_createBackend(ALCjackBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
{
|
||||
ALCjackPlayback *backend;
|
||||
NEW_OBJ(backend, ALCjackPlayback)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(ALCjackBackendFactory);
|
||||
|
||||
|
||||
ALCbackendFactory *ALCjackBackendFactory_getFactory(void)
|
||||
{
|
||||
static ALCjackBackendFactory factory = ALCJACKBACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2011 by Chris Robinson
|
||||
* 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 <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
|
||||
typedef struct ALCloopback {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
} ALCloopback;
|
||||
|
||||
static void ALCloopback_Construct(ALCloopback *self, ALCdevice *device);
|
||||
static DECLARE_FORWARD(ALCloopback, ALCbackend, void, Destruct)
|
||||
static ALCenum ALCloopback_open(ALCloopback *self, const ALCchar *name);
|
||||
static ALCboolean ALCloopback_reset(ALCloopback *self);
|
||||
static ALCboolean ALCloopback_start(ALCloopback *self);
|
||||
static void ALCloopback_stop(ALCloopback *self);
|
||||
static DECLARE_FORWARD2(ALCloopback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
|
||||
static DECLARE_FORWARD(ALCloopback, ALCbackend, ALCuint, availableSamples)
|
||||
static DECLARE_FORWARD(ALCloopback, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCloopback, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCloopback, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCloopback)
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCloopback);
|
||||
|
||||
|
||||
static void ALCloopback_Construct(ALCloopback *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCloopback, ALCbackend, self);
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCloopback_open(ALCloopback *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean ALCloopback_reset(ALCloopback *self)
|
||||
{
|
||||
SetDefaultWFXChannelOrder(STATIC_CAST(ALCbackend, self)->mDevice);
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCloopback_start(ALCloopback* UNUSED(self))
|
||||
{
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCloopback_stop(ALCloopback* UNUSED(self))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCloopbackFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} ALCloopbackFactory;
|
||||
#define ALCNULLBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCloopbackFactory, ALCbackendFactory) } }
|
||||
|
||||
ALCbackendFactory *ALCloopbackFactory_getFactory(void);
|
||||
static ALCboolean ALCloopbackFactory_init(ALCloopbackFactory *self);
|
||||
static DECLARE_FORWARD(ALCloopbackFactory, ALCbackendFactory, void, deinit)
|
||||
static ALCboolean ALCloopbackFactory_querySupport(ALCloopbackFactory *self, ALCbackend_Type type);
|
||||
static void ALCloopbackFactory_probe(ALCloopbackFactory *self, enum DevProbe type, al_string *outnames);
|
||||
static ALCbackend* ALCloopbackFactory_createBackend(ALCloopbackFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(ALCloopbackFactory);
|
||||
|
||||
|
||||
ALCbackendFactory *ALCloopbackFactory_getFactory(void)
|
||||
{
|
||||
static ALCloopbackFactory factory = ALCNULLBACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
|
||||
static ALCboolean ALCloopbackFactory_init(ALCloopbackFactory* UNUSED(self))
|
||||
{
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCloopbackFactory_querySupport(ALCloopbackFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Loopback)
|
||||
return ALC_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void ALCloopbackFactory_probe(ALCloopbackFactory* UNUSED(self), enum DevProbe UNUSED(type), al_string* UNUSED(outnames))
|
||||
{
|
||||
}
|
||||
|
||||
static ALCbackend* ALCloopbackFactory_createBackend(ALCloopbackFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Loopback)
|
||||
{
|
||||
ALCloopback *backend;
|
||||
NEW_OBJ(backend, ALCloopback)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2010 by Chris Robinson
|
||||
* 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 <stdlib.h>
|
||||
#ifdef HAVE_WINDOWS_H
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "threads.h"
|
||||
#include "compat.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
|
||||
typedef struct ALCnullBackend {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
ATOMIC(int) killNow;
|
||||
althrd_t thread;
|
||||
} ALCnullBackend;
|
||||
|
||||
static int ALCnullBackend_mixerProc(void *ptr);
|
||||
|
||||
static void ALCnullBackend_Construct(ALCnullBackend *self, ALCdevice *device);
|
||||
static DECLARE_FORWARD(ALCnullBackend, ALCbackend, void, Destruct)
|
||||
static ALCenum ALCnullBackend_open(ALCnullBackend *self, const ALCchar *name);
|
||||
static ALCboolean ALCnullBackend_reset(ALCnullBackend *self);
|
||||
static ALCboolean ALCnullBackend_start(ALCnullBackend *self);
|
||||
static void ALCnullBackend_stop(ALCnullBackend *self);
|
||||
static DECLARE_FORWARD2(ALCnullBackend, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
|
||||
static DECLARE_FORWARD(ALCnullBackend, ALCbackend, ALCuint, availableSamples)
|
||||
static DECLARE_FORWARD(ALCnullBackend, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCnullBackend, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCnullBackend, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCnullBackend)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCnullBackend);
|
||||
|
||||
|
||||
static const ALCchar nullDevice[] = "No Output";
|
||||
|
||||
|
||||
static void ALCnullBackend_Construct(ALCnullBackend *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCnullBackend, ALCbackend, self);
|
||||
|
||||
ATOMIC_INIT(&self->killNow, AL_TRUE);
|
||||
}
|
||||
|
||||
|
||||
static int ALCnullBackend_mixerProc(void *ptr)
|
||||
{
|
||||
ALCnullBackend *self = (ALCnullBackend*)ptr;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
struct timespec now, start;
|
||||
ALuint64 avail, done;
|
||||
const long restTime = (long)((ALuint64)device->UpdateSize * 1000000000 /
|
||||
device->Frequency / 2);
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(althrd_current(), MIXER_THREAD_NAME);
|
||||
|
||||
done = 0;
|
||||
if(altimespec_get(&start, AL_TIME_UTC) != AL_TIME_UTC)
|
||||
{
|
||||
ERR("Failed to get starting time\n");
|
||||
return 1;
|
||||
}
|
||||
while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
|
||||
ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
|
||||
{
|
||||
if(altimespec_get(&now, AL_TIME_UTC) != AL_TIME_UTC)
|
||||
{
|
||||
ERR("Failed to get current time\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
avail = (now.tv_sec - start.tv_sec) * device->Frequency;
|
||||
avail += (ALint64)(now.tv_nsec - start.tv_nsec) * device->Frequency / 1000000000;
|
||||
if(avail < done)
|
||||
{
|
||||
/* Oops, time skipped backwards. Reset the number of samples done
|
||||
* with one update available since we (likely) just came back from
|
||||
* sleeping. */
|
||||
done = avail - device->UpdateSize;
|
||||
}
|
||||
|
||||
if(avail-done < device->UpdateSize)
|
||||
al_nssleep(restTime);
|
||||
else while(avail-done >= device->UpdateSize)
|
||||
{
|
||||
ALCnullBackend_lock(self);
|
||||
aluMixData(device, NULL, device->UpdateSize);
|
||||
ALCnullBackend_unlock(self);
|
||||
done += device->UpdateSize;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCnullBackend_open(ALCnullBackend *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device;
|
||||
|
||||
if(!name)
|
||||
name = nullDevice;
|
||||
else if(strcmp(name, nullDevice) != 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean ALCnullBackend_reset(ALCnullBackend *self)
|
||||
{
|
||||
SetDefaultWFXChannelOrder(STATIC_CAST(ALCbackend, self)->mDevice);
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCnullBackend_start(ALCnullBackend *self)
|
||||
{
|
||||
ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
|
||||
if(althrd_create(&self->thread, ALCnullBackend_mixerProc, self) != althrd_success)
|
||||
return ALC_FALSE;
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCnullBackend_stop(ALCnullBackend *self)
|
||||
{
|
||||
int res;
|
||||
|
||||
if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
|
||||
return;
|
||||
althrd_join(self->thread, &res);
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCnullBackendFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} ALCnullBackendFactory;
|
||||
#define ALCNULLBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCnullBackendFactory, ALCbackendFactory) } }
|
||||
|
||||
ALCbackendFactory *ALCnullBackendFactory_getFactory(void);
|
||||
|
||||
static ALCboolean ALCnullBackendFactory_init(ALCnullBackendFactory *self);
|
||||
static DECLARE_FORWARD(ALCnullBackendFactory, ALCbackendFactory, void, deinit)
|
||||
static ALCboolean ALCnullBackendFactory_querySupport(ALCnullBackendFactory *self, ALCbackend_Type type);
|
||||
static void ALCnullBackendFactory_probe(ALCnullBackendFactory *self, enum DevProbe type, al_string *outnames);
|
||||
static ALCbackend* ALCnullBackendFactory_createBackend(ALCnullBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(ALCnullBackendFactory);
|
||||
|
||||
|
||||
ALCbackendFactory *ALCnullBackendFactory_getFactory(void)
|
||||
{
|
||||
static ALCnullBackendFactory factory = ALCNULLBACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
|
||||
|
||||
static ALCboolean ALCnullBackendFactory_init(ALCnullBackendFactory* UNUSED(self))
|
||||
{
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCnullBackendFactory_querySupport(ALCnullBackendFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
return ALC_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void ALCnullBackendFactory_probe(ALCnullBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case ALL_DEVICE_PROBE:
|
||||
case CAPTURE_DEVICE_PROBE:
|
||||
alstr_append_range(outnames, nullDevice, nullDevice+sizeof(nullDevice));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static ALCbackend* ALCnullBackendFactory_createBackend(ALCnullBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
{
|
||||
ALCnullBackend *backend;
|
||||
NEW_OBJ(backend, ALCnullBackend)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,869 @@
|
||||
/**
|
||||
* 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 <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <memory.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "threads.h"
|
||||
#include "compat.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
#include <sys/soundcard.h>
|
||||
|
||||
/*
|
||||
* The OSS documentation talks about SOUND_MIXER_READ, but the header
|
||||
* only contains MIXER_READ. Play safe. Same for WRITE.
|
||||
*/
|
||||
#ifndef SOUND_MIXER_READ
|
||||
#define SOUND_MIXER_READ MIXER_READ
|
||||
#endif
|
||||
#ifndef SOUND_MIXER_WRITE
|
||||
#define SOUND_MIXER_WRITE MIXER_WRITE
|
||||
#endif
|
||||
|
||||
#if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
|
||||
#define ALC_OSS_COMPAT
|
||||
#endif
|
||||
#ifndef SNDCTL_AUDIOINFO
|
||||
#define ALC_OSS_COMPAT
|
||||
#endif
|
||||
|
||||
/*
|
||||
* FreeBSD strongly discourages the use of specific devices,
|
||||
* such as those returned in oss_audioinfo.devnode
|
||||
*/
|
||||
#ifdef __FreeBSD__
|
||||
#define ALC_OSS_DEVNODE_TRUC
|
||||
#endif
|
||||
|
||||
struct oss_device {
|
||||
const ALCchar *handle;
|
||||
const char *path;
|
||||
struct oss_device *next;
|
||||
};
|
||||
|
||||
static struct oss_device oss_playback = {
|
||||
"OSS Default",
|
||||
"/dev/dsp",
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct oss_device oss_capture = {
|
||||
"OSS Default",
|
||||
"/dev/dsp",
|
||||
NULL
|
||||
};
|
||||
|
||||
#ifdef ALC_OSS_COMPAT
|
||||
|
||||
#define DSP_CAP_OUTPUT 0x00020000
|
||||
#define DSP_CAP_INPUT 0x00010000
|
||||
static void ALCossListPopulate(struct oss_device *UNUSED(devlist), int UNUSED(type_flag))
|
||||
{
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#ifndef HAVE_STRNLEN
|
||||
static size_t strnlen(const char *str, size_t maxlen)
|
||||
{
|
||||
const char *end = memchr(str, 0, maxlen);
|
||||
if(!end) return maxlen;
|
||||
return end - str;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ALCossListAppend(struct oss_device *list, const char *handle, size_t hlen, const char *path, size_t plen)
|
||||
{
|
||||
struct oss_device *next;
|
||||
struct oss_device *last;
|
||||
size_t i;
|
||||
|
||||
/* skip the first item "OSS Default" */
|
||||
last = list;
|
||||
next = list->next;
|
||||
#ifdef ALC_OSS_DEVNODE_TRUC
|
||||
for(i = 0;i < plen;i++)
|
||||
{
|
||||
if(path[i] == '.')
|
||||
{
|
||||
if(strncmp(path + i, handle + hlen + i - plen, plen - i) == 0)
|
||||
hlen = hlen + i - plen;
|
||||
plen = i;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)i;
|
||||
#endif
|
||||
if(handle[0] == '\0')
|
||||
{
|
||||
handle = path;
|
||||
hlen = plen;
|
||||
}
|
||||
|
||||
while(next != NULL)
|
||||
{
|
||||
if(strncmp(next->path, path, plen) == 0)
|
||||
return;
|
||||
last = next;
|
||||
next = next->next;
|
||||
}
|
||||
|
||||
next = (struct oss_device*)malloc(sizeof(struct oss_device) + hlen + plen + 2);
|
||||
next->handle = (char*)(next + 1);
|
||||
next->path = next->handle + hlen + 1;
|
||||
next->next = NULL;
|
||||
last->next = next;
|
||||
|
||||
strncpy((char*)next->handle, handle, hlen);
|
||||
((char*)next->handle)[hlen] = '\0';
|
||||
strncpy((char*)next->path, path, plen);
|
||||
((char*)next->path)[plen] = '\0';
|
||||
|
||||
TRACE("Got device \"%s\", \"%s\"\n", next->handle, next->path);
|
||||
}
|
||||
|
||||
static void ALCossListPopulate(struct oss_device *devlist, int type_flag)
|
||||
{
|
||||
struct oss_sysinfo si;
|
||||
struct oss_audioinfo ai;
|
||||
int fd, i;
|
||||
|
||||
if((fd=open("/dev/mixer", O_RDONLY)) < 0)
|
||||
{
|
||||
TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
|
||||
{
|
||||
TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
for(i = 0;i < si.numaudios;i++)
|
||||
{
|
||||
const char *handle;
|
||||
size_t len;
|
||||
|
||||
ai.dev = i;
|
||||
if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
|
||||
{
|
||||
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
if(ai.devnode[0] == '\0')
|
||||
continue;
|
||||
|
||||
if(ai.handle[0] != '\0')
|
||||
{
|
||||
len = strnlen(ai.handle, sizeof(ai.handle));
|
||||
handle = ai.handle;
|
||||
}
|
||||
else
|
||||
{
|
||||
len = strnlen(ai.name, sizeof(ai.name));
|
||||
handle = ai.name;
|
||||
}
|
||||
if((ai.caps&type_flag))
|
||||
ALCossListAppend(devlist, handle, len, ai.devnode,
|
||||
strnlen(ai.devnode, sizeof(ai.devnode)));
|
||||
}
|
||||
|
||||
done:
|
||||
close(fd);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void ALCossListFree(struct oss_device *list)
|
||||
{
|
||||
struct oss_device *cur;
|
||||
if(list == NULL)
|
||||
return;
|
||||
|
||||
/* skip the first item "OSS Default" */
|
||||
cur = list->next;
|
||||
list->next = NULL;
|
||||
|
||||
while(cur != NULL)
|
||||
{
|
||||
struct oss_device *next = cur->next;
|
||||
free(cur);
|
||||
cur = next;
|
||||
}
|
||||
}
|
||||
|
||||
static int log2i(ALCuint x)
|
||||
{
|
||||
int y = 0;
|
||||
while (x > 1)
|
||||
{
|
||||
x >>= 1;
|
||||
y++;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
typedef struct ALCplaybackOSS {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
int fd;
|
||||
|
||||
ALubyte *mix_data;
|
||||
int data_size;
|
||||
|
||||
ATOMIC(ALenum) killNow;
|
||||
althrd_t thread;
|
||||
} ALCplaybackOSS;
|
||||
|
||||
static int ALCplaybackOSS_mixerProc(void *ptr);
|
||||
|
||||
static void ALCplaybackOSS_Construct(ALCplaybackOSS *self, ALCdevice *device);
|
||||
static void ALCplaybackOSS_Destruct(ALCplaybackOSS *self);
|
||||
static ALCenum ALCplaybackOSS_open(ALCplaybackOSS *self, const ALCchar *name);
|
||||
static ALCboolean ALCplaybackOSS_reset(ALCplaybackOSS *self);
|
||||
static ALCboolean ALCplaybackOSS_start(ALCplaybackOSS *self);
|
||||
static void ALCplaybackOSS_stop(ALCplaybackOSS *self);
|
||||
static DECLARE_FORWARD2(ALCplaybackOSS, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint)
|
||||
static DECLARE_FORWARD(ALCplaybackOSS, ALCbackend, ALCuint, availableSamples)
|
||||
static DECLARE_FORWARD(ALCplaybackOSS, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCplaybackOSS, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCplaybackOSS, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCplaybackOSS)
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCplaybackOSS);
|
||||
|
||||
|
||||
static int ALCplaybackOSS_mixerProc(void *ptr)
|
||||
{
|
||||
ALCplaybackOSS *self = (ALCplaybackOSS*)ptr;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
struct timeval timeout;
|
||||
ALubyte *write_ptr;
|
||||
ALint frame_size;
|
||||
ALint to_write;
|
||||
ssize_t wrote;
|
||||
fd_set wfds;
|
||||
int sret;
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(althrd_current(), MIXER_THREAD_NAME);
|
||||
|
||||
frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
|
||||
ALCplaybackOSS_lock(self);
|
||||
while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
|
||||
ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
|
||||
{
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(self->fd, &wfds);
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
ALCplaybackOSS_unlock(self);
|
||||
sret = select(self->fd+1, NULL, &wfds, NULL, &timeout);
|
||||
ALCplaybackOSS_lock(self);
|
||||
if(sret < 0)
|
||||
{
|
||||
if(errno == EINTR)
|
||||
continue;
|
||||
ERR("select failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
else if(sret == 0)
|
||||
{
|
||||
WARN("select timeout\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
write_ptr = self->mix_data;
|
||||
to_write = self->data_size;
|
||||
aluMixData(device, write_ptr, to_write/frame_size);
|
||||
while(to_write > 0 && !ATOMIC_LOAD_SEQ(&self->killNow))
|
||||
{
|
||||
wrote = write(self->fd, write_ptr, to_write);
|
||||
if(wrote < 0)
|
||||
{
|
||||
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
continue;
|
||||
ERR("write failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(device, "Failed writing playback samples: %s",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
to_write -= wrote;
|
||||
write_ptr += wrote;
|
||||
}
|
||||
}
|
||||
ALCplaybackOSS_unlock(self);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void ALCplaybackOSS_Construct(ALCplaybackOSS *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCplaybackOSS, ALCbackend, self);
|
||||
|
||||
self->fd = -1;
|
||||
ATOMIC_INIT(&self->killNow, AL_FALSE);
|
||||
}
|
||||
|
||||
static void ALCplaybackOSS_Destruct(ALCplaybackOSS *self)
|
||||
{
|
||||
if(self->fd != -1)
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
static ALCenum ALCplaybackOSS_open(ALCplaybackOSS *self, const ALCchar *name)
|
||||
{
|
||||
struct oss_device *dev = &oss_playback;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
|
||||
if(!name || strcmp(name, dev->handle) == 0)
|
||||
name = dev->handle;
|
||||
else
|
||||
{
|
||||
if(!dev->next)
|
||||
{
|
||||
ALCossListPopulate(&oss_playback, DSP_CAP_OUTPUT);
|
||||
dev = &oss_playback;
|
||||
}
|
||||
while(dev != NULL)
|
||||
{
|
||||
if (strcmp(dev->handle, name) == 0)
|
||||
break;
|
||||
dev = dev->next;
|
||||
}
|
||||
if(dev == NULL)
|
||||
{
|
||||
WARN("Could not find \"%s\" in device list\n", name);
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
self->fd = open(dev->path, O_WRONLY);
|
||||
if(self->fd == -1)
|
||||
{
|
||||
ERR("Could not open %s: %s\n", dev->path, strerror(errno));
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean ALCplaybackOSS_reset(ALCplaybackOSS *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
int numFragmentsLogSize;
|
||||
int log2FragmentSize;
|
||||
unsigned int periods;
|
||||
audio_buf_info info;
|
||||
ALuint frameSize;
|
||||
int numChannels;
|
||||
int ossFormat;
|
||||
int ossSpeed;
|
||||
char *err;
|
||||
|
||||
switch(device->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
ossFormat = AFMT_S8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
ossFormat = AFMT_U8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtUInt:
|
||||
case DevFmtFloat:
|
||||
device->FmtType = DevFmtShort;
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
ossFormat = AFMT_S16_NE;
|
||||
break;
|
||||
}
|
||||
|
||||
periods = device->NumUpdates;
|
||||
numChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
|
||||
ossSpeed = device->Frequency;
|
||||
frameSize = numChannels * BytesFromDevFmt(device->FmtType);
|
||||
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
|
||||
log2FragmentSize = maxi(log2i(device->UpdateSize*frameSize), 4);
|
||||
numFragmentsLogSize = (periods << 16) | log2FragmentSize;
|
||||
|
||||
#define CHECKERR(func) if((func) < 0) { \
|
||||
err = #func; \
|
||||
goto err; \
|
||||
}
|
||||
/* Don't fail if SETFRAGMENT fails. We can handle just about anything
|
||||
* that's reported back via GETOSPACE */
|
||||
ioctl(self->fd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
|
||||
CHECKERR(ioctl(self->fd, SNDCTL_DSP_SETFMT, &ossFormat));
|
||||
CHECKERR(ioctl(self->fd, SNDCTL_DSP_CHANNELS, &numChannels));
|
||||
CHECKERR(ioctl(self->fd, SNDCTL_DSP_SPEED, &ossSpeed));
|
||||
CHECKERR(ioctl(self->fd, SNDCTL_DSP_GETOSPACE, &info));
|
||||
if(0)
|
||||
{
|
||||
err:
|
||||
ERR("%s failed: %s\n", err, strerror(errno));
|
||||
return ALC_FALSE;
|
||||
}
|
||||
#undef CHECKERR
|
||||
|
||||
if((int)ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder) != numChannels)
|
||||
{
|
||||
ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(device->FmtChans), numChannels);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
if(!((ossFormat == AFMT_S8 && device->FmtType == DevFmtByte) ||
|
||||
(ossFormat == AFMT_U8 && device->FmtType == DevFmtUByte) ||
|
||||
(ossFormat == AFMT_S16_NE && device->FmtType == DevFmtShort)))
|
||||
{
|
||||
ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(device->FmtType), ossFormat);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
device->Frequency = ossSpeed;
|
||||
device->UpdateSize = info.fragsize / frameSize;
|
||||
device->NumUpdates = info.fragments;
|
||||
|
||||
SetDefaultChannelOrder(device);
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCplaybackOSS_start(ALCplaybackOSS *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
|
||||
self->data_size = device->UpdateSize * FrameSizeFromDevFmt(
|
||||
device->FmtChans, device->FmtType, device->AmbiOrder
|
||||
);
|
||||
self->mix_data = calloc(1, self->data_size);
|
||||
|
||||
ATOMIC_STORE_SEQ(&self->killNow, AL_FALSE);
|
||||
if(althrd_create(&self->thread, ALCplaybackOSS_mixerProc, self) != althrd_success)
|
||||
{
|
||||
free(self->mix_data);
|
||||
self->mix_data = NULL;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCplaybackOSS_stop(ALCplaybackOSS *self)
|
||||
{
|
||||
int res;
|
||||
|
||||
if(ATOMIC_EXCHANGE_SEQ(&self->killNow, AL_TRUE))
|
||||
return;
|
||||
althrd_join(self->thread, &res);
|
||||
|
||||
if(ioctl(self->fd, SNDCTL_DSP_RESET) != 0)
|
||||
ERR("Error resetting device: %s\n", strerror(errno));
|
||||
|
||||
free(self->mix_data);
|
||||
self->mix_data = NULL;
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCcaptureOSS {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
int fd;
|
||||
|
||||
ll_ringbuffer_t *ring;
|
||||
|
||||
ATOMIC(ALenum) killNow;
|
||||
althrd_t thread;
|
||||
} ALCcaptureOSS;
|
||||
|
||||
static int ALCcaptureOSS_recordProc(void *ptr);
|
||||
|
||||
static void ALCcaptureOSS_Construct(ALCcaptureOSS *self, ALCdevice *device);
|
||||
static void ALCcaptureOSS_Destruct(ALCcaptureOSS *self);
|
||||
static ALCenum ALCcaptureOSS_open(ALCcaptureOSS *self, const ALCchar *name);
|
||||
static DECLARE_FORWARD(ALCcaptureOSS, ALCbackend, ALCboolean, reset)
|
||||
static ALCboolean ALCcaptureOSS_start(ALCcaptureOSS *self);
|
||||
static void ALCcaptureOSS_stop(ALCcaptureOSS *self);
|
||||
static ALCenum ALCcaptureOSS_captureSamples(ALCcaptureOSS *self, ALCvoid *buffer, ALCuint samples);
|
||||
static ALCuint ALCcaptureOSS_availableSamples(ALCcaptureOSS *self);
|
||||
static DECLARE_FORWARD(ALCcaptureOSS, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCcaptureOSS, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCcaptureOSS, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCcaptureOSS)
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCcaptureOSS);
|
||||
|
||||
|
||||
static int ALCcaptureOSS_recordProc(void *ptr)
|
||||
{
|
||||
ALCcaptureOSS *self = (ALCcaptureOSS*)ptr;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
struct timeval timeout;
|
||||
int frame_size;
|
||||
fd_set rfds;
|
||||
ssize_t amt;
|
||||
int sret;
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(althrd_current(), RECORD_THREAD_NAME);
|
||||
|
||||
frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
|
||||
while(!ATOMIC_LOAD_SEQ(&self->killNow))
|
||||
{
|
||||
ll_ringbuffer_data_t vec[2];
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(self->fd, &rfds);
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
sret = select(self->fd+1, &rfds, NULL, NULL, &timeout);
|
||||
if(sret < 0)
|
||||
{
|
||||
if(errno == EINTR)
|
||||
continue;
|
||||
ERR("select failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(device, "Failed to check capture samples: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
else if(sret == 0)
|
||||
{
|
||||
WARN("select timeout\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
ll_ringbuffer_get_write_vector(self->ring, vec);
|
||||
if(vec[0].len > 0)
|
||||
{
|
||||
amt = read(self->fd, vec[0].buf, vec[0].len*frame_size);
|
||||
if(amt < 0)
|
||||
{
|
||||
ERR("read failed: %s\n", strerror(errno));
|
||||
ALCcaptureOSS_lock(self);
|
||||
aluHandleDisconnect(device, "Failed reading capture samples: %s", strerror(errno));
|
||||
ALCcaptureOSS_unlock(self);
|
||||
break;
|
||||
}
|
||||
ll_ringbuffer_write_advance(self->ring, amt/frame_size);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void ALCcaptureOSS_Construct(ALCcaptureOSS *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCcaptureOSS, ALCbackend, self);
|
||||
|
||||
self->fd = -1;
|
||||
self->ring = NULL;
|
||||
ATOMIC_INIT(&self->killNow, AL_FALSE);
|
||||
}
|
||||
|
||||
static void ALCcaptureOSS_Destruct(ALCcaptureOSS *self)
|
||||
{
|
||||
if(self->fd != -1)
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
|
||||
ll_ringbuffer_free(self->ring);
|
||||
self->ring = NULL;
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
static ALCenum ALCcaptureOSS_open(ALCcaptureOSS *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
struct oss_device *dev = &oss_capture;
|
||||
int numFragmentsLogSize;
|
||||
int log2FragmentSize;
|
||||
unsigned int periods;
|
||||
audio_buf_info info;
|
||||
ALuint frameSize;
|
||||
int numChannels;
|
||||
int ossFormat;
|
||||
int ossSpeed;
|
||||
char *err;
|
||||
|
||||
if(!name || strcmp(name, dev->handle) == 0)
|
||||
name = dev->handle;
|
||||
else
|
||||
{
|
||||
if(!dev->next)
|
||||
{
|
||||
ALCossListPopulate(&oss_capture, DSP_CAP_INPUT);
|
||||
dev = &oss_capture;
|
||||
}
|
||||
while(dev != NULL)
|
||||
{
|
||||
if (strcmp(dev->handle, name) == 0)
|
||||
break;
|
||||
dev = dev->next;
|
||||
}
|
||||
if(dev == NULL)
|
||||
{
|
||||
WARN("Could not find \"%s\" in device list\n", name);
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
self->fd = open(dev->path, O_RDONLY);
|
||||
if(self->fd == -1)
|
||||
{
|
||||
ERR("Could not open %s: %s\n", dev->path, strerror(errno));
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
switch(device->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(device->FmtType));
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
periods = 4;
|
||||
numChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
|
||||
frameSize = numChannels * BytesFromDevFmt(device->FmtType);
|
||||
ossSpeed = device->Frequency;
|
||||
log2FragmentSize = log2i(device->UpdateSize * device->NumUpdates *
|
||||
frameSize / periods);
|
||||
|
||||
/* according to the OSS spec, 16 bytes are the minimum */
|
||||
if (log2FragmentSize < 4)
|
||||
log2FragmentSize = 4;
|
||||
numFragmentsLogSize = (periods << 16) | log2FragmentSize;
|
||||
|
||||
#define CHECKERR(func) if((func) < 0) { \
|
||||
err = #func; \
|
||||
goto err; \
|
||||
}
|
||||
CHECKERR(ioctl(self->fd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
|
||||
CHECKERR(ioctl(self->fd, SNDCTL_DSP_SETFMT, &ossFormat));
|
||||
CHECKERR(ioctl(self->fd, SNDCTL_DSP_CHANNELS, &numChannels));
|
||||
CHECKERR(ioctl(self->fd, SNDCTL_DSP_SPEED, &ossSpeed));
|
||||
CHECKERR(ioctl(self->fd, SNDCTL_DSP_GETISPACE, &info));
|
||||
if(0)
|
||||
{
|
||||
err:
|
||||
ERR("%s failed: %s\n", err, strerror(errno));
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
#undef CHECKERR
|
||||
|
||||
if((int)ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder) != numChannels)
|
||||
{
|
||||
ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(device->FmtChans), numChannels);
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
if(!((ossFormat == AFMT_S8 && device->FmtType == DevFmtByte) ||
|
||||
(ossFormat == AFMT_U8 && device->FmtType == DevFmtUByte) ||
|
||||
(ossFormat == AFMT_S16_NE && device->FmtType == DevFmtShort)))
|
||||
{
|
||||
ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(device->FmtType), ossFormat);
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
self->ring = ll_ringbuffer_create(device->UpdateSize*device->NumUpdates, frameSize, false);
|
||||
if(!self->ring)
|
||||
{
|
||||
ERR("Ring buffer create failed\n");
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
return ALC_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean ALCcaptureOSS_start(ALCcaptureOSS *self)
|
||||
{
|
||||
ATOMIC_STORE_SEQ(&self->killNow, AL_FALSE);
|
||||
if(althrd_create(&self->thread, ALCcaptureOSS_recordProc, self) != althrd_success)
|
||||
return ALC_FALSE;
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCcaptureOSS_stop(ALCcaptureOSS *self)
|
||||
{
|
||||
int res;
|
||||
|
||||
if(ATOMIC_EXCHANGE_SEQ(&self->killNow, AL_TRUE))
|
||||
return;
|
||||
|
||||
althrd_join(self->thread, &res);
|
||||
|
||||
if(ioctl(self->fd, SNDCTL_DSP_RESET) != 0)
|
||||
ERR("Error resetting device: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
static ALCenum ALCcaptureOSS_captureSamples(ALCcaptureOSS *self, ALCvoid *buffer, ALCuint samples)
|
||||
{
|
||||
ll_ringbuffer_read(self->ring, buffer, samples);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCuint ALCcaptureOSS_availableSamples(ALCcaptureOSS *self)
|
||||
{
|
||||
return ll_ringbuffer_read_space(self->ring);
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCossBackendFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} ALCossBackendFactory;
|
||||
#define ALCOSSBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCossBackendFactory, ALCbackendFactory) } }
|
||||
|
||||
ALCbackendFactory *ALCossBackendFactory_getFactory(void);
|
||||
|
||||
static ALCboolean ALCossBackendFactory_init(ALCossBackendFactory *self);
|
||||
static void ALCossBackendFactory_deinit(ALCossBackendFactory *self);
|
||||
static ALCboolean ALCossBackendFactory_querySupport(ALCossBackendFactory *self, ALCbackend_Type type);
|
||||
static void ALCossBackendFactory_probe(ALCossBackendFactory *self, enum DevProbe type, al_string *outnames);
|
||||
static ALCbackend* ALCossBackendFactory_createBackend(ALCossBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(ALCossBackendFactory);
|
||||
|
||||
|
||||
ALCbackendFactory *ALCossBackendFactory_getFactory(void)
|
||||
{
|
||||
static ALCossBackendFactory factory = ALCOSSBACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
|
||||
|
||||
ALCboolean ALCossBackendFactory_init(ALCossBackendFactory* UNUSED(self))
|
||||
{
|
||||
ConfigValueStr(NULL, "oss", "device", &oss_playback.path);
|
||||
ConfigValueStr(NULL, "oss", "capture", &oss_capture.path);
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
void ALCossBackendFactory_deinit(ALCossBackendFactory* UNUSED(self))
|
||||
{
|
||||
ALCossListFree(&oss_playback);
|
||||
ALCossListFree(&oss_capture);
|
||||
}
|
||||
|
||||
|
||||
ALCboolean ALCossBackendFactory_querySupport(ALCossBackendFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback || type == ALCbackend_Capture)
|
||||
return ALC_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
void ALCossBackendFactory_probe(ALCossBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
|
||||
{
|
||||
struct oss_device *cur = NULL;
|
||||
switch(type)
|
||||
{
|
||||
case ALL_DEVICE_PROBE:
|
||||
ALCossListFree(&oss_playback);
|
||||
ALCossListPopulate(&oss_playback, DSP_CAP_OUTPUT);
|
||||
cur = &oss_playback;
|
||||
break;
|
||||
|
||||
case CAPTURE_DEVICE_PROBE:
|
||||
ALCossListFree(&oss_capture);
|
||||
ALCossListPopulate(&oss_capture, DSP_CAP_INPUT);
|
||||
cur = &oss_capture;
|
||||
break;
|
||||
}
|
||||
while(cur != NULL)
|
||||
{
|
||||
#ifdef HAVE_STAT
|
||||
struct stat buf;
|
||||
if(stat(cur->path, &buf) == 0)
|
||||
#endif
|
||||
alstr_append_range(outnames, cur->handle, cur->handle+strlen(cur->handle)+1);
|
||||
cur = cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
ALCbackend* ALCossBackendFactory_createBackend(ALCossBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
{
|
||||
ALCplaybackOSS *backend;
|
||||
NEW_OBJ(backend, ALCplaybackOSS)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
if(type == ALCbackend_Capture)
|
||||
{
|
||||
ALCcaptureOSS *backend;
|
||||
NEW_OBJ(backend, ALCcaptureOSS)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
/**
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "compat.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
#include <portaudio.h>
|
||||
|
||||
|
||||
static const ALCchar pa_device[] = "PortAudio Default";
|
||||
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
static void *pa_handle;
|
||||
#define MAKE_FUNC(x) static __typeof(x) * p##x
|
||||
MAKE_FUNC(Pa_Initialize);
|
||||
MAKE_FUNC(Pa_Terminate);
|
||||
MAKE_FUNC(Pa_GetErrorText);
|
||||
MAKE_FUNC(Pa_StartStream);
|
||||
MAKE_FUNC(Pa_StopStream);
|
||||
MAKE_FUNC(Pa_OpenStream);
|
||||
MAKE_FUNC(Pa_CloseStream);
|
||||
MAKE_FUNC(Pa_GetDefaultOutputDevice);
|
||||
MAKE_FUNC(Pa_GetDefaultInputDevice);
|
||||
MAKE_FUNC(Pa_GetStreamInfo);
|
||||
#undef MAKE_FUNC
|
||||
|
||||
#define Pa_Initialize pPa_Initialize
|
||||
#define Pa_Terminate pPa_Terminate
|
||||
#define Pa_GetErrorText pPa_GetErrorText
|
||||
#define Pa_StartStream pPa_StartStream
|
||||
#define Pa_StopStream pPa_StopStream
|
||||
#define Pa_OpenStream pPa_OpenStream
|
||||
#define Pa_CloseStream pPa_CloseStream
|
||||
#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
|
||||
#define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
|
||||
#define Pa_GetStreamInfo pPa_GetStreamInfo
|
||||
#endif
|
||||
|
||||
static ALCboolean pa_load(void)
|
||||
{
|
||||
PaError err;
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
if(!pa_handle)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
# define PALIB "portaudio.dll"
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
# define PALIB "libportaudio.2.dylib"
|
||||
#elif defined(__OpenBSD__)
|
||||
# define PALIB "libportaudio.so"
|
||||
#else
|
||||
# define PALIB "libportaudio.so.2"
|
||||
#endif
|
||||
|
||||
pa_handle = LoadLib(PALIB);
|
||||
if(!pa_handle)
|
||||
return ALC_FALSE;
|
||||
|
||||
#define LOAD_FUNC(f) do { \
|
||||
p##f = GetSymbol(pa_handle, #f); \
|
||||
if(p##f == NULL) \
|
||||
{ \
|
||||
CloseLib(pa_handle); \
|
||||
pa_handle = NULL; \
|
||||
return ALC_FALSE; \
|
||||
} \
|
||||
} while(0)
|
||||
LOAD_FUNC(Pa_Initialize);
|
||||
LOAD_FUNC(Pa_Terminate);
|
||||
LOAD_FUNC(Pa_GetErrorText);
|
||||
LOAD_FUNC(Pa_StartStream);
|
||||
LOAD_FUNC(Pa_StopStream);
|
||||
LOAD_FUNC(Pa_OpenStream);
|
||||
LOAD_FUNC(Pa_CloseStream);
|
||||
LOAD_FUNC(Pa_GetDefaultOutputDevice);
|
||||
LOAD_FUNC(Pa_GetDefaultInputDevice);
|
||||
LOAD_FUNC(Pa_GetStreamInfo);
|
||||
#undef LOAD_FUNC
|
||||
|
||||
if((err=Pa_Initialize()) != paNoError)
|
||||
{
|
||||
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
|
||||
CloseLib(pa_handle);
|
||||
pa_handle = NULL;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if((err=Pa_Initialize()) != paNoError)
|
||||
{
|
||||
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
|
||||
return ALC_FALSE;
|
||||
}
|
||||
#endif
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCportPlayback {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
PaStream *stream;
|
||||
PaStreamParameters params;
|
||||
ALuint update_size;
|
||||
} ALCportPlayback;
|
||||
|
||||
static int ALCportPlayback_WriteCallback(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
|
||||
const PaStreamCallbackFlags statusFlags, void *userData);
|
||||
|
||||
static void ALCportPlayback_Construct(ALCportPlayback *self, ALCdevice *device);
|
||||
static void ALCportPlayback_Destruct(ALCportPlayback *self);
|
||||
static ALCenum ALCportPlayback_open(ALCportPlayback *self, const ALCchar *name);
|
||||
static ALCboolean ALCportPlayback_reset(ALCportPlayback *self);
|
||||
static ALCboolean ALCportPlayback_start(ALCportPlayback *self);
|
||||
static void ALCportPlayback_stop(ALCportPlayback *self);
|
||||
static DECLARE_FORWARD2(ALCportPlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint)
|
||||
static DECLARE_FORWARD(ALCportPlayback, ALCbackend, ALCuint, availableSamples)
|
||||
static DECLARE_FORWARD(ALCportPlayback, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCportPlayback, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCportPlayback, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCportPlayback)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCportPlayback);
|
||||
|
||||
|
||||
static void ALCportPlayback_Construct(ALCportPlayback *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCportPlayback, ALCbackend, self);
|
||||
|
||||
self->stream = NULL;
|
||||
}
|
||||
|
||||
static void ALCportPlayback_Destruct(ALCportPlayback *self)
|
||||
{
|
||||
PaError err = self->stream ? Pa_CloseStream(self->stream) : paNoError;
|
||||
if(err != paNoError)
|
||||
ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
|
||||
self->stream = NULL;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
static int ALCportPlayback_WriteCallback(const void *UNUSED(inputBuffer), void *outputBuffer,
|
||||
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *UNUSED(timeInfo),
|
||||
const PaStreamCallbackFlags UNUSED(statusFlags), void *userData)
|
||||
{
|
||||
ALCportPlayback *self = userData;
|
||||
|
||||
ALCportPlayback_lock(self);
|
||||
aluMixData(STATIC_CAST(ALCbackend, self)->mDevice, outputBuffer, framesPerBuffer);
|
||||
ALCportPlayback_unlock(self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCportPlayback_open(ALCportPlayback *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
PaError err;
|
||||
|
||||
if(!name)
|
||||
name = pa_device;
|
||||
else if(strcmp(name, pa_device) != 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
self->update_size = device->UpdateSize;
|
||||
|
||||
self->params.device = -1;
|
||||
if(!ConfigValueInt(NULL, "port", "device", &self->params.device) ||
|
||||
self->params.device < 0)
|
||||
self->params.device = Pa_GetDefaultOutputDevice();
|
||||
self->params.suggestedLatency = (device->UpdateSize*device->NumUpdates) /
|
||||
(float)device->Frequency;
|
||||
self->params.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
self->params.channelCount = ((device->FmtChans == DevFmtMono) ? 1 : 2);
|
||||
|
||||
switch(device->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
self->params.sampleFormat = paInt8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
self->params.sampleFormat = paUInt8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
self->params.sampleFormat = paInt16;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
/* fall-through */
|
||||
case DevFmtInt:
|
||||
self->params.sampleFormat = paInt32;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
self->params.sampleFormat = paFloat32;
|
||||
break;
|
||||
}
|
||||
|
||||
retry_open:
|
||||
err = Pa_OpenStream(&self->stream, NULL, &self->params,
|
||||
device->Frequency, device->UpdateSize, paNoFlag,
|
||||
ALCportPlayback_WriteCallback, self
|
||||
);
|
||||
if(err != paNoError)
|
||||
{
|
||||
if(self->params.sampleFormat == paFloat32)
|
||||
{
|
||||
self->params.sampleFormat = paInt16;
|
||||
goto retry_open;
|
||||
}
|
||||
ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err));
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
|
||||
}
|
||||
|
||||
static ALCboolean ALCportPlayback_reset(ALCportPlayback *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
const PaStreamInfo *streamInfo;
|
||||
|
||||
streamInfo = Pa_GetStreamInfo(self->stream);
|
||||
device->Frequency = streamInfo->sampleRate;
|
||||
device->UpdateSize = self->update_size;
|
||||
|
||||
if(self->params.sampleFormat == paInt8)
|
||||
device->FmtType = DevFmtByte;
|
||||
else if(self->params.sampleFormat == paUInt8)
|
||||
device->FmtType = DevFmtUByte;
|
||||
else if(self->params.sampleFormat == paInt16)
|
||||
device->FmtType = DevFmtShort;
|
||||
else if(self->params.sampleFormat == paInt32)
|
||||
device->FmtType = DevFmtInt;
|
||||
else if(self->params.sampleFormat == paFloat32)
|
||||
device->FmtType = DevFmtFloat;
|
||||
else
|
||||
{
|
||||
ERR("Unexpected sample format: 0x%lx\n", self->params.sampleFormat);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
if(self->params.channelCount == 2)
|
||||
device->FmtChans = DevFmtStereo;
|
||||
else if(self->params.channelCount == 1)
|
||||
device->FmtChans = DevFmtMono;
|
||||
else
|
||||
{
|
||||
ERR("Unexpected channel count: %u\n", self->params.channelCount);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
SetDefaultChannelOrder(device);
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCportPlayback_start(ALCportPlayback *self)
|
||||
{
|
||||
PaError err;
|
||||
|
||||
err = Pa_StartStream(self->stream);
|
||||
if(err != paNoError)
|
||||
{
|
||||
ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err));
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCportPlayback_stop(ALCportPlayback *self)
|
||||
{
|
||||
PaError err = Pa_StopStream(self->stream);
|
||||
if(err != paNoError)
|
||||
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCportCapture {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
PaStream *stream;
|
||||
PaStreamParameters params;
|
||||
|
||||
ll_ringbuffer_t *ring;
|
||||
} ALCportCapture;
|
||||
|
||||
static int ALCportCapture_ReadCallback(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
|
||||
const PaStreamCallbackFlags statusFlags, void *userData);
|
||||
|
||||
static void ALCportCapture_Construct(ALCportCapture *self, ALCdevice *device);
|
||||
static void ALCportCapture_Destruct(ALCportCapture *self);
|
||||
static ALCenum ALCportCapture_open(ALCportCapture *self, const ALCchar *name);
|
||||
static DECLARE_FORWARD(ALCportCapture, ALCbackend, ALCboolean, reset)
|
||||
static ALCboolean ALCportCapture_start(ALCportCapture *self);
|
||||
static void ALCportCapture_stop(ALCportCapture *self);
|
||||
static ALCenum ALCportCapture_captureSamples(ALCportCapture *self, ALCvoid *buffer, ALCuint samples);
|
||||
static ALCuint ALCportCapture_availableSamples(ALCportCapture *self);
|
||||
static DECLARE_FORWARD(ALCportCapture, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCportCapture, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCportCapture, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCportCapture)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCportCapture);
|
||||
|
||||
|
||||
static void ALCportCapture_Construct(ALCportCapture *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCportCapture, ALCbackend, self);
|
||||
|
||||
self->stream = NULL;
|
||||
self->ring = NULL;
|
||||
}
|
||||
|
||||
static void ALCportCapture_Destruct(ALCportCapture *self)
|
||||
{
|
||||
PaError err = self->stream ? Pa_CloseStream(self->stream) : paNoError;
|
||||
if(err != paNoError)
|
||||
ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
|
||||
self->stream = NULL;
|
||||
|
||||
ll_ringbuffer_free(self->ring);
|
||||
self->ring = NULL;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
static int ALCportCapture_ReadCallback(const void *inputBuffer, void *UNUSED(outputBuffer),
|
||||
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *UNUSED(timeInfo),
|
||||
const PaStreamCallbackFlags UNUSED(statusFlags), void *userData)
|
||||
{
|
||||
ALCportCapture *self = userData;
|
||||
size_t writable = ll_ringbuffer_write_space(self->ring);
|
||||
|
||||
if(framesPerBuffer > writable)
|
||||
framesPerBuffer = writable;
|
||||
ll_ringbuffer_write(self->ring, inputBuffer, framesPerBuffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCportCapture_open(ALCportCapture *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
ALuint samples, frame_size;
|
||||
PaError err;
|
||||
|
||||
if(!name)
|
||||
name = pa_device;
|
||||
else if(strcmp(name, pa_device) != 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
samples = device->UpdateSize * device->NumUpdates;
|
||||
samples = maxu(samples, 100 * device->Frequency / 1000);
|
||||
frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
|
||||
self->ring = ll_ringbuffer_create(samples, frame_size, false);
|
||||
if(self->ring == NULL) return ALC_INVALID_VALUE;
|
||||
|
||||
self->params.device = -1;
|
||||
if(!ConfigValueInt(NULL, "port", "capture", &self->params.device) ||
|
||||
self->params.device < 0)
|
||||
self->params.device = Pa_GetDefaultInputDevice();
|
||||
self->params.suggestedLatency = 0.0f;
|
||||
self->params.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
switch(device->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
self->params.sampleFormat = paInt8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
self->params.sampleFormat = paUInt8;
|
||||
break;
|
||||
case DevFmtShort:
|
||||
self->params.sampleFormat = paInt16;
|
||||
break;
|
||||
case DevFmtInt:
|
||||
self->params.sampleFormat = paInt32;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
self->params.sampleFormat = paFloat32;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
case DevFmtUShort:
|
||||
ERR("%s samples not supported\n", DevFmtTypeString(device->FmtType));
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
self->params.channelCount = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
|
||||
|
||||
err = Pa_OpenStream(&self->stream, &self->params, NULL,
|
||||
device->Frequency, paFramesPerBufferUnspecified, paNoFlag,
|
||||
ALCportCapture_ReadCallback, self
|
||||
);
|
||||
if(err != paNoError)
|
||||
{
|
||||
ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err));
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ALCboolean ALCportCapture_start(ALCportCapture *self)
|
||||
{
|
||||
PaError err = Pa_StartStream(self->stream);
|
||||
if(err != paNoError)
|
||||
{
|
||||
ERR("Error starting stream: %s\n", Pa_GetErrorText(err));
|
||||
return ALC_FALSE;
|
||||
}
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCportCapture_stop(ALCportCapture *self)
|
||||
{
|
||||
PaError err = Pa_StopStream(self->stream);
|
||||
if(err != paNoError)
|
||||
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
|
||||
}
|
||||
|
||||
|
||||
static ALCuint ALCportCapture_availableSamples(ALCportCapture *self)
|
||||
{
|
||||
return ll_ringbuffer_read_space(self->ring);
|
||||
}
|
||||
|
||||
static ALCenum ALCportCapture_captureSamples(ALCportCapture *self, ALCvoid *buffer, ALCuint samples)
|
||||
{
|
||||
ll_ringbuffer_read(self->ring, buffer, samples);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCportBackendFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} ALCportBackendFactory;
|
||||
#define ALCPORTBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCportBackendFactory, ALCbackendFactory) } }
|
||||
|
||||
static ALCboolean ALCportBackendFactory_init(ALCportBackendFactory *self);
|
||||
static void ALCportBackendFactory_deinit(ALCportBackendFactory *self);
|
||||
static ALCboolean ALCportBackendFactory_querySupport(ALCportBackendFactory *self, ALCbackend_Type type);
|
||||
static void ALCportBackendFactory_probe(ALCportBackendFactory *self, enum DevProbe type, al_string *outnames);
|
||||
static ALCbackend* ALCportBackendFactory_createBackend(ALCportBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(ALCportBackendFactory);
|
||||
|
||||
|
||||
static ALCboolean ALCportBackendFactory_init(ALCportBackendFactory* UNUSED(self))
|
||||
{
|
||||
if(!pa_load())
|
||||
return ALC_FALSE;
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCportBackendFactory_deinit(ALCportBackendFactory* UNUSED(self))
|
||||
{
|
||||
#ifdef HAVE_DYNLOAD
|
||||
if(pa_handle)
|
||||
{
|
||||
Pa_Terminate();
|
||||
CloseLib(pa_handle);
|
||||
pa_handle = NULL;
|
||||
}
|
||||
#else
|
||||
Pa_Terminate();
|
||||
#endif
|
||||
}
|
||||
|
||||
static ALCboolean ALCportBackendFactory_querySupport(ALCportBackendFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback || type == ALCbackend_Capture)
|
||||
return ALC_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void ALCportBackendFactory_probe(ALCportBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case ALL_DEVICE_PROBE:
|
||||
case CAPTURE_DEVICE_PROBE:
|
||||
alstr_append_range(outnames, pa_device, pa_device+sizeof(pa_device));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static ALCbackend* ALCportBackendFactory_createBackend(ALCportBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
{
|
||||
ALCportPlayback *backend;
|
||||
NEW_OBJ(backend, ALCportPlayback)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
if(type == ALCbackend_Capture)
|
||||
{
|
||||
ALCportCapture *backend;
|
||||
NEW_OBJ(backend, ALCportCapture)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ALCbackendFactory *ALCportBackendFactory_getFactory(void)
|
||||
{
|
||||
static ALCportBackendFactory factory = ALCPORTBACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+1068
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 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 <stdlib.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "threads.h"
|
||||
#include "compat.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DEVNAME_PREFIX "OpenAL Soft on "
|
||||
#else
|
||||
#define DEVNAME_PREFIX ""
|
||||
#endif
|
||||
|
||||
typedef struct ALCsdl2Backend {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
SDL_AudioDeviceID deviceID;
|
||||
ALsizei frameSize;
|
||||
|
||||
ALuint Frequency;
|
||||
enum DevFmtChannels FmtChans;
|
||||
enum DevFmtType FmtType;
|
||||
ALuint UpdateSize;
|
||||
} ALCsdl2Backend;
|
||||
|
||||
static void ALCsdl2Backend_Construct(ALCsdl2Backend *self, ALCdevice *device);
|
||||
static void ALCsdl2Backend_Destruct(ALCsdl2Backend *self);
|
||||
static ALCenum ALCsdl2Backend_open(ALCsdl2Backend *self, const ALCchar *name);
|
||||
static ALCboolean ALCsdl2Backend_reset(ALCsdl2Backend *self);
|
||||
static ALCboolean ALCsdl2Backend_start(ALCsdl2Backend *self);
|
||||
static void ALCsdl2Backend_stop(ALCsdl2Backend *self);
|
||||
static DECLARE_FORWARD2(ALCsdl2Backend, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
|
||||
static DECLARE_FORWARD(ALCsdl2Backend, ALCbackend, ALCuint, availableSamples)
|
||||
static DECLARE_FORWARD(ALCsdl2Backend, ALCbackend, ClockLatency, getClockLatency)
|
||||
static void ALCsdl2Backend_lock(ALCsdl2Backend *self);
|
||||
static void ALCsdl2Backend_unlock(ALCsdl2Backend *self);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCsdl2Backend)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCsdl2Backend);
|
||||
|
||||
static const ALCchar defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
|
||||
|
||||
static void ALCsdl2Backend_Construct(ALCsdl2Backend *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCsdl2Backend, ALCbackend, self);
|
||||
|
||||
self->deviceID = 0;
|
||||
self->frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
self->Frequency = device->Frequency;
|
||||
self->FmtChans = device->FmtChans;
|
||||
self->FmtType = device->FmtType;
|
||||
self->UpdateSize = device->UpdateSize;
|
||||
}
|
||||
|
||||
static void ALCsdl2Backend_Destruct(ALCsdl2Backend *self)
|
||||
{
|
||||
if(self->deviceID)
|
||||
SDL_CloseAudioDevice(self->deviceID);
|
||||
self->deviceID = 0;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
static void ALCsdl2Backend_audioCallback(void *ptr, Uint8 *stream, int len)
|
||||
{
|
||||
ALCsdl2Backend *self = (ALCsdl2Backend*)ptr;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
|
||||
assert((len % self->frameSize) == 0);
|
||||
aluMixData(device, stream, len / self->frameSize);
|
||||
}
|
||||
|
||||
static ALCenum ALCsdl2Backend_open(ALCsdl2Backend *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
SDL_AudioSpec want, have;
|
||||
|
||||
SDL_zero(want);
|
||||
SDL_zero(have);
|
||||
|
||||
want.freq = device->Frequency;
|
||||
switch(device->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;
|
||||
}
|
||||
want.channels = (device->FmtChans == DevFmtMono) ? 1 : 2;
|
||||
want.samples = device->UpdateSize;
|
||||
want.callback = ALCsdl2Backend_audioCallback;
|
||||
want.userdata = self;
|
||||
|
||||
/* Passing NULL to SDL_OpenAudioDevice opens a default, which isn't
|
||||
* necessarily the first in the list.
|
||||
*/
|
||||
if(!name || strcmp(name, defaultDeviceName) == 0)
|
||||
self->deviceID = SDL_OpenAudioDevice(NULL, 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)
|
||||
self->deviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
else
|
||||
self->deviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
}
|
||||
if(self->deviceID == 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
device->Frequency = have.freq;
|
||||
if(have.channels == 1)
|
||||
device->FmtChans = DevFmtMono;
|
||||
else if(have.channels == 2)
|
||||
device->FmtChans = DevFmtStereo;
|
||||
else
|
||||
{
|
||||
ERR("Got unhandled SDL channel count: %d\n", (int)have.channels);
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
switch(have.format)
|
||||
{
|
||||
case AUDIO_U8: device->FmtType = DevFmtUByte; break;
|
||||
case AUDIO_S8: device->FmtType = DevFmtByte; break;
|
||||
case AUDIO_U16SYS: device->FmtType = DevFmtUShort; break;
|
||||
case AUDIO_S16SYS: device->FmtType = DevFmtShort; break;
|
||||
case AUDIO_S32SYS: device->FmtType = DevFmtInt; break;
|
||||
case AUDIO_F32SYS: device->FmtType = DevFmtFloat; break;
|
||||
default:
|
||||
ERR("Got unsupported SDL format: 0x%04x\n", have.format);
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
device->UpdateSize = have.samples;
|
||||
device->NumUpdates = 2; /* SDL always (tries to) use two periods. */
|
||||
|
||||
self->frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
self->Frequency = device->Frequency;
|
||||
self->FmtChans = device->FmtChans;
|
||||
self->FmtType = device->FmtType;
|
||||
self->UpdateSize = device->UpdateSize;
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name ? name : defaultDeviceName);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean ALCsdl2Backend_reset(ALCsdl2Backend *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
device->Frequency = self->Frequency;
|
||||
device->FmtChans = self->FmtChans;
|
||||
device->FmtType = self->FmtType;
|
||||
device->UpdateSize = self->UpdateSize;
|
||||
device->NumUpdates = 2;
|
||||
SetDefaultWFXChannelOrder(device);
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCsdl2Backend_start(ALCsdl2Backend *self)
|
||||
{
|
||||
SDL_PauseAudioDevice(self->deviceID, 0);
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCsdl2Backend_stop(ALCsdl2Backend *self)
|
||||
{
|
||||
SDL_PauseAudioDevice(self->deviceID, 1);
|
||||
}
|
||||
|
||||
static void ALCsdl2Backend_lock(ALCsdl2Backend *self)
|
||||
{
|
||||
SDL_LockAudioDevice(self->deviceID);
|
||||
}
|
||||
|
||||
static void ALCsdl2Backend_unlock(ALCsdl2Backend *self)
|
||||
{
|
||||
SDL_UnlockAudioDevice(self->deviceID);
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCsdl2BackendFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} ALCsdl2BackendFactory;
|
||||
#define ALCsdl2BACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCsdl2BackendFactory, ALCbackendFactory) } }
|
||||
|
||||
ALCbackendFactory *ALCsdl2BackendFactory_getFactory(void);
|
||||
|
||||
static ALCboolean ALCsdl2BackendFactory_init(ALCsdl2BackendFactory *self);
|
||||
static void ALCsdl2BackendFactory_deinit(ALCsdl2BackendFactory *self);
|
||||
static ALCboolean ALCsdl2BackendFactory_querySupport(ALCsdl2BackendFactory *self, ALCbackend_Type type);
|
||||
static void ALCsdl2BackendFactory_probe(ALCsdl2BackendFactory *self, enum DevProbe type, al_string *outnames);
|
||||
static ALCbackend* ALCsdl2BackendFactory_createBackend(ALCsdl2BackendFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(ALCsdl2BackendFactory);
|
||||
|
||||
|
||||
ALCbackendFactory *ALCsdl2BackendFactory_getFactory(void)
|
||||
{
|
||||
static ALCsdl2BackendFactory factory = ALCsdl2BACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
|
||||
|
||||
static ALCboolean ALCsdl2BackendFactory_init(ALCsdl2BackendFactory* UNUSED(self))
|
||||
{
|
||||
if(SDL_InitSubSystem(SDL_INIT_AUDIO) == 0)
|
||||
return AL_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void ALCsdl2BackendFactory_deinit(ALCsdl2BackendFactory* UNUSED(self))
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
}
|
||||
|
||||
static ALCboolean ALCsdl2BackendFactory_querySupport(ALCsdl2BackendFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
return ALC_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void ALCsdl2BackendFactory_probe(ALCsdl2BackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
|
||||
{
|
||||
int num_devices, i;
|
||||
al_string name;
|
||||
|
||||
if(type != ALL_DEVICE_PROBE)
|
||||
return;
|
||||
|
||||
AL_STRING_INIT(name);
|
||||
num_devices = SDL_GetNumAudioDevices(SDL_FALSE);
|
||||
|
||||
alstr_append_range(outnames, defaultDeviceName, defaultDeviceName+sizeof(defaultDeviceName));
|
||||
for(i = 0;i < num_devices;++i)
|
||||
{
|
||||
alstr_copy_cstr(&name, DEVNAME_PREFIX);
|
||||
alstr_append_cstr(&name, SDL_GetAudioDeviceName(i, SDL_FALSE));
|
||||
if(!alstr_empty(name))
|
||||
alstr_append_range(outnames, VECTOR_BEGIN(name), VECTOR_END(name)+1);
|
||||
}
|
||||
alstr_reset(&name);
|
||||
}
|
||||
|
||||
static ALCbackend* ALCsdl2BackendFactory_createBackend(ALCsdl2BackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
{
|
||||
ALCsdl2Backend *backend;
|
||||
NEW_OBJ(backend, ALCsdl2Backend)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
/**
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "threads.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
#include <sndio.h>
|
||||
|
||||
|
||||
static const ALCchar sndio_device[] = "SndIO Default";
|
||||
|
||||
|
||||
typedef struct SndioPlayback {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
struct sio_hdl *sndHandle;
|
||||
|
||||
ALvoid *mix_data;
|
||||
ALsizei data_size;
|
||||
|
||||
ATOMIC(int) killNow;
|
||||
althrd_t thread;
|
||||
} SndioPlayback;
|
||||
|
||||
static int SndioPlayback_mixerProc(void *ptr);
|
||||
|
||||
static void SndioPlayback_Construct(SndioPlayback *self, ALCdevice *device);
|
||||
static void SndioPlayback_Destruct(SndioPlayback *self);
|
||||
static ALCenum SndioPlayback_open(SndioPlayback *self, const ALCchar *name);
|
||||
static ALCboolean SndioPlayback_reset(SndioPlayback *self);
|
||||
static ALCboolean SndioPlayback_start(SndioPlayback *self);
|
||||
static void SndioPlayback_stop(SndioPlayback *self);
|
||||
static DECLARE_FORWARD2(SndioPlayback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
|
||||
static DECLARE_FORWARD(SndioPlayback, ALCbackend, ALCuint, availableSamples)
|
||||
static DECLARE_FORWARD(SndioPlayback, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(SndioPlayback, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(SndioPlayback, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(SndioPlayback)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(SndioPlayback);
|
||||
|
||||
|
||||
static void SndioPlayback_Construct(SndioPlayback *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(SndioPlayback, ALCbackend, self);
|
||||
|
||||
self->sndHandle = NULL;
|
||||
self->mix_data = NULL;
|
||||
ATOMIC_INIT(&self->killNow, AL_TRUE);
|
||||
}
|
||||
|
||||
static void SndioPlayback_Destruct(SndioPlayback *self)
|
||||
{
|
||||
if(self->sndHandle)
|
||||
sio_close(self->sndHandle);
|
||||
self->sndHandle = NULL;
|
||||
|
||||
al_free(self->mix_data);
|
||||
self->mix_data = NULL;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
static int SndioPlayback_mixerProc(void *ptr)
|
||||
{
|
||||
SndioPlayback *self = (SndioPlayback*)ptr;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
ALsizei frameSize;
|
||||
size_t wrote;
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(althrd_current(), MIXER_THREAD_NAME);
|
||||
|
||||
frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
|
||||
while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
|
||||
ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
|
||||
{
|
||||
ALsizei len = self->data_size;
|
||||
ALubyte *WritePtr = self->mix_data;
|
||||
|
||||
SndioPlayback_lock(self);
|
||||
aluMixData(device, WritePtr, len/frameSize);
|
||||
SndioPlayback_unlock(self);
|
||||
while(len > 0 && !ATOMIC_LOAD(&self->killNow, almemory_order_acquire))
|
||||
{
|
||||
wrote = sio_write(self->sndHandle, WritePtr, len);
|
||||
if(wrote == 0)
|
||||
{
|
||||
ERR("sio_write failed\n");
|
||||
ALCdevice_Lock(device);
|
||||
aluHandleDisconnect(device, "Failed to write playback samples");
|
||||
ALCdevice_Unlock(device);
|
||||
break;
|
||||
}
|
||||
|
||||
len -= wrote;
|
||||
WritePtr += wrote;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum SndioPlayback_open(SndioPlayback *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
|
||||
if(!name)
|
||||
name = sndio_device;
|
||||
else if(strcmp(name, sndio_device) != 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
self->sndHandle = sio_open(NULL, SIO_PLAY, 0);
|
||||
if(self->sndHandle == NULL)
|
||||
{
|
||||
ERR("Could not open device\n");
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean SndioPlayback_reset(SndioPlayback *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
struct sio_par par;
|
||||
|
||||
sio_initpar(&par);
|
||||
|
||||
par.rate = device->Frequency;
|
||||
par.pchan = ((device->FmtChans != DevFmtMono) ? 2 : 1);
|
||||
|
||||
switch(device->FmtType)
|
||||
{
|
||||
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;
|
||||
}
|
||||
par.le = SIO_LE_NATIVE;
|
||||
|
||||
par.round = device->UpdateSize;
|
||||
par.appbufsz = device->UpdateSize * (device->NumUpdates-1);
|
||||
if(!par.appbufsz) par.appbufsz = device->UpdateSize;
|
||||
|
||||
if(!sio_setpar(self->sndHandle, &par) || !sio_getpar(self->sndHandle, &par))
|
||||
{
|
||||
ERR("Failed to set device parameters\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
if(par.bits != par.bps*8)
|
||||
{
|
||||
ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
device->Frequency = par.rate;
|
||||
device->FmtChans = ((par.pchan==1) ? DevFmtMono : DevFmtStereo);
|
||||
|
||||
if(par.bits == 8 && par.sig == 1)
|
||||
device->FmtType = DevFmtByte;
|
||||
else if(par.bits == 8 && par.sig == 0)
|
||||
device->FmtType = DevFmtUByte;
|
||||
else if(par.bits == 16 && par.sig == 1)
|
||||
device->FmtType = DevFmtShort;
|
||||
else if(par.bits == 16 && par.sig == 0)
|
||||
device->FmtType = DevFmtUShort;
|
||||
else if(par.bits == 32 && par.sig == 1)
|
||||
device->FmtType = DevFmtInt;
|
||||
else if(par.bits == 32 && par.sig == 0)
|
||||
device->FmtType = DevFmtUInt;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
device->UpdateSize = par.round;
|
||||
device->NumUpdates = (par.bufsz/par.round) + 1;
|
||||
|
||||
SetDefaultChannelOrder(device);
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean SndioPlayback_start(SndioPlayback *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
|
||||
self->data_size = device->UpdateSize * FrameSizeFromDevFmt(
|
||||
device->FmtChans, device->FmtType, device->AmbiOrder
|
||||
);
|
||||
al_free(self->mix_data);
|
||||
self->mix_data = al_calloc(16, self->data_size);
|
||||
|
||||
if(!sio_start(self->sndHandle))
|
||||
{
|
||||
ERR("Error starting playback\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
|
||||
if(althrd_create(&self->thread, SndioPlayback_mixerProc, self) != althrd_success)
|
||||
{
|
||||
sio_stop(self->sndHandle);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void SndioPlayback_stop(SndioPlayback *self)
|
||||
{
|
||||
int res;
|
||||
|
||||
if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
|
||||
return;
|
||||
althrd_join(self->thread, &res);
|
||||
|
||||
if(!sio_stop(self->sndHandle))
|
||||
ERR("Error stopping device\n");
|
||||
|
||||
al_free(self->mix_data);
|
||||
self->mix_data = NULL;
|
||||
}
|
||||
|
||||
|
||||
typedef struct SndioCapture {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
struct sio_hdl *sndHandle;
|
||||
|
||||
ll_ringbuffer_t *ring;
|
||||
|
||||
ATOMIC(int) killNow;
|
||||
althrd_t thread;
|
||||
} SndioCapture;
|
||||
|
||||
static int SndioCapture_recordProc(void *ptr);
|
||||
|
||||
static void SndioCapture_Construct(SndioCapture *self, ALCdevice *device);
|
||||
static void SndioCapture_Destruct(SndioCapture *self);
|
||||
static ALCenum SndioCapture_open(SndioCapture *self, const ALCchar *name);
|
||||
static DECLARE_FORWARD(SndioCapture, ALCbackend, ALCboolean, reset)
|
||||
static ALCboolean SndioCapture_start(SndioCapture *self);
|
||||
static void SndioCapture_stop(SndioCapture *self);
|
||||
static ALCenum SndioCapture_captureSamples(SndioCapture *self, void *buffer, ALCuint samples);
|
||||
static ALCuint SndioCapture_availableSamples(SndioCapture *self);
|
||||
static DECLARE_FORWARD(SndioCapture, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(SndioCapture, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(SndioCapture, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(SndioCapture)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(SndioCapture);
|
||||
|
||||
|
||||
static void SndioCapture_Construct(SndioCapture *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(SndioCapture, ALCbackend, self);
|
||||
|
||||
self->sndHandle = NULL;
|
||||
self->ring = NULL;
|
||||
ATOMIC_INIT(&self->killNow, AL_TRUE);
|
||||
}
|
||||
|
||||
static void SndioCapture_Destruct(SndioCapture *self)
|
||||
{
|
||||
if(self->sndHandle)
|
||||
sio_close(self->sndHandle);
|
||||
self->sndHandle = NULL;
|
||||
|
||||
ll_ringbuffer_free(self->ring);
|
||||
self->ring = NULL;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
static int SndioCapture_recordProc(void* ptr)
|
||||
{
|
||||
SndioCapture *self = (SndioCapture*)ptr;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
ALsizei frameSize;
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(althrd_current(), RECORD_THREAD_NAME);
|
||||
|
||||
frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
|
||||
while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
|
||||
ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
|
||||
{
|
||||
ll_ringbuffer_data_t data[2];
|
||||
size_t total, todo;
|
||||
|
||||
ll_ringbuffer_get_write_vector(self->ring, data);
|
||||
todo = data[0].len + data[1].len;
|
||||
if(todo == 0)
|
||||
{
|
||||
static char junk[4096];
|
||||
sio_read(self->sndHandle, junk, minz(sizeof(junk)/frameSize, device->UpdateSize)*frameSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
total = 0;
|
||||
data[0].len *= frameSize;
|
||||
data[1].len *= frameSize;
|
||||
todo = minz(todo, device->UpdateSize) * frameSize;
|
||||
while(total < todo)
|
||||
{
|
||||
size_t got;
|
||||
|
||||
if(!data[0].len)
|
||||
data[0] = data[1];
|
||||
|
||||
got = sio_read(self->sndHandle, data[0].buf, minz(todo-total, data[0].len));
|
||||
if(!got)
|
||||
{
|
||||
SndioCapture_lock(self);
|
||||
aluHandleDisconnect(device, "Failed to read capture samples");
|
||||
SndioCapture_unlock(self);
|
||||
break;
|
||||
}
|
||||
|
||||
data[0].buf += got;
|
||||
data[0].len -= got;
|
||||
total += got;
|
||||
}
|
||||
ll_ringbuffer_write_advance(self->ring, total / frameSize);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum SndioCapture_open(SndioCapture *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
struct sio_par par;
|
||||
|
||||
if(!name)
|
||||
name = sndio_device;
|
||||
else if(strcmp(name, sndio_device) != 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
self->sndHandle = sio_open(NULL, SIO_REC, 0);
|
||||
if(self->sndHandle == NULL)
|
||||
{
|
||||
ERR("Could not open device\n");
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
sio_initpar(&par);
|
||||
|
||||
switch(device->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(device->FmtType));
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
par.bits = par.bps * 8;
|
||||
par.le = SIO_LE_NATIVE;
|
||||
par.msb = SIO_LE_NATIVE ? 0 : 1;
|
||||
par.rchan = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
|
||||
par.rate = device->Frequency;
|
||||
|
||||
par.appbufsz = maxu(device->UpdateSize*device->NumUpdates, (device->Frequency+9)/10);
|
||||
par.round = clampu(par.appbufsz/device->NumUpdates, (device->Frequency+99)/100,
|
||||
(device->Frequency+19)/20);
|
||||
|
||||
device->UpdateSize = par.round;
|
||||
device->NumUpdates = maxu(par.appbufsz/par.round, 1);
|
||||
|
||||
if(!sio_setpar(self->sndHandle, &par) || !sio_getpar(self->sndHandle, &par))
|
||||
{
|
||||
ERR("Failed to set device parameters\n");
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
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(!((device->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0) ||
|
||||
(device->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0) ||
|
||||
(device->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0) ||
|
||||
(device->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0) ||
|
||||
(device->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0) ||
|
||||
(device->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0)) ||
|
||||
ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder) != (ALsizei)par.rchan ||
|
||||
device->Frequency != par.rate)
|
||||
{
|
||||
ERR("Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead\n",
|
||||
DevFmtTypeString(device->FmtType), DevFmtChannelsString(device->FmtChans),
|
||||
device->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate);
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
self->ring = ll_ringbuffer_create(device->UpdateSize*device->NumUpdates, par.bps*par.rchan, 0);
|
||||
if(!self->ring)
|
||||
{
|
||||
ERR("Failed to allocate %u-byte ringbuffer\n",
|
||||
device->UpdateSize*device->NumUpdates*par.bps*par.rchan);
|
||||
return ALC_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
SetDefaultChannelOrder(device);
|
||||
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean SndioCapture_start(SndioCapture *self)
|
||||
{
|
||||
if(!sio_start(self->sndHandle))
|
||||
{
|
||||
ERR("Error starting playback\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
|
||||
if(althrd_create(&self->thread, SndioCapture_recordProc, self) != althrd_success)
|
||||
{
|
||||
sio_stop(self->sndHandle);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void SndioCapture_stop(SndioCapture *self)
|
||||
{
|
||||
int res;
|
||||
|
||||
if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
|
||||
return;
|
||||
althrd_join(self->thread, &res);
|
||||
|
||||
if(!sio_stop(self->sndHandle))
|
||||
ERR("Error stopping device\n");
|
||||
}
|
||||
|
||||
static ALCenum SndioCapture_captureSamples(SndioCapture *self, void *buffer, ALCuint samples)
|
||||
{
|
||||
ll_ringbuffer_read(self->ring, buffer, samples);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCuint SndioCapture_availableSamples(SndioCapture *self)
|
||||
{
|
||||
return ll_ringbuffer_read_space(self->ring);
|
||||
}
|
||||
|
||||
|
||||
typedef struct SndioBackendFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} SndioBackendFactory;
|
||||
#define SNDIOBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(SndioBackendFactory, ALCbackendFactory) } }
|
||||
|
||||
ALCbackendFactory *SndioBackendFactory_getFactory(void);
|
||||
|
||||
static ALCboolean SndioBackendFactory_init(SndioBackendFactory *self);
|
||||
static DECLARE_FORWARD(SndioBackendFactory, ALCbackendFactory, void, deinit)
|
||||
static ALCboolean SndioBackendFactory_querySupport(SndioBackendFactory *self, ALCbackend_Type type);
|
||||
static void SndioBackendFactory_probe(SndioBackendFactory *self, enum DevProbe type, al_string *outnames);
|
||||
static ALCbackend* SndioBackendFactory_createBackend(SndioBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(SndioBackendFactory);
|
||||
|
||||
ALCbackendFactory *SndioBackendFactory_getFactory(void)
|
||||
{
|
||||
static SndioBackendFactory factory = SNDIOBACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
|
||||
static ALCboolean SndioBackendFactory_init(SndioBackendFactory* UNUSED(self))
|
||||
{
|
||||
/* No dynamic loading */
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean SndioBackendFactory_querySupport(SndioBackendFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback || type == ALCbackend_Capture)
|
||||
return ALC_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void SndioBackendFactory_probe(SndioBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case ALL_DEVICE_PROBE:
|
||||
case CAPTURE_DEVICE_PROBE:
|
||||
alstr_append_range(outnames, sndio_device, sndio_device+sizeof(sndio_device));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static ALCbackend* SndioBackendFactory_createBackend(SndioBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
{
|
||||
SndioPlayback *backend;
|
||||
NEW_OBJ(backend, SndioPlayback)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
if(type == ALCbackend_Capture)
|
||||
{
|
||||
SndioCapture *backend;
|
||||
NEW_OBJ(backend, SndioCapture)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
/**
|
||||
* 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 <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.h"
|
||||
#include "threads.h"
|
||||
#include "compat.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
#include <sys/audioio.h>
|
||||
|
||||
|
||||
typedef struct ALCsolarisBackend {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
int fd;
|
||||
|
||||
ALubyte *mix_data;
|
||||
int data_size;
|
||||
|
||||
ATOMIC(ALenum) killNow;
|
||||
althrd_t thread;
|
||||
} ALCsolarisBackend;
|
||||
|
||||
static int ALCsolarisBackend_mixerProc(void *ptr);
|
||||
|
||||
static void ALCsolarisBackend_Construct(ALCsolarisBackend *self, ALCdevice *device);
|
||||
static void ALCsolarisBackend_Destruct(ALCsolarisBackend *self);
|
||||
static ALCenum ALCsolarisBackend_open(ALCsolarisBackend *self, const ALCchar *name);
|
||||
static ALCboolean ALCsolarisBackend_reset(ALCsolarisBackend *self);
|
||||
static ALCboolean ALCsolarisBackend_start(ALCsolarisBackend *self);
|
||||
static void ALCsolarisBackend_stop(ALCsolarisBackend *self);
|
||||
static DECLARE_FORWARD2(ALCsolarisBackend, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
|
||||
static DECLARE_FORWARD(ALCsolarisBackend, ALCbackend, ALCuint, availableSamples)
|
||||
static DECLARE_FORWARD(ALCsolarisBackend, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCsolarisBackend, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCsolarisBackend, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCsolarisBackend)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCsolarisBackend);
|
||||
|
||||
|
||||
static const ALCchar solaris_device[] = "Solaris Default";
|
||||
|
||||
static const char *solaris_driver = "/dev/audio";
|
||||
|
||||
|
||||
static void ALCsolarisBackend_Construct(ALCsolarisBackend *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCsolarisBackend, ALCbackend, self);
|
||||
|
||||
self->fd = -1;
|
||||
self->mix_data = NULL;
|
||||
ATOMIC_INIT(&self->killNow, AL_FALSE);
|
||||
}
|
||||
|
||||
static void ALCsolarisBackend_Destruct(ALCsolarisBackend *self)
|
||||
{
|
||||
if(self->fd != -1)
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
|
||||
free(self->mix_data);
|
||||
self->mix_data = NULL;
|
||||
self->data_size = 0;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
static int ALCsolarisBackend_mixerProc(void *ptr)
|
||||
{
|
||||
ALCsolarisBackend *self = ptr;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
struct timeval timeout;
|
||||
ALubyte *write_ptr;
|
||||
ALint frame_size;
|
||||
ALint to_write;
|
||||
ssize_t wrote;
|
||||
fd_set wfds;
|
||||
int sret;
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(althrd_current(), MIXER_THREAD_NAME);
|
||||
|
||||
frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
|
||||
ALCsolarisBackend_lock(self);
|
||||
while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
|
||||
ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
|
||||
{
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(self->fd, &wfds);
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
ALCsolarisBackend_unlock(self);
|
||||
sret = select(self->fd+1, NULL, &wfds, NULL, &timeout);
|
||||
ALCsolarisBackend_lock(self);
|
||||
if(sret < 0)
|
||||
{
|
||||
if(errno == EINTR)
|
||||
continue;
|
||||
ERR("select failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(device, "Failed to wait for playback buffer: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
else if(sret == 0)
|
||||
{
|
||||
WARN("select timeout\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
write_ptr = self->mix_data;
|
||||
to_write = self->data_size;
|
||||
aluMixData(device, write_ptr, to_write/frame_size);
|
||||
while(to_write > 0 && !ATOMIC_LOAD_SEQ(&self->killNow))
|
||||
{
|
||||
wrote = write(self->fd, write_ptr, to_write);
|
||||
if(wrote < 0)
|
||||
{
|
||||
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
continue;
|
||||
ERR("write failed: %s\n", strerror(errno));
|
||||
aluHandleDisconnect(device, "Failed to write playback samples: %s",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
to_write -= wrote;
|
||||
write_ptr += wrote;
|
||||
}
|
||||
}
|
||||
ALCsolarisBackend_unlock(self);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCsolarisBackend_open(ALCsolarisBackend *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device;
|
||||
|
||||
if(!name)
|
||||
name = solaris_device;
|
||||
else if(strcmp(name, solaris_device) != 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
self->fd = open(solaris_driver, O_WRONLY);
|
||||
if(self->fd == -1)
|
||||
{
|
||||
ERR("Could not open %s: %s\n", solaris_driver, strerror(errno));
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean ALCsolarisBackend_reset(ALCsolarisBackend *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
|
||||
audio_info_t info;
|
||||
ALsizei frameSize;
|
||||
ALsizei numChannels;
|
||||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.play.sample_rate = device->Frequency;
|
||||
|
||||
if(device->FmtChans != DevFmtMono)
|
||||
device->FmtChans = DevFmtStereo;
|
||||
numChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
|
||||
info.play.channels = numChannels;
|
||||
|
||||
switch(device->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:
|
||||
device->FmtType = DevFmtShort;
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
info.play.precision = 16;
|
||||
info.play.encoding = AUDIO_ENCODING_LINEAR;
|
||||
break;
|
||||
}
|
||||
|
||||
frameSize = numChannels * BytesFromDevFmt(device->FmtType);
|
||||
info.play.buffer_size = device->UpdateSize*device->NumUpdates * frameSize;
|
||||
|
||||
if(ioctl(self->fd, AUDIO_SETINFO, &info) < 0)
|
||||
{
|
||||
ERR("ioctl failed: %s\n", strerror(errno));
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
if(ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder) != (ALsizei)info.play.channels)
|
||||
{
|
||||
ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(device->FmtChans), info.play.channels);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && device->FmtType == DevFmtUByte) ||
|
||||
(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && device->FmtType == DevFmtByte) ||
|
||||
(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && device->FmtType == DevFmtShort) ||
|
||||
(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && device->FmtType == DevFmtInt)))
|
||||
{
|
||||
ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(device->FmtType),
|
||||
info.play.precision, info.play.encoding);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
device->Frequency = info.play.sample_rate;
|
||||
device->UpdateSize = (info.play.buffer_size/device->NumUpdates) + 1;
|
||||
|
||||
SetDefaultChannelOrder(device);
|
||||
|
||||
free(self->mix_data);
|
||||
self->data_size = device->UpdateSize * FrameSizeFromDevFmt(
|
||||
device->FmtChans, device->FmtType, device->AmbiOrder
|
||||
);
|
||||
self->mix_data = calloc(1, self->data_size);
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCsolarisBackend_start(ALCsolarisBackend *self)
|
||||
{
|
||||
ATOMIC_STORE_SEQ(&self->killNow, AL_FALSE);
|
||||
if(althrd_create(&self->thread, ALCsolarisBackend_mixerProc, self) != althrd_success)
|
||||
return ALC_FALSE;
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCsolarisBackend_stop(ALCsolarisBackend *self)
|
||||
{
|
||||
int res;
|
||||
|
||||
if(ATOMIC_EXCHANGE_SEQ(&self->killNow, AL_TRUE))
|
||||
return;
|
||||
|
||||
althrd_join(self->thread, &res);
|
||||
|
||||
if(ioctl(self->fd, AUDIO_DRAIN) < 0)
|
||||
ERR("Error draining device: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCsolarisBackendFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} ALCsolarisBackendFactory;
|
||||
#define ALCSOLARISBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCsolarisBackendFactory, ALCbackendFactory) } }
|
||||
|
||||
ALCbackendFactory *ALCsolarisBackendFactory_getFactory(void);
|
||||
|
||||
static ALCboolean ALCsolarisBackendFactory_init(ALCsolarisBackendFactory *self);
|
||||
static DECLARE_FORWARD(ALCsolarisBackendFactory, ALCbackendFactory, void, deinit)
|
||||
static ALCboolean ALCsolarisBackendFactory_querySupport(ALCsolarisBackendFactory *self, ALCbackend_Type type);
|
||||
static void ALCsolarisBackendFactory_probe(ALCsolarisBackendFactory *self, enum DevProbe type, al_string *outnames);
|
||||
static ALCbackend* ALCsolarisBackendFactory_createBackend(ALCsolarisBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(ALCsolarisBackendFactory);
|
||||
|
||||
|
||||
ALCbackendFactory *ALCsolarisBackendFactory_getFactory(void)
|
||||
{
|
||||
static ALCsolarisBackendFactory factory = ALCSOLARISBACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
|
||||
|
||||
static ALCboolean ALCsolarisBackendFactory_init(ALCsolarisBackendFactory* UNUSED(self))
|
||||
{
|
||||
ConfigValueStr(NULL, "solaris", "device", &solaris_driver);
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCsolarisBackendFactory_querySupport(ALCsolarisBackendFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
return ALC_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void ALCsolarisBackendFactory_probe(ALCsolarisBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case ALL_DEVICE_PROBE:
|
||||
{
|
||||
#ifdef HAVE_STAT
|
||||
struct stat buf;
|
||||
if(stat(solaris_driver, &buf) == 0)
|
||||
#endif
|
||||
alstr_append_range(outnames, solaris_device, solaris_device+sizeof(solaris_device));
|
||||
}
|
||||
break;
|
||||
|
||||
case CAPTURE_DEVICE_PROBE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ALCbackend* ALCsolarisBackendFactory_createBackend(ALCsolarisBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
{
|
||||
ALCsolarisBackend *backend;
|
||||
NEW_OBJ(backend, ALCsolarisBackend)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,453 @@
|
||||
/**
|
||||
* 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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.h"
|
||||
#include "threads.h"
|
||||
#include "compat.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
|
||||
static const ALCchar waveDevice[] = "Wave File Writer";
|
||||
|
||||
static const ALubyte SUBTYPE_PCM[] = {
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
|
||||
0x00, 0x38, 0x9b, 0x71
|
||||
};
|
||||
static const ALubyte SUBTYPE_FLOAT[] = {
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
|
||||
0x00, 0x38, 0x9b, 0x71
|
||||
};
|
||||
|
||||
static const ALubyte SUBTYPE_BFORMAT_PCM[] = {
|
||||
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
|
||||
0xca, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
static const ALubyte SUBTYPE_BFORMAT_FLOAT[] = {
|
||||
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
|
||||
0xca, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
static void fwrite16le(ALushort val, FILE *f)
|
||||
{
|
||||
ALubyte data[2] = { val&0xff, (val>>8)&0xff };
|
||||
fwrite(data, 1, 2, f);
|
||||
}
|
||||
|
||||
static void fwrite32le(ALuint val, FILE *f)
|
||||
{
|
||||
ALubyte data[4] = { val&0xff, (val>>8)&0xff, (val>>16)&0xff, (val>>24)&0xff };
|
||||
fwrite(data, 1, 4, f);
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCwaveBackend {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
FILE *mFile;
|
||||
long mDataStart;
|
||||
|
||||
ALvoid *mBuffer;
|
||||
ALuint mSize;
|
||||
|
||||
ATOMIC(ALenum) killNow;
|
||||
althrd_t thread;
|
||||
} ALCwaveBackend;
|
||||
|
||||
static int ALCwaveBackend_mixerProc(void *ptr);
|
||||
|
||||
static void ALCwaveBackend_Construct(ALCwaveBackend *self, ALCdevice *device);
|
||||
static void ALCwaveBackend_Destruct(ALCwaveBackend *self);
|
||||
static ALCenum ALCwaveBackend_open(ALCwaveBackend *self, const ALCchar *name);
|
||||
static ALCboolean ALCwaveBackend_reset(ALCwaveBackend *self);
|
||||
static ALCboolean ALCwaveBackend_start(ALCwaveBackend *self);
|
||||
static void ALCwaveBackend_stop(ALCwaveBackend *self);
|
||||
static DECLARE_FORWARD2(ALCwaveBackend, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
|
||||
static DECLARE_FORWARD(ALCwaveBackend, ALCbackend, ALCuint, availableSamples)
|
||||
static DECLARE_FORWARD(ALCwaveBackend, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCwaveBackend, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCwaveBackend, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCwaveBackend)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCwaveBackend);
|
||||
|
||||
|
||||
static void ALCwaveBackend_Construct(ALCwaveBackend *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCwaveBackend, ALCbackend, self);
|
||||
|
||||
self->mFile = NULL;
|
||||
self->mDataStart = -1;
|
||||
|
||||
self->mBuffer = NULL;
|
||||
self->mSize = 0;
|
||||
|
||||
ATOMIC_INIT(&self->killNow, AL_TRUE);
|
||||
}
|
||||
|
||||
static void ALCwaveBackend_Destruct(ALCwaveBackend *self)
|
||||
{
|
||||
if(self->mFile)
|
||||
fclose(self->mFile);
|
||||
self->mFile = NULL;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
static int ALCwaveBackend_mixerProc(void *ptr)
|
||||
{
|
||||
ALCwaveBackend *self = (ALCwaveBackend*)ptr;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
struct timespec now, start;
|
||||
ALint64 avail, done;
|
||||
ALuint frameSize;
|
||||
size_t fs;
|
||||
const long restTime = (long)((ALuint64)device->UpdateSize * 1000000000 /
|
||||
device->Frequency / 2);
|
||||
|
||||
althrd_setname(althrd_current(), MIXER_THREAD_NAME);
|
||||
|
||||
frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
|
||||
done = 0;
|
||||
if(altimespec_get(&start, AL_TIME_UTC) != AL_TIME_UTC)
|
||||
{
|
||||
ERR("Failed to get starting time\n");
|
||||
return 1;
|
||||
}
|
||||
while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
|
||||
ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
|
||||
{
|
||||
if(altimespec_get(&now, AL_TIME_UTC) != AL_TIME_UTC)
|
||||
{
|
||||
ERR("Failed to get current time\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
avail = (now.tv_sec - start.tv_sec) * device->Frequency;
|
||||
avail += (ALint64)(now.tv_nsec - start.tv_nsec) * device->Frequency / 1000000000;
|
||||
if(avail < done)
|
||||
{
|
||||
/* Oops, time skipped backwards. Reset the number of samples done
|
||||
* with one update available since we (likely) just came back from
|
||||
* sleeping. */
|
||||
done = avail - device->UpdateSize;
|
||||
}
|
||||
|
||||
if(avail-done < device->UpdateSize)
|
||||
al_nssleep(restTime);
|
||||
else while(avail-done >= device->UpdateSize)
|
||||
{
|
||||
ALCwaveBackend_lock(self);
|
||||
aluMixData(device, self->mBuffer, device->UpdateSize);
|
||||
ALCwaveBackend_unlock(self);
|
||||
done += device->UpdateSize;
|
||||
|
||||
if(!IS_LITTLE_ENDIAN)
|
||||
{
|
||||
ALuint bytesize = BytesFromDevFmt(device->FmtType);
|
||||
ALuint i;
|
||||
|
||||
if(bytesize == 2)
|
||||
{
|
||||
ALushort *samples = self->mBuffer;
|
||||
ALuint len = self->mSize / 2;
|
||||
for(i = 0;i < len;i++)
|
||||
{
|
||||
ALushort samp = samples[i];
|
||||
samples[i] = (samp>>8) | (samp<<8);
|
||||
}
|
||||
}
|
||||
else if(bytesize == 4)
|
||||
{
|
||||
ALuint *samples = self->mBuffer;
|
||||
ALuint len = self->mSize / 4;
|
||||
for(i = 0;i < len;i++)
|
||||
{
|
||||
ALuint samp = samples[i];
|
||||
samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
|
||||
((samp<<8)&0x00ff0000) | (samp<<24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs = fwrite(self->mBuffer, frameSize, device->UpdateSize, self->mFile);
|
||||
(void)fs;
|
||||
if(ferror(self->mFile))
|
||||
{
|
||||
ERR("Error writing to file\n");
|
||||
ALCdevice_Lock(device);
|
||||
aluHandleDisconnect(device, "Failed to write playback samples");
|
||||
ALCdevice_Unlock(device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCwaveBackend_open(ALCwaveBackend *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device;
|
||||
const char *fname;
|
||||
|
||||
fname = GetConfigValue(NULL, "wave", "file", "");
|
||||
if(!fname[0]) return ALC_INVALID_VALUE;
|
||||
|
||||
if(!name)
|
||||
name = waveDevice;
|
||||
else if(strcmp(name, waveDevice) != 0)
|
||||
return ALC_INVALID_VALUE;
|
||||
|
||||
self->mFile = al_fopen(fname, "wb");
|
||||
if(!self->mFile)
|
||||
{
|
||||
ERR("Could not open file '%s': %s\n", fname, strerror(errno));
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
alstr_copy_cstr(&device->DeviceName, name);
|
||||
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCboolean ALCwaveBackend_reset(ALCwaveBackend *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
ALuint channels=0, bits=0, chanmask=0;
|
||||
int isbformat = 0;
|
||||
size_t val;
|
||||
|
||||
fseek(self->mFile, 0, SEEK_SET);
|
||||
clearerr(self->mFile);
|
||||
|
||||
if(GetConfigValueBool(NULL, "wave", "bformat", 0))
|
||||
{
|
||||
device->FmtChans = DevFmtAmbi3D;
|
||||
device->AmbiOrder = 1;
|
||||
}
|
||||
|
||||
switch(device->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
device->FmtType = DevFmtUByte;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
device->FmtType = DevFmtShort;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
device->FmtType = DevFmtInt;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtFloat:
|
||||
break;
|
||||
}
|
||||
switch(device->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 */
|
||||
device->AmbiLayout = AmbiLayout_FuMa;
|
||||
device->AmbiScale = AmbiNorm_FuMa;
|
||||
isbformat = 1;
|
||||
chanmask = 0;
|
||||
break;
|
||||
}
|
||||
bits = BytesFromDevFmt(device->FmtType) * 8;
|
||||
channels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
|
||||
|
||||
fputs("RIFF", self->mFile);
|
||||
fwrite32le(0xFFFFFFFF, self->mFile); // 'RIFF' header len; filled in at close
|
||||
|
||||
fputs("WAVE", self->mFile);
|
||||
|
||||
fputs("fmt ", self->mFile);
|
||||
fwrite32le(40, self->mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
|
||||
|
||||
// 16-bit val, format type id (extensible: 0xFFFE)
|
||||
fwrite16le(0xFFFE, self->mFile);
|
||||
// 16-bit val, channel count
|
||||
fwrite16le(channels, self->mFile);
|
||||
// 32-bit val, frequency
|
||||
fwrite32le(device->Frequency, self->mFile);
|
||||
// 32-bit val, bytes per second
|
||||
fwrite32le(device->Frequency * channels * bits / 8, self->mFile);
|
||||
// 16-bit val, frame size
|
||||
fwrite16le(channels * bits / 8, self->mFile);
|
||||
// 16-bit val, bits per sample
|
||||
fwrite16le(bits, self->mFile);
|
||||
// 16-bit val, extra byte count
|
||||
fwrite16le(22, self->mFile);
|
||||
// 16-bit val, valid bits per sample
|
||||
fwrite16le(bits, self->mFile);
|
||||
// 32-bit val, channel mask
|
||||
fwrite32le(chanmask, self->mFile);
|
||||
// 16 byte GUID, sub-type format
|
||||
val = fwrite((device->FmtType == DevFmtFloat) ?
|
||||
(isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
|
||||
(isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, self->mFile);
|
||||
(void)val;
|
||||
|
||||
fputs("data", self->mFile);
|
||||
fwrite32le(0xFFFFFFFF, self->mFile); // 'data' header len; filled in at close
|
||||
|
||||
if(ferror(self->mFile))
|
||||
{
|
||||
ERR("Error writing header: %s\n", strerror(errno));
|
||||
return ALC_FALSE;
|
||||
}
|
||||
self->mDataStart = ftell(self->mFile);
|
||||
|
||||
SetDefaultWFXChannelOrder(device);
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCwaveBackend_start(ALCwaveBackend *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
|
||||
self->mSize = device->UpdateSize * FrameSizeFromDevFmt(
|
||||
device->FmtChans, device->FmtType, device->AmbiOrder
|
||||
);
|
||||
self->mBuffer = malloc(self->mSize);
|
||||
if(!self->mBuffer)
|
||||
{
|
||||
ERR("Buffer malloc failed\n");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
|
||||
if(althrd_create(&self->thread, ALCwaveBackend_mixerProc, self) != althrd_success)
|
||||
{
|
||||
free(self->mBuffer);
|
||||
self->mBuffer = NULL;
|
||||
self->mSize = 0;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCwaveBackend_stop(ALCwaveBackend *self)
|
||||
{
|
||||
ALuint dataLen;
|
||||
long size;
|
||||
int res;
|
||||
|
||||
if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
|
||||
return;
|
||||
althrd_join(self->thread, &res);
|
||||
|
||||
free(self->mBuffer);
|
||||
self->mBuffer = NULL;
|
||||
|
||||
size = ftell(self->mFile);
|
||||
if(size > 0)
|
||||
{
|
||||
dataLen = size - self->mDataStart;
|
||||
if(fseek(self->mFile, self->mDataStart-4, SEEK_SET) == 0)
|
||||
fwrite32le(dataLen, self->mFile); // 'data' header len
|
||||
if(fseek(self->mFile, 4, SEEK_SET) == 0)
|
||||
fwrite32le(size-8, self->mFile); // 'WAVE' header len
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCwaveBackendFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} ALCwaveBackendFactory;
|
||||
#define ALCWAVEBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCwaveBackendFactory, ALCbackendFactory) } }
|
||||
|
||||
ALCbackendFactory *ALCwaveBackendFactory_getFactory(void);
|
||||
|
||||
static ALCboolean ALCwaveBackendFactory_init(ALCwaveBackendFactory *self);
|
||||
static DECLARE_FORWARD(ALCwaveBackendFactory, ALCbackendFactory, void, deinit)
|
||||
static ALCboolean ALCwaveBackendFactory_querySupport(ALCwaveBackendFactory *self, ALCbackend_Type type);
|
||||
static void ALCwaveBackendFactory_probe(ALCwaveBackendFactory *self, enum DevProbe type, al_string *outnames);
|
||||
static ALCbackend* ALCwaveBackendFactory_createBackend(ALCwaveBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(ALCwaveBackendFactory);
|
||||
|
||||
|
||||
ALCbackendFactory *ALCwaveBackendFactory_getFactory(void)
|
||||
{
|
||||
static ALCwaveBackendFactory factory = ALCWAVEBACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
|
||||
|
||||
static ALCboolean ALCwaveBackendFactory_init(ALCwaveBackendFactory* UNUSED(self))
|
||||
{
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCwaveBackendFactory_querySupport(ALCwaveBackendFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
return !!ConfigValueExists(NULL, "wave", "file");
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void ALCwaveBackendFactory_probe(ALCwaveBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case ALL_DEVICE_PROBE:
|
||||
alstr_append_range(outnames, waveDevice, waveDevice+sizeof(waveDevice));
|
||||
break;
|
||||
case CAPTURE_DEVICE_PROBE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static ALCbackend* ALCwaveBackendFactory_createBackend(ALCwaveBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
{
|
||||
ALCwaveBackend *backend;
|
||||
NEW_OBJ(backend, ALCwaveBackend)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,786 @@
|
||||
/**
|
||||
* 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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "threads.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
#ifndef WAVE_FORMAT_IEEE_FLOAT
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
|
||||
#endif
|
||||
|
||||
#define DEVNAME_HEAD "OpenAL Soft on "
|
||||
|
||||
|
||||
static vector_al_string PlaybackDevices;
|
||||
static vector_al_string CaptureDevices;
|
||||
|
||||
static void clear_devlist(vector_al_string *list)
|
||||
{
|
||||
VECTOR_FOR_EACH(al_string, *list, alstr_reset);
|
||||
VECTOR_RESIZE(*list, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
static void ProbePlaybackDevices(void)
|
||||
{
|
||||
ALuint numdevs;
|
||||
ALuint i;
|
||||
|
||||
clear_devlist(&PlaybackDevices);
|
||||
|
||||
numdevs = waveOutGetNumDevs();
|
||||
VECTOR_RESIZE(PlaybackDevices, 0, numdevs);
|
||||
for(i = 0;i < numdevs;i++)
|
||||
{
|
||||
WAVEOUTCAPSW WaveCaps;
|
||||
const al_string *iter;
|
||||
al_string dname;
|
||||
|
||||
AL_STRING_INIT(dname);
|
||||
if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
|
||||
{
|
||||
ALuint count = 0;
|
||||
while(1)
|
||||
{
|
||||
alstr_copy_cstr(&dname, DEVNAME_HEAD);
|
||||
alstr_append_wcstr(&dname, WaveCaps.szPname);
|
||||
if(count != 0)
|
||||
{
|
||||
char str[64];
|
||||
snprintf(str, sizeof(str), " #%d", count+1);
|
||||
alstr_append_cstr(&dname, str);
|
||||
}
|
||||
count++;
|
||||
|
||||
#define MATCH_ENTRY(i) (alstr_cmp(dname, *(i)) == 0)
|
||||
VECTOR_FIND_IF(iter, const al_string, PlaybackDevices, MATCH_ENTRY);
|
||||
if(iter == VECTOR_END(PlaybackDevices)) break;
|
||||
#undef MATCH_ENTRY
|
||||
}
|
||||
|
||||
TRACE("Got device \"%s\", ID %u\n", alstr_get_cstr(dname), i);
|
||||
}
|
||||
VECTOR_PUSH_BACK(PlaybackDevices, dname);
|
||||
}
|
||||
}
|
||||
|
||||
static void ProbeCaptureDevices(void)
|
||||
{
|
||||
ALuint numdevs;
|
||||
ALuint i;
|
||||
|
||||
clear_devlist(&CaptureDevices);
|
||||
|
||||
numdevs = waveInGetNumDevs();
|
||||
VECTOR_RESIZE(CaptureDevices, 0, numdevs);
|
||||
for(i = 0;i < numdevs;i++)
|
||||
{
|
||||
WAVEINCAPSW WaveCaps;
|
||||
const al_string *iter;
|
||||
al_string dname;
|
||||
|
||||
AL_STRING_INIT(dname);
|
||||
if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
|
||||
{
|
||||
ALuint count = 0;
|
||||
while(1)
|
||||
{
|
||||
alstr_copy_cstr(&dname, DEVNAME_HEAD);
|
||||
alstr_append_wcstr(&dname, WaveCaps.szPname);
|
||||
if(count != 0)
|
||||
{
|
||||
char str[64];
|
||||
snprintf(str, sizeof(str), " #%d", count+1);
|
||||
alstr_append_cstr(&dname, str);
|
||||
}
|
||||
count++;
|
||||
|
||||
#define MATCH_ENTRY(i) (alstr_cmp(dname, *(i)) == 0)
|
||||
VECTOR_FIND_IF(iter, const al_string, CaptureDevices, MATCH_ENTRY);
|
||||
if(iter == VECTOR_END(CaptureDevices)) break;
|
||||
#undef MATCH_ENTRY
|
||||
}
|
||||
|
||||
TRACE("Got device \"%s\", ID %u\n", alstr_get_cstr(dname), i);
|
||||
}
|
||||
VECTOR_PUSH_BACK(CaptureDevices, dname);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCwinmmPlayback {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
RefCount WaveBuffersCommitted;
|
||||
WAVEHDR WaveBuffer[4];
|
||||
|
||||
HWAVEOUT OutHdl;
|
||||
|
||||
WAVEFORMATEX Format;
|
||||
|
||||
ATOMIC(ALenum) killNow;
|
||||
althrd_t thread;
|
||||
} ALCwinmmPlayback;
|
||||
|
||||
static void ALCwinmmPlayback_Construct(ALCwinmmPlayback *self, ALCdevice *device);
|
||||
static void ALCwinmmPlayback_Destruct(ALCwinmmPlayback *self);
|
||||
|
||||
static void CALLBACK ALCwinmmPlayback_waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2);
|
||||
static int ALCwinmmPlayback_mixerProc(void *arg);
|
||||
|
||||
static ALCenum ALCwinmmPlayback_open(ALCwinmmPlayback *self, const ALCchar *name);
|
||||
static ALCboolean ALCwinmmPlayback_reset(ALCwinmmPlayback *self);
|
||||
static ALCboolean ALCwinmmPlayback_start(ALCwinmmPlayback *self);
|
||||
static void ALCwinmmPlayback_stop(ALCwinmmPlayback *self);
|
||||
static DECLARE_FORWARD2(ALCwinmmPlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint)
|
||||
static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, ALCuint, availableSamples)
|
||||
static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCwinmmPlayback)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCwinmmPlayback);
|
||||
|
||||
|
||||
static void ALCwinmmPlayback_Construct(ALCwinmmPlayback *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCwinmmPlayback, ALCbackend, self);
|
||||
|
||||
InitRef(&self->WaveBuffersCommitted, 0);
|
||||
self->OutHdl = NULL;
|
||||
|
||||
ATOMIC_INIT(&self->killNow, AL_TRUE);
|
||||
}
|
||||
|
||||
static void ALCwinmmPlayback_Destruct(ALCwinmmPlayback *self)
|
||||
{
|
||||
if(self->OutHdl)
|
||||
waveOutClose(self->OutHdl);
|
||||
self->OutHdl = 0;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
/* ALCwinmmPlayback_waveOutProc
|
||||
*
|
||||
* Posts a message to 'ALCwinmmPlayback_mixerProc' everytime a WaveOut Buffer
|
||||
* is completed and returns to the application (for more data)
|
||||
*/
|
||||
static void CALLBACK ALCwinmmPlayback_waveOutProc(HWAVEOUT UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2))
|
||||
{
|
||||
ALCwinmmPlayback *self = (ALCwinmmPlayback*)instance;
|
||||
|
||||
if(msg != WOM_DONE)
|
||||
return;
|
||||
|
||||
DecrementRef(&self->WaveBuffersCommitted);
|
||||
PostThreadMessage(self->thread, msg, 0, param1);
|
||||
}
|
||||
|
||||
FORCE_ALIGN static int ALCwinmmPlayback_mixerProc(void *arg)
|
||||
{
|
||||
ALCwinmmPlayback *self = arg;
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
WAVEHDR *WaveHdr;
|
||||
MSG msg;
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(althrd_current(), MIXER_THREAD_NAME);
|
||||
|
||||
while(GetMessage(&msg, NULL, 0, 0))
|
||||
{
|
||||
if(msg.message != WOM_DONE)
|
||||
continue;
|
||||
|
||||
if(ATOMIC_LOAD(&self->killNow, almemory_order_acquire))
|
||||
{
|
||||
if(ReadRef(&self->WaveBuffersCommitted) == 0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
WaveHdr = ((WAVEHDR*)msg.lParam);
|
||||
ALCwinmmPlayback_lock(self);
|
||||
aluMixData(device, WaveHdr->lpData, WaveHdr->dwBufferLength /
|
||||
self->Format.nBlockAlign);
|
||||
ALCwinmmPlayback_unlock(self);
|
||||
|
||||
// Send buffer back to play more data
|
||||
waveOutWrite(self->OutHdl, WaveHdr, sizeof(WAVEHDR));
|
||||
IncrementRef(&self->WaveBuffersCommitted);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCwinmmPlayback_open(ALCwinmmPlayback *self, const ALCchar *deviceName)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
const al_string *iter;
|
||||
UINT DeviceID;
|
||||
MMRESULT res;
|
||||
|
||||
if(VECTOR_SIZE(PlaybackDevices) == 0)
|
||||
ProbePlaybackDevices();
|
||||
|
||||
// Find the Device ID matching the deviceName if valid
|
||||
#define MATCH_DEVNAME(iter) (!alstr_empty(*(iter)) && \
|
||||
(!deviceName || alstr_cmp_cstr(*(iter), deviceName) == 0))
|
||||
VECTOR_FIND_IF(iter, const al_string, PlaybackDevices, MATCH_DEVNAME);
|
||||
if(iter == VECTOR_END(PlaybackDevices))
|
||||
return ALC_INVALID_VALUE;
|
||||
#undef MATCH_DEVNAME
|
||||
|
||||
DeviceID = (UINT)(iter - VECTOR_BEGIN(PlaybackDevices));
|
||||
|
||||
retry_open:
|
||||
memset(&self->Format, 0, sizeof(WAVEFORMATEX));
|
||||
if(device->FmtType == DevFmtFloat)
|
||||
{
|
||||
self->Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
self->Format.wBitsPerSample = 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->Format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
if(device->FmtType == DevFmtUByte || device->FmtType == DevFmtByte)
|
||||
self->Format.wBitsPerSample = 8;
|
||||
else
|
||||
self->Format.wBitsPerSample = 16;
|
||||
}
|
||||
self->Format.nChannels = ((device->FmtChans == DevFmtMono) ? 1 : 2);
|
||||
self->Format.nBlockAlign = self->Format.wBitsPerSample *
|
||||
self->Format.nChannels / 8;
|
||||
self->Format.nSamplesPerSec = device->Frequency;
|
||||
self->Format.nAvgBytesPerSec = self->Format.nSamplesPerSec *
|
||||
self->Format.nBlockAlign;
|
||||
self->Format.cbSize = 0;
|
||||
|
||||
if((res=waveOutOpen(&self->OutHdl, DeviceID, &self->Format, (DWORD_PTR)&ALCwinmmPlayback_waveOutProc, (DWORD_PTR)self, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
|
||||
{
|
||||
if(device->FmtType == DevFmtFloat)
|
||||
{
|
||||
device->FmtType = DevFmtShort;
|
||||
goto retry_open;
|
||||
}
|
||||
ERR("waveOutOpen failed: %u\n", res);
|
||||
goto failure;
|
||||
}
|
||||
|
||||
alstr_copy(&device->DeviceName, VECTOR_ELEM(PlaybackDevices, DeviceID));
|
||||
return ALC_NO_ERROR;
|
||||
|
||||
failure:
|
||||
if(self->OutHdl)
|
||||
waveOutClose(self->OutHdl);
|
||||
self->OutHdl = NULL;
|
||||
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCwinmmPlayback_reset(ALCwinmmPlayback *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
|
||||
device->UpdateSize = (ALuint)((ALuint64)device->UpdateSize *
|
||||
self->Format.nSamplesPerSec /
|
||||
device->Frequency);
|
||||
device->UpdateSize = (device->UpdateSize*device->NumUpdates + 3) / 4;
|
||||
device->NumUpdates = 4;
|
||||
device->Frequency = self->Format.nSamplesPerSec;
|
||||
|
||||
if(self->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
|
||||
{
|
||||
if(self->Format.wBitsPerSample == 32)
|
||||
device->FmtType = DevFmtFloat;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled IEEE float sample depth: %d\n", self->Format.wBitsPerSample);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
}
|
||||
else if(self->Format.wFormatTag == WAVE_FORMAT_PCM)
|
||||
{
|
||||
if(self->Format.wBitsPerSample == 16)
|
||||
device->FmtType = DevFmtShort;
|
||||
else if(self->Format.wBitsPerSample == 8)
|
||||
device->FmtType = DevFmtUByte;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled PCM sample depth: %d\n", self->Format.wBitsPerSample);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unhandled format tag: 0x%04x\n", self->Format.wFormatTag);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
if(self->Format.nChannels == 2)
|
||||
device->FmtChans = DevFmtStereo;
|
||||
else if(self->Format.nChannels == 1)
|
||||
device->FmtChans = DevFmtMono;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled channel count: %d\n", self->Format.nChannels);
|
||||
return ALC_FALSE;
|
||||
}
|
||||
SetDefaultWFXChannelOrder(device);
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCwinmmPlayback_start(ALCwinmmPlayback *self)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
ALbyte *BufferData;
|
||||
ALint BufferSize;
|
||||
ALuint i;
|
||||
|
||||
ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
|
||||
if(althrd_create(&self->thread, ALCwinmmPlayback_mixerProc, self) != althrd_success)
|
||||
return ALC_FALSE;
|
||||
|
||||
InitRef(&self->WaveBuffersCommitted, 0);
|
||||
|
||||
// Create 4 Buffers
|
||||
BufferSize = device->UpdateSize*device->NumUpdates / 4;
|
||||
BufferSize *= FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
|
||||
|
||||
BufferData = calloc(4, BufferSize);
|
||||
for(i = 0;i < 4;i++)
|
||||
{
|
||||
memset(&self->WaveBuffer[i], 0, sizeof(WAVEHDR));
|
||||
self->WaveBuffer[i].dwBufferLength = BufferSize;
|
||||
self->WaveBuffer[i].lpData = ((i==0) ? (CHAR*)BufferData :
|
||||
(self->WaveBuffer[i-1].lpData +
|
||||
self->WaveBuffer[i-1].dwBufferLength));
|
||||
waveOutPrepareHeader(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
|
||||
waveOutWrite(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
|
||||
IncrementRef(&self->WaveBuffersCommitted);
|
||||
}
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCwinmmPlayback_stop(ALCwinmmPlayback *self)
|
||||
{
|
||||
void *buffer = NULL;
|
||||
int i;
|
||||
|
||||
if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
|
||||
return;
|
||||
althrd_join(self->thread, &i);
|
||||
|
||||
// Release the wave buffers
|
||||
for(i = 0;i < 4;i++)
|
||||
{
|
||||
waveOutUnprepareHeader(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
|
||||
if(i == 0) buffer = self->WaveBuffer[i].lpData;
|
||||
self->WaveBuffer[i].lpData = NULL;
|
||||
}
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
typedef struct ALCwinmmCapture {
|
||||
DERIVE_FROM_TYPE(ALCbackend);
|
||||
|
||||
RefCount WaveBuffersCommitted;
|
||||
WAVEHDR WaveBuffer[4];
|
||||
|
||||
HWAVEIN InHdl;
|
||||
|
||||
ll_ringbuffer_t *Ring;
|
||||
|
||||
WAVEFORMATEX Format;
|
||||
|
||||
ATOMIC(ALenum) killNow;
|
||||
althrd_t thread;
|
||||
} ALCwinmmCapture;
|
||||
|
||||
static void ALCwinmmCapture_Construct(ALCwinmmCapture *self, ALCdevice *device);
|
||||
static void ALCwinmmCapture_Destruct(ALCwinmmCapture *self);
|
||||
|
||||
static void CALLBACK ALCwinmmCapture_waveInProc(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2);
|
||||
static int ALCwinmmCapture_captureProc(void *arg);
|
||||
|
||||
static ALCenum ALCwinmmCapture_open(ALCwinmmCapture *self, const ALCchar *name);
|
||||
static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, ALCboolean, reset)
|
||||
static ALCboolean ALCwinmmCapture_start(ALCwinmmCapture *self);
|
||||
static void ALCwinmmCapture_stop(ALCwinmmCapture *self);
|
||||
static ALCenum ALCwinmmCapture_captureSamples(ALCwinmmCapture *self, ALCvoid *buffer, ALCuint samples);
|
||||
static ALCuint ALCwinmmCapture_availableSamples(ALCwinmmCapture *self);
|
||||
static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, ClockLatency, getClockLatency)
|
||||
static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, void, lock)
|
||||
static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, void, unlock)
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALCwinmmCapture)
|
||||
|
||||
DEFINE_ALCBACKEND_VTABLE(ALCwinmmCapture);
|
||||
|
||||
|
||||
static void ALCwinmmCapture_Construct(ALCwinmmCapture *self, ALCdevice *device)
|
||||
{
|
||||
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
|
||||
SET_VTABLE2(ALCwinmmCapture, ALCbackend, self);
|
||||
|
||||
InitRef(&self->WaveBuffersCommitted, 0);
|
||||
self->InHdl = NULL;
|
||||
|
||||
ATOMIC_INIT(&self->killNow, AL_TRUE);
|
||||
}
|
||||
|
||||
static void ALCwinmmCapture_Destruct(ALCwinmmCapture *self)
|
||||
{
|
||||
void *buffer = NULL;
|
||||
int i;
|
||||
|
||||
/* Tell the processing thread to quit and wait for it to do so. */
|
||||
if(!ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
|
||||
{
|
||||
PostThreadMessage(self->thread, WM_QUIT, 0, 0);
|
||||
|
||||
althrd_join(self->thread, &i);
|
||||
|
||||
/* Make sure capture is stopped and all pending buffers are flushed. */
|
||||
waveInReset(self->InHdl);
|
||||
|
||||
// Release the wave buffers
|
||||
for(i = 0;i < 4;i++)
|
||||
{
|
||||
waveInUnprepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
|
||||
if(i == 0) buffer = self->WaveBuffer[i].lpData;
|
||||
self->WaveBuffer[i].lpData = NULL;
|
||||
}
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
ll_ringbuffer_free(self->Ring);
|
||||
self->Ring = NULL;
|
||||
|
||||
// Close the Wave device
|
||||
if(self->InHdl)
|
||||
waveInClose(self->InHdl);
|
||||
self->InHdl = 0;
|
||||
|
||||
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
|
||||
}
|
||||
|
||||
|
||||
/* ALCwinmmCapture_waveInProc
|
||||
*
|
||||
* Posts a message to 'ALCwinmmCapture_captureProc' everytime a WaveIn Buffer
|
||||
* is completed and returns to the application (with more data).
|
||||
*/
|
||||
static void CALLBACK ALCwinmmCapture_waveInProc(HWAVEIN UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2))
|
||||
{
|
||||
ALCwinmmCapture *self = (ALCwinmmCapture*)instance;
|
||||
|
||||
if(msg != WIM_DATA)
|
||||
return;
|
||||
|
||||
DecrementRef(&self->WaveBuffersCommitted);
|
||||
PostThreadMessage(self->thread, msg, 0, param1);
|
||||
}
|
||||
|
||||
static int ALCwinmmCapture_captureProc(void *arg)
|
||||
{
|
||||
ALCwinmmCapture *self = arg;
|
||||
WAVEHDR *WaveHdr;
|
||||
MSG msg;
|
||||
|
||||
althrd_setname(althrd_current(), RECORD_THREAD_NAME);
|
||||
|
||||
while(GetMessage(&msg, NULL, 0, 0))
|
||||
{
|
||||
if(msg.message != WIM_DATA)
|
||||
continue;
|
||||
/* Don't wait for other buffers to finish before quitting. We're
|
||||
* closing so we don't need them. */
|
||||
if(ATOMIC_LOAD(&self->killNow, almemory_order_acquire))
|
||||
break;
|
||||
|
||||
WaveHdr = ((WAVEHDR*)msg.lParam);
|
||||
ll_ringbuffer_write(self->Ring, WaveHdr->lpData,
|
||||
WaveHdr->dwBytesRecorded / self->Format.nBlockAlign
|
||||
);
|
||||
|
||||
// Send buffer back to capture more data
|
||||
waveInAddBuffer(self->InHdl, WaveHdr, sizeof(WAVEHDR));
|
||||
IncrementRef(&self->WaveBuffersCommitted);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ALCenum ALCwinmmCapture_open(ALCwinmmCapture *self, const ALCchar *name)
|
||||
{
|
||||
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
|
||||
const al_string *iter;
|
||||
ALbyte *BufferData = NULL;
|
||||
DWORD CapturedDataSize;
|
||||
ALint BufferSize;
|
||||
UINT DeviceID;
|
||||
MMRESULT res;
|
||||
ALuint i;
|
||||
|
||||
if(VECTOR_SIZE(CaptureDevices) == 0)
|
||||
ProbeCaptureDevices();
|
||||
|
||||
// Find the Device ID matching the deviceName if valid
|
||||
#define MATCH_DEVNAME(iter) (!alstr_empty(*(iter)) && (!name || alstr_cmp_cstr(*iter, name) == 0))
|
||||
VECTOR_FIND_IF(iter, const al_string, CaptureDevices, MATCH_DEVNAME);
|
||||
if(iter == VECTOR_END(CaptureDevices))
|
||||
return ALC_INVALID_VALUE;
|
||||
#undef MATCH_DEVNAME
|
||||
|
||||
DeviceID = (UINT)(iter - VECTOR_BEGIN(CaptureDevices));
|
||||
|
||||
switch(device->FmtChans)
|
||||
{
|
||||
case DevFmtMono:
|
||||
case DevFmtStereo:
|
||||
break;
|
||||
|
||||
case DevFmtQuad:
|
||||
case DevFmtX51:
|
||||
case DevFmtX51Rear:
|
||||
case DevFmtX61:
|
||||
case DevFmtX71:
|
||||
case DevFmtAmbi3D:
|
||||
return ALC_INVALID_ENUM;
|
||||
}
|
||||
|
||||
switch(device->FmtType)
|
||||
{
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtFloat:
|
||||
break;
|
||||
|
||||
case DevFmtByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
return ALC_INVALID_ENUM;
|
||||
}
|
||||
|
||||
memset(&self->Format, 0, sizeof(WAVEFORMATEX));
|
||||
self->Format.wFormatTag = ((device->FmtType == DevFmtFloat) ?
|
||||
WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM);
|
||||
self->Format.nChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
|
||||
self->Format.wBitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
|
||||
self->Format.nBlockAlign = self->Format.wBitsPerSample *
|
||||
self->Format.nChannels / 8;
|
||||
self->Format.nSamplesPerSec = device->Frequency;
|
||||
self->Format.nAvgBytesPerSec = self->Format.nSamplesPerSec *
|
||||
self->Format.nBlockAlign;
|
||||
self->Format.cbSize = 0;
|
||||
|
||||
if((res=waveInOpen(&self->InHdl, DeviceID, &self->Format, (DWORD_PTR)&ALCwinmmCapture_waveInProc, (DWORD_PTR)self, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
|
||||
{
|
||||
ERR("waveInOpen failed: %u\n", res);
|
||||
goto failure;
|
||||
}
|
||||
|
||||
// Allocate circular memory buffer for the captured audio
|
||||
CapturedDataSize = device->UpdateSize*device->NumUpdates;
|
||||
|
||||
// Make sure circular buffer is at least 100ms in size
|
||||
if(CapturedDataSize < (self->Format.nSamplesPerSec / 10))
|
||||
CapturedDataSize = self->Format.nSamplesPerSec / 10;
|
||||
|
||||
self->Ring = ll_ringbuffer_create(CapturedDataSize, self->Format.nBlockAlign, false);
|
||||
if(!self->Ring) goto failure;
|
||||
|
||||
InitRef(&self->WaveBuffersCommitted, 0);
|
||||
|
||||
// Create 4 Buffers of 50ms each
|
||||
BufferSize = self->Format.nAvgBytesPerSec / 20;
|
||||
BufferSize -= (BufferSize % self->Format.nBlockAlign);
|
||||
|
||||
BufferData = calloc(4, BufferSize);
|
||||
if(!BufferData) goto failure;
|
||||
|
||||
for(i = 0;i < 4;i++)
|
||||
{
|
||||
memset(&self->WaveBuffer[i], 0, sizeof(WAVEHDR));
|
||||
self->WaveBuffer[i].dwBufferLength = BufferSize;
|
||||
self->WaveBuffer[i].lpData = ((i==0) ? (CHAR*)BufferData :
|
||||
(self->WaveBuffer[i-1].lpData +
|
||||
self->WaveBuffer[i-1].dwBufferLength));
|
||||
self->WaveBuffer[i].dwFlags = 0;
|
||||
self->WaveBuffer[i].dwLoops = 0;
|
||||
waveInPrepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
|
||||
waveInAddBuffer(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
|
||||
IncrementRef(&self->WaveBuffersCommitted);
|
||||
}
|
||||
|
||||
ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
|
||||
if(althrd_create(&self->thread, ALCwinmmCapture_captureProc, self) != althrd_success)
|
||||
goto failure;
|
||||
|
||||
alstr_copy(&device->DeviceName, VECTOR_ELEM(CaptureDevices, DeviceID));
|
||||
return ALC_NO_ERROR;
|
||||
|
||||
failure:
|
||||
if(BufferData)
|
||||
{
|
||||
for(i = 0;i < 4;i++)
|
||||
waveInUnprepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
|
||||
free(BufferData);
|
||||
}
|
||||
|
||||
ll_ringbuffer_free(self->Ring);
|
||||
self->Ring = NULL;
|
||||
|
||||
if(self->InHdl)
|
||||
waveInClose(self->InHdl);
|
||||
self->InHdl = NULL;
|
||||
|
||||
return ALC_INVALID_VALUE;
|
||||
}
|
||||
|
||||
static ALCboolean ALCwinmmCapture_start(ALCwinmmCapture *self)
|
||||
{
|
||||
waveInStart(self->InHdl);
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCwinmmCapture_stop(ALCwinmmCapture *self)
|
||||
{
|
||||
waveInStop(self->InHdl);
|
||||
}
|
||||
|
||||
static ALCenum ALCwinmmCapture_captureSamples(ALCwinmmCapture *self, ALCvoid *buffer, ALCuint samples)
|
||||
{
|
||||
ll_ringbuffer_read(self->Ring, buffer, samples);
|
||||
return ALC_NO_ERROR;
|
||||
}
|
||||
|
||||
static ALCuint ALCwinmmCapture_availableSamples(ALCwinmmCapture *self)
|
||||
{
|
||||
return (ALCuint)ll_ringbuffer_read_space(self->Ring);
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALCwinmmBackendFactory {
|
||||
DERIVE_FROM_TYPE(ALCbackendFactory);
|
||||
} ALCwinmmBackendFactory;
|
||||
#define ALCWINMMBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCwinmmBackendFactory, ALCbackendFactory) } }
|
||||
|
||||
static ALCboolean ALCwinmmBackendFactory_init(ALCwinmmBackendFactory *self);
|
||||
static void ALCwinmmBackendFactory_deinit(ALCwinmmBackendFactory *self);
|
||||
static ALCboolean ALCwinmmBackendFactory_querySupport(ALCwinmmBackendFactory *self, ALCbackend_Type type);
|
||||
static void ALCwinmmBackendFactory_probe(ALCwinmmBackendFactory *self, enum DevProbe type, al_string *outnames);
|
||||
static ALCbackend* ALCwinmmBackendFactory_createBackend(ALCwinmmBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
|
||||
|
||||
DEFINE_ALCBACKENDFACTORY_VTABLE(ALCwinmmBackendFactory);
|
||||
|
||||
|
||||
static ALCboolean ALCwinmmBackendFactory_init(ALCwinmmBackendFactory* UNUSED(self))
|
||||
{
|
||||
VECTOR_INIT(PlaybackDevices);
|
||||
VECTOR_INIT(CaptureDevices);
|
||||
|
||||
return ALC_TRUE;
|
||||
}
|
||||
|
||||
static void ALCwinmmBackendFactory_deinit(ALCwinmmBackendFactory* UNUSED(self))
|
||||
{
|
||||
clear_devlist(&PlaybackDevices);
|
||||
VECTOR_DEINIT(PlaybackDevices);
|
||||
|
||||
clear_devlist(&CaptureDevices);
|
||||
VECTOR_DEINIT(CaptureDevices);
|
||||
}
|
||||
|
||||
static ALCboolean ALCwinmmBackendFactory_querySupport(ALCwinmmBackendFactory* UNUSED(self), ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback || type == ALCbackend_Capture)
|
||||
return ALC_TRUE;
|
||||
return ALC_FALSE;
|
||||
}
|
||||
|
||||
static void ALCwinmmBackendFactory_probe(ALCwinmmBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
#define APPEND_OUTNAME(n) do { \
|
||||
if(!alstr_empty(*(n))) \
|
||||
alstr_append_range(outnames, VECTOR_BEGIN(*(n)), VECTOR_END(*(n))+1); \
|
||||
} while(0)
|
||||
case ALL_DEVICE_PROBE:
|
||||
ProbePlaybackDevices();
|
||||
VECTOR_FOR_EACH(const al_string, PlaybackDevices, APPEND_OUTNAME);
|
||||
break;
|
||||
|
||||
case CAPTURE_DEVICE_PROBE:
|
||||
ProbeCaptureDevices();
|
||||
VECTOR_FOR_EACH(const al_string, CaptureDevices, APPEND_OUTNAME);
|
||||
break;
|
||||
#undef APPEND_OUTNAME
|
||||
}
|
||||
}
|
||||
|
||||
static ALCbackend* ALCwinmmBackendFactory_createBackend(ALCwinmmBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
|
||||
{
|
||||
if(type == ALCbackend_Playback)
|
||||
{
|
||||
ALCwinmmPlayback *backend;
|
||||
NEW_OBJ(backend, ALCwinmmPlayback)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
if(type == ALCbackend_Capture)
|
||||
{
|
||||
ALCwinmmCapture *backend;
|
||||
NEW_OBJ(backend, ALCwinmmCapture)(device);
|
||||
if(!backend) return NULL;
|
||||
return STATIC_CAST(ALCbackend, backend);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ALCbackendFactory *ALCwinmmBackendFactory_getFactory(void)
|
||||
{
|
||||
static ALCwinmmBackendFactory factory = ALCWINMMBACKENDFACTORY_INITIALIZER;
|
||||
return STATIC_CAST(ALCbackendFactory, &factory);
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "bformatdec.h"
|
||||
#include "ambdec.h"
|
||||
#include "filters/splitter.h"
|
||||
#include "alu.h"
|
||||
|
||||
#include "bool.h"
|
||||
#include "threads.h"
|
||||
#include "almalloc.h"
|
||||
|
||||
|
||||
/* NOTE: These are scale factors as applied to Ambisonics content. Decoder
|
||||
* coefficients should be divided by these values to get proper N3D scalings.
|
||||
*/
|
||||
const ALfloat N3D2N3DScale[MAX_AMBI_COEFFS] = {
|
||||
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
|
||||
};
|
||||
const ALfloat SN3D2N3DScale[MAX_AMBI_COEFFS] = {
|
||||
1.000000000f, /* ACN 0 (W), sqrt(1) */
|
||||
1.732050808f, /* ACN 1 (Y), sqrt(3) */
|
||||
1.732050808f, /* ACN 2 (Z), sqrt(3) */
|
||||
1.732050808f, /* ACN 3 (X), sqrt(3) */
|
||||
2.236067978f, /* ACN 4 (V), sqrt(5) */
|
||||
2.236067978f, /* ACN 5 (T), sqrt(5) */
|
||||
2.236067978f, /* ACN 6 (R), sqrt(5) */
|
||||
2.236067978f, /* ACN 7 (S), sqrt(5) */
|
||||
2.236067978f, /* ACN 8 (U), sqrt(5) */
|
||||
2.645751311f, /* ACN 9 (Q), sqrt(7) */
|
||||
2.645751311f, /* ACN 10 (O), sqrt(7) */
|
||||
2.645751311f, /* ACN 11 (M), sqrt(7) */
|
||||
2.645751311f, /* ACN 12 (K), sqrt(7) */
|
||||
2.645751311f, /* ACN 13 (L), sqrt(7) */
|
||||
2.645751311f, /* ACN 14 (N), sqrt(7) */
|
||||
2.645751311f, /* ACN 15 (P), sqrt(7) */
|
||||
};
|
||||
const ALfloat FuMa2N3DScale[MAX_AMBI_COEFFS] = {
|
||||
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) */
|
||||
};
|
||||
|
||||
|
||||
#define HF_BAND 0
|
||||
#define LF_BAND 1
|
||||
#define NUM_BANDS 2
|
||||
|
||||
/* These points are in AL coordinates! */
|
||||
static const ALfloat Ambi3DPoints[8][3] = {
|
||||
{ -0.577350269f, 0.577350269f, -0.577350269f },
|
||||
{ 0.577350269f, 0.577350269f, -0.577350269f },
|
||||
{ -0.577350269f, 0.577350269f, 0.577350269f },
|
||||
{ 0.577350269f, 0.577350269f, 0.577350269f },
|
||||
{ -0.577350269f, -0.577350269f, -0.577350269f },
|
||||
{ 0.577350269f, -0.577350269f, -0.577350269f },
|
||||
{ -0.577350269f, -0.577350269f, 0.577350269f },
|
||||
{ 0.577350269f, -0.577350269f, 0.577350269f },
|
||||
};
|
||||
static const ALfloat Ambi3DDecoder[8][MAX_AMBI_COEFFS] = {
|
||||
{ 0.125f, 0.125f, 0.125f, 0.125f },
|
||||
{ 0.125f, -0.125f, 0.125f, 0.125f },
|
||||
{ 0.125f, 0.125f, 0.125f, -0.125f },
|
||||
{ 0.125f, -0.125f, 0.125f, -0.125f },
|
||||
{ 0.125f, 0.125f, -0.125f, 0.125f },
|
||||
{ 0.125f, -0.125f, -0.125f, 0.125f },
|
||||
{ 0.125f, 0.125f, -0.125f, -0.125f },
|
||||
{ 0.125f, -0.125f, -0.125f, -0.125f },
|
||||
};
|
||||
static const ALfloat Ambi3DDecoderHFScale[MAX_AMBI_COEFFS] = {
|
||||
2.0f,
|
||||
1.15470054f, 1.15470054f, 1.15470054f
|
||||
};
|
||||
|
||||
|
||||
/* NOTE: BandSplitter filters are unused with single-band decoding */
|
||||
typedef struct BFormatDec {
|
||||
ALuint Enabled; /* Bitfield of enabled channels. */
|
||||
|
||||
union {
|
||||
alignas(16) ALfloat Dual[MAX_OUTPUT_CHANNELS][NUM_BANDS][MAX_AMBI_COEFFS];
|
||||
alignas(16) ALfloat Single[MAX_OUTPUT_CHANNELS][MAX_AMBI_COEFFS];
|
||||
} Matrix;
|
||||
|
||||
BandSplitter XOver[MAX_AMBI_COEFFS];
|
||||
|
||||
ALfloat (*Samples)[BUFFERSIZE];
|
||||
/* These two alias into Samples */
|
||||
ALfloat (*SamplesHF)[BUFFERSIZE];
|
||||
ALfloat (*SamplesLF)[BUFFERSIZE];
|
||||
|
||||
alignas(16) ALfloat ChannelMix[BUFFERSIZE];
|
||||
|
||||
struct {
|
||||
BandSplitter XOver;
|
||||
ALfloat Gains[NUM_BANDS];
|
||||
} UpSampler[4];
|
||||
|
||||
ALsizei NumChannels;
|
||||
ALboolean DualBand;
|
||||
} BFormatDec;
|
||||
|
||||
BFormatDec *bformatdec_alloc()
|
||||
{
|
||||
return al_calloc(16, sizeof(BFormatDec));
|
||||
}
|
||||
|
||||
void bformatdec_free(BFormatDec **dec)
|
||||
{
|
||||
if(dec && *dec)
|
||||
{
|
||||
al_free((*dec)->Samples);
|
||||
(*dec)->Samples = NULL;
|
||||
(*dec)->SamplesHF = NULL;
|
||||
(*dec)->SamplesLF = NULL;
|
||||
|
||||
al_free(*dec);
|
||||
*dec = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void bformatdec_reset(BFormatDec *dec, const AmbDecConf *conf, ALsizei chancount, ALuint srate, const ALsizei chanmap[MAX_OUTPUT_CHANNELS])
|
||||
{
|
||||
static const ALsizei map2DTo3D[MAX_AMBI2D_COEFFS] = {
|
||||
0, 1, 3, 4, 8, 9, 15
|
||||
};
|
||||
const ALfloat *coeff_scale = N3D2N3DScale;
|
||||
bool periphonic;
|
||||
ALfloat ratio;
|
||||
ALsizei i;
|
||||
|
||||
al_free(dec->Samples);
|
||||
dec->Samples = NULL;
|
||||
dec->SamplesHF = NULL;
|
||||
dec->SamplesLF = NULL;
|
||||
|
||||
dec->NumChannels = chancount;
|
||||
dec->Samples = al_calloc(16, dec->NumChannels*2 * sizeof(dec->Samples[0]));
|
||||
dec->SamplesHF = dec->Samples;
|
||||
dec->SamplesLF = dec->SamplesHF + dec->NumChannels;
|
||||
|
||||
dec->Enabled = 0;
|
||||
for(i = 0;i < conf->NumSpeakers;i++)
|
||||
dec->Enabled |= 1 << chanmap[i];
|
||||
|
||||
if(conf->CoeffScale == ADS_SN3D)
|
||||
coeff_scale = SN3D2N3DScale;
|
||||
else if(conf->CoeffScale == ADS_FuMa)
|
||||
coeff_scale = FuMa2N3DScale;
|
||||
|
||||
memset(dec->UpSampler, 0, sizeof(dec->UpSampler));
|
||||
ratio = 400.0f / (ALfloat)srate;
|
||||
for(i = 0;i < 4;i++)
|
||||
bandsplit_init(&dec->UpSampler[i].XOver, ratio);
|
||||
if((conf->ChanMask&AMBI_PERIPHONIC_MASK))
|
||||
{
|
||||
periphonic = true;
|
||||
|
||||
dec->UpSampler[0].Gains[HF_BAND] = (conf->ChanMask > 0x1ff) ? W_SCALE_3H3P :
|
||||
(conf->ChanMask > 0xf) ? W_SCALE_2H2P : 1.0f;
|
||||
dec->UpSampler[0].Gains[LF_BAND] = 1.0f;
|
||||
for(i = 1;i < 4;i++)
|
||||
{
|
||||
dec->UpSampler[i].Gains[HF_BAND] = (conf->ChanMask > 0x1ff) ? XYZ_SCALE_3H3P :
|
||||
(conf->ChanMask > 0xf) ? XYZ_SCALE_2H2P : 1.0f;
|
||||
dec->UpSampler[i].Gains[LF_BAND] = 1.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
periphonic = false;
|
||||
|
||||
dec->UpSampler[0].Gains[HF_BAND] = (conf->ChanMask > 0x1ff) ? W_SCALE_3H0P :
|
||||
(conf->ChanMask > 0xf) ? W_SCALE_2H0P : 1.0f;
|
||||
dec->UpSampler[0].Gains[LF_BAND] = 1.0f;
|
||||
for(i = 1;i < 3;i++)
|
||||
{
|
||||
dec->UpSampler[i].Gains[HF_BAND] = (conf->ChanMask > 0x1ff) ? XYZ_SCALE_3H0P :
|
||||
(conf->ChanMask > 0xf) ? XYZ_SCALE_2H0P : 1.0f;
|
||||
dec->UpSampler[i].Gains[LF_BAND] = 1.0f;
|
||||
}
|
||||
dec->UpSampler[3].Gains[HF_BAND] = 0.0f;
|
||||
dec->UpSampler[3].Gains[LF_BAND] = 0.0f;
|
||||
}
|
||||
|
||||
memset(&dec->Matrix, 0, sizeof(dec->Matrix));
|
||||
if(conf->FreqBands == 1)
|
||||
{
|
||||
dec->DualBand = AL_FALSE;
|
||||
for(i = 0;i < conf->NumSpeakers;i++)
|
||||
{
|
||||
ALsizei chan = chanmap[i];
|
||||
ALfloat gain;
|
||||
ALsizei j, k;
|
||||
|
||||
if(!periphonic)
|
||||
{
|
||||
for(j = 0,k = 0;j < MAX_AMBI2D_COEFFS;j++)
|
||||
{
|
||||
ALsizei l = map2DTo3D[j];
|
||||
if(j == 0) gain = conf->HFOrderGain[0];
|
||||
else if(j == 1) gain = conf->HFOrderGain[1];
|
||||
else if(j == 3) gain = conf->HFOrderGain[2];
|
||||
else if(j == 5) gain = conf->HFOrderGain[3];
|
||||
if((conf->ChanMask&(1<<l)))
|
||||
dec->Matrix.Single[chan][j] = conf->HFMatrix[i][k++] / coeff_scale[l] *
|
||||
gain;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(j = 0,k = 0;j < MAX_AMBI_COEFFS;j++)
|
||||
{
|
||||
if(j == 0) gain = conf->HFOrderGain[0];
|
||||
else if(j == 1) gain = conf->HFOrderGain[1];
|
||||
else if(j == 4) gain = conf->HFOrderGain[2];
|
||||
else if(j == 9) gain = conf->HFOrderGain[3];
|
||||
if((conf->ChanMask&(1<<j)))
|
||||
dec->Matrix.Single[chan][j] = conf->HFMatrix[i][k++] / coeff_scale[j] *
|
||||
gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dec->DualBand = AL_TRUE;
|
||||
|
||||
ratio = conf->XOverFreq / (ALfloat)srate;
|
||||
for(i = 0;i < MAX_AMBI_COEFFS;i++)
|
||||
bandsplit_init(&dec->XOver[i], ratio);
|
||||
|
||||
ratio = powf(10.0f, conf->XOverRatio / 40.0f);
|
||||
for(i = 0;i < conf->NumSpeakers;i++)
|
||||
{
|
||||
ALsizei chan = chanmap[i];
|
||||
ALfloat gain;
|
||||
ALsizei j, k;
|
||||
|
||||
if(!periphonic)
|
||||
{
|
||||
for(j = 0,k = 0;j < MAX_AMBI2D_COEFFS;j++)
|
||||
{
|
||||
ALsizei l = map2DTo3D[j];
|
||||
if(j == 0) gain = conf->HFOrderGain[0] * ratio;
|
||||
else if(j == 1) gain = conf->HFOrderGain[1] * ratio;
|
||||
else if(j == 3) gain = conf->HFOrderGain[2] * ratio;
|
||||
else if(j == 5) gain = conf->HFOrderGain[3] * ratio;
|
||||
if((conf->ChanMask&(1<<l)))
|
||||
dec->Matrix.Dual[chan][HF_BAND][j] = conf->HFMatrix[i][k++] /
|
||||
coeff_scale[l] * gain;
|
||||
}
|
||||
for(j = 0,k = 0;j < MAX_AMBI2D_COEFFS;j++)
|
||||
{
|
||||
ALsizei l = map2DTo3D[j];
|
||||
if(j == 0) gain = conf->LFOrderGain[0] / ratio;
|
||||
else if(j == 1) gain = conf->LFOrderGain[1] / ratio;
|
||||
else if(j == 3) gain = conf->LFOrderGain[2] / ratio;
|
||||
else if(j == 5) gain = conf->LFOrderGain[3] / ratio;
|
||||
if((conf->ChanMask&(1<<l)))
|
||||
dec->Matrix.Dual[chan][LF_BAND][j] = conf->LFMatrix[i][k++] /
|
||||
coeff_scale[l] * gain;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(j = 0,k = 0;j < MAX_AMBI_COEFFS;j++)
|
||||
{
|
||||
if(j == 0) gain = conf->HFOrderGain[0] * ratio;
|
||||
else if(j == 1) gain = conf->HFOrderGain[1] * ratio;
|
||||
else if(j == 4) gain = conf->HFOrderGain[2] * ratio;
|
||||
else if(j == 9) gain = conf->HFOrderGain[3] * ratio;
|
||||
if((conf->ChanMask&(1<<j)))
|
||||
dec->Matrix.Dual[chan][HF_BAND][j] = conf->HFMatrix[i][k++] /
|
||||
coeff_scale[j] * gain;
|
||||
}
|
||||
for(j = 0,k = 0;j < MAX_AMBI_COEFFS;j++)
|
||||
{
|
||||
if(j == 0) gain = conf->LFOrderGain[0] / ratio;
|
||||
else if(j == 1) gain = conf->LFOrderGain[1] / ratio;
|
||||
else if(j == 4) gain = conf->LFOrderGain[2] / ratio;
|
||||
else if(j == 9) gain = conf->LFOrderGain[3] / ratio;
|
||||
if((conf->ChanMask&(1<<j)))
|
||||
dec->Matrix.Dual[chan][LF_BAND][j] = conf->LFMatrix[i][k++] /
|
||||
coeff_scale[j] * gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void bformatdec_process(struct BFormatDec *dec, ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALsizei OutChannels, const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo)
|
||||
{
|
||||
ALsizei chan, i;
|
||||
|
||||
OutBuffer = ASSUME_ALIGNED(OutBuffer, 16);
|
||||
if(dec->DualBand)
|
||||
{
|
||||
for(i = 0;i < dec->NumChannels;i++)
|
||||
bandsplit_process(&dec->XOver[i], dec->SamplesHF[i], dec->SamplesLF[i],
|
||||
InSamples[i], SamplesToDo);
|
||||
|
||||
for(chan = 0;chan < OutChannels;chan++)
|
||||
{
|
||||
if(!(dec->Enabled&(1<<chan)))
|
||||
continue;
|
||||
|
||||
memset(dec->ChannelMix, 0, SamplesToDo*sizeof(ALfloat));
|
||||
MixRowSamples(dec->ChannelMix, dec->Matrix.Dual[chan][HF_BAND],
|
||||
dec->SamplesHF, dec->NumChannels, 0, SamplesToDo
|
||||
);
|
||||
MixRowSamples(dec->ChannelMix, dec->Matrix.Dual[chan][LF_BAND],
|
||||
dec->SamplesLF, dec->NumChannels, 0, SamplesToDo
|
||||
);
|
||||
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
OutBuffer[chan][i] += dec->ChannelMix[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(chan = 0;chan < OutChannels;chan++)
|
||||
{
|
||||
if(!(dec->Enabled&(1<<chan)))
|
||||
continue;
|
||||
|
||||
memset(dec->ChannelMix, 0, SamplesToDo*sizeof(ALfloat));
|
||||
MixRowSamples(dec->ChannelMix, dec->Matrix.Single[chan], InSamples,
|
||||
dec->NumChannels, 0, SamplesToDo);
|
||||
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
OutBuffer[chan][i] += dec->ChannelMix[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void bformatdec_upSample(struct BFormatDec *dec, ALfloat (*restrict OutBuffer)[BUFFERSIZE], const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei InChannels, ALsizei SamplesToDo)
|
||||
{
|
||||
ALsizei i;
|
||||
|
||||
/* This up-sampler leverages the differences observed in dual-band second-
|
||||
* and third-order decoder matrices compared to first-order. For the same
|
||||
* output channel configuration, the low-frequency matrix has identical
|
||||
* coefficients in the shared input channels, while the high-frequency
|
||||
* matrix has extra scalars applied to the W channel and X/Y/Z channels.
|
||||
* Mixing the first-order content into the higher-order stream with the
|
||||
* appropriate counter-scales applied to the HF response results in the
|
||||
* subsequent higher-order decode generating the same response as a first-
|
||||
* order decode.
|
||||
*/
|
||||
for(i = 0;i < InChannels;i++)
|
||||
{
|
||||
/* First, split the first-order components into low and high frequency
|
||||
* bands.
|
||||
*/
|
||||
bandsplit_process(&dec->UpSampler[i].XOver,
|
||||
dec->Samples[HF_BAND], dec->Samples[LF_BAND],
|
||||
InSamples[i], SamplesToDo
|
||||
);
|
||||
|
||||
/* Now write each band to the output. */
|
||||
MixRowSamples(OutBuffer[i], dec->UpSampler[i].Gains,
|
||||
dec->Samples, NUM_BANDS, 0, SamplesToDo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define INVALID_UPSAMPLE_INDEX INT_MAX
|
||||
|
||||
static ALsizei GetACNIndex(const BFChannelConfig *chans, ALsizei numchans, ALsizei acn)
|
||||
{
|
||||
ALsizei i;
|
||||
for(i = 0;i < numchans;i++)
|
||||
{
|
||||
if(chans[i].Index == acn)
|
||||
return i;
|
||||
}
|
||||
return INVALID_UPSAMPLE_INDEX;
|
||||
}
|
||||
#define GetChannelForACN(b, a) GetACNIndex((b).Ambi.Map, (b).NumChannels, (a))
|
||||
|
||||
typedef struct AmbiUpsampler {
|
||||
alignas(16) ALfloat Samples[NUM_BANDS][BUFFERSIZE];
|
||||
|
||||
BandSplitter XOver[4];
|
||||
|
||||
ALfloat Gains[4][MAX_OUTPUT_CHANNELS][NUM_BANDS];
|
||||
} AmbiUpsampler;
|
||||
|
||||
AmbiUpsampler *ambiup_alloc()
|
||||
{
|
||||
return al_calloc(16, sizeof(AmbiUpsampler));
|
||||
}
|
||||
|
||||
void ambiup_free(struct AmbiUpsampler **ambiup)
|
||||
{
|
||||
if(ambiup)
|
||||
{
|
||||
al_free(*ambiup);
|
||||
*ambiup = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ambiup_reset(struct AmbiUpsampler *ambiup, const ALCdevice *device, ALfloat w_scale, ALfloat xyz_scale)
|
||||
{
|
||||
ALfloat ratio;
|
||||
ALsizei i;
|
||||
|
||||
ratio = 400.0f / (ALfloat)device->Frequency;
|
||||
for(i = 0;i < 4;i++)
|
||||
bandsplit_init(&ambiup->XOver[i], ratio);
|
||||
|
||||
memset(ambiup->Gains, 0, sizeof(ambiup->Gains));
|
||||
if(device->Dry.CoeffCount > 0)
|
||||
{
|
||||
ALfloat encgains[8][MAX_OUTPUT_CHANNELS];
|
||||
ALsizei j;
|
||||
size_t k;
|
||||
|
||||
for(k = 0;k < COUNTOF(Ambi3DPoints);k++)
|
||||
{
|
||||
ALfloat coeffs[MAX_AMBI_COEFFS] = { 0.0f };
|
||||
CalcDirectionCoeffs(Ambi3DPoints[k], 0.0f, coeffs);
|
||||
ComputePanGains(&device->Dry, coeffs, 1.0f, encgains[k]);
|
||||
}
|
||||
|
||||
/* Combine the matrices that do the in->virt and virt->out conversions
|
||||
* so we get a single in->out conversion. NOTE: the Encoder matrix
|
||||
* (encgains) and output are transposed, so the input channels line up
|
||||
* with the rows and the output channels line up with the columns.
|
||||
*/
|
||||
for(i = 0;i < 4;i++)
|
||||
{
|
||||
for(j = 0;j < device->Dry.NumChannels;j++)
|
||||
{
|
||||
ALdouble gain = 0.0;
|
||||
for(k = 0;k < COUNTOF(Ambi3DDecoder);k++)
|
||||
gain += (ALdouble)Ambi3DDecoder[k][i] * encgains[k][j];
|
||||
ambiup->Gains[i][j][HF_BAND] = (ALfloat)(gain * Ambi3DDecoderHFScale[i]);
|
||||
ambiup->Gains[i][j][LF_BAND] = (ALfloat)gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(i = 0;i < 4;i++)
|
||||
{
|
||||
ALsizei index = GetChannelForACN(device->Dry, i);
|
||||
if(index != INVALID_UPSAMPLE_INDEX)
|
||||
{
|
||||
ALfloat scale = device->Dry.Ambi.Map[index].Scale;
|
||||
ambiup->Gains[i][index][HF_BAND] = scale * ((i==0) ? w_scale : xyz_scale);
|
||||
ambiup->Gains[i][index][LF_BAND] = scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ambiup_process(struct AmbiUpsampler *ambiup, ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALsizei OutChannels, const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo)
|
||||
{
|
||||
ALsizei i, j;
|
||||
|
||||
for(i = 0;i < 4;i++)
|
||||
{
|
||||
bandsplit_process(&ambiup->XOver[i],
|
||||
ambiup->Samples[HF_BAND], ambiup->Samples[LF_BAND],
|
||||
InSamples[i], SamplesToDo
|
||||
);
|
||||
|
||||
for(j = 0;j < OutChannels;j++)
|
||||
MixRowSamples(OutBuffer[j], ambiup->Gains[i][j],
|
||||
ambiup->Samples, NUM_BANDS, 0, SamplesToDo
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef BFORMATDEC_H
|
||||
#define BFORMATDEC_H
|
||||
|
||||
#include "alMain.h"
|
||||
|
||||
|
||||
/* These are the necessary scales for first-order HF responses to play over
|
||||
* higher-order 2D (non-periphonic) decoders.
|
||||
*/
|
||||
#define W_SCALE_2H0P 1.224744871f /* sqrt(1.5) */
|
||||
#define XYZ_SCALE_2H0P 1.0f
|
||||
#define W_SCALE_3H0P 1.414213562f /* sqrt(2) */
|
||||
#define XYZ_SCALE_3H0P 1.082392196f
|
||||
|
||||
/* These are the necessary scales for first-order HF responses to play over
|
||||
* higher-order 3D (periphonic) decoders.
|
||||
*/
|
||||
#define W_SCALE_2H2P 1.341640787f /* sqrt(1.8) */
|
||||
#define XYZ_SCALE_2H2P 1.0f
|
||||
#define W_SCALE_3H3P 1.695486018f
|
||||
#define XYZ_SCALE_3H3P 1.136697713f
|
||||
|
||||
|
||||
/* NOTE: These are scale factors as applied to Ambisonics content. Decoder
|
||||
* coefficients should be divided by these values to get proper N3D scalings.
|
||||
*/
|
||||
const ALfloat N3D2N3DScale[MAX_AMBI_COEFFS];
|
||||
const ALfloat SN3D2N3DScale[MAX_AMBI_COEFFS];
|
||||
const ALfloat FuMa2N3DScale[MAX_AMBI_COEFFS];
|
||||
|
||||
|
||||
struct AmbDecConf;
|
||||
struct BFormatDec;
|
||||
struct AmbiUpsampler;
|
||||
|
||||
|
||||
struct BFormatDec *bformatdec_alloc();
|
||||
void bformatdec_free(struct BFormatDec **dec);
|
||||
void bformatdec_reset(struct BFormatDec *dec, const struct AmbDecConf *conf, ALsizei chancount, ALuint srate, const ALsizei chanmap[MAX_OUTPUT_CHANNELS]);
|
||||
|
||||
/* Decodes the ambisonic input to the given output channels. */
|
||||
void bformatdec_process(struct BFormatDec *dec, ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALsizei OutChannels, const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo);
|
||||
|
||||
/* Up-samples a first-order input to the decoder's configuration. */
|
||||
void bformatdec_upSample(struct BFormatDec *dec, ALfloat (*restrict OutBuffer)[BUFFERSIZE], const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei InChannels, ALsizei SamplesToDo);
|
||||
|
||||
|
||||
/* Stand-alone first-order upsampler. Kept here because it shares some stuff
|
||||
* with bformatdec. Assumes a periphonic (4-channel) input mix!
|
||||
*/
|
||||
struct AmbiUpsampler *ambiup_alloc();
|
||||
void ambiup_free(struct AmbiUpsampler **ambiup);
|
||||
void ambiup_reset(struct AmbiUpsampler *ambiup, const ALCdevice *device, ALfloat w_scale, ALfloat xyz_scale);
|
||||
|
||||
void ambiup_process(struct AmbiUpsampler *ambiup, ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALsizei OutChannels, const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo);
|
||||
|
||||
#endif /* BFORMATDEC_H */
|
||||
+42
-38
@@ -23,19 +23,18 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bs2b.h"
|
||||
#include "math_defs.h"
|
||||
#include "alu.h"
|
||||
|
||||
|
||||
/* Set up all data. */
|
||||
static void init(struct bs2b *bs2b)
|
||||
{
|
||||
float Fc_lo, Fc_hi;
|
||||
float G_lo, G_hi;
|
||||
float G_lo, G_hi;
|
||||
float x, g;
|
||||
|
||||
switch(bs2b->level)
|
||||
@@ -91,11 +90,11 @@ static void init(struct bs2b *bs2b)
|
||||
* $d = 1 / 2 / pi / $fc;
|
||||
* $x = exp(-1 / $d);
|
||||
*/
|
||||
x = std::exp(-al::MathDefs<float>::Tau() * Fc_lo / static_cast<float>(bs2b->srate));
|
||||
x = expf(-2.0f * F_PI * Fc_lo / bs2b->srate);
|
||||
bs2b->b1_lo = x;
|
||||
bs2b->a0_lo = G_lo * (1.0f - x) * g;
|
||||
|
||||
x = std::exp(-al::MathDefs<float>::Tau() * Fc_hi / static_cast<float>(bs2b->srate));
|
||||
x = expf(-2.0f * F_PI * Fc_hi / bs2b->srate);
|
||||
bs2b->b1_hi = x;
|
||||
bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g;
|
||||
bs2b->a1_hi = -x * g;
|
||||
@@ -127,55 +126,60 @@ int bs2b_get_srate(struct bs2b *bs2b)
|
||||
|
||||
void bs2b_clear(struct bs2b *bs2b)
|
||||
{
|
||||
std::fill(std::begin(bs2b->history), std::end(bs2b->history), bs2b::t_last_sample{});
|
||||
memset(&bs2b->last_sample, 0, sizeof(bs2b->last_sample));
|
||||
} /* bs2b_clear */
|
||||
|
||||
void bs2b_cross_feed(struct bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo)
|
||||
void bs2b_cross_feed(struct bs2b *bs2b, float *restrict Left, float *restrict Right, int SamplesToDo)
|
||||
{
|
||||
const float a0_lo{bs2b->a0_lo};
|
||||
const float b1_lo{bs2b->b1_lo};
|
||||
const float a0_hi{bs2b->a0_hi};
|
||||
const float a1_hi{bs2b->a1_hi};
|
||||
const float b1_hi{bs2b->b1_hi};
|
||||
float lsamples[128][2];
|
||||
float rsamples[128][2];
|
||||
int base;
|
||||
|
||||
for(size_t base{0};base < SamplesToDo;)
|
||||
for(base = 0;base < SamplesToDo;)
|
||||
{
|
||||
const size_t todo{std::min<size_t>(128, SamplesToDo-base)};
|
||||
int todo = mini(128, SamplesToDo-base);
|
||||
int i;
|
||||
|
||||
/* Process left input */
|
||||
float z_lo{bs2b->history[0].lo};
|
||||
float z_hi{bs2b->history[0].hi};
|
||||
for(size_t i{0};i < todo;i++)
|
||||
lsamples[0][0] = bs2b->a0_lo*Left[0] +
|
||||
bs2b->b1_lo*bs2b->last_sample[0].lo;
|
||||
lsamples[0][1] = bs2b->a0_hi*Left[0] +
|
||||
bs2b->a1_hi*bs2b->last_sample[0].asis +
|
||||
bs2b->b1_hi*bs2b->last_sample[0].hi;
|
||||
for(i = 1;i < todo;i++)
|
||||
{
|
||||
lsamples[i][0] = a0_lo*Left[i] + z_lo;
|
||||
z_lo = b1_lo*lsamples[i][0];
|
||||
|
||||
lsamples[i][1] = a0_hi*Left[i] + z_hi;
|
||||
z_hi = a1_hi*Left[i] + b1_hi*lsamples[i][1];
|
||||
lsamples[i][0] = bs2b->a0_lo*Left[i] +
|
||||
bs2b->b1_lo*lsamples[i-1][0];
|
||||
lsamples[i][1] = bs2b->a0_hi*Left[i] +
|
||||
bs2b->a1_hi*Left[i-1] +
|
||||
bs2b->b1_hi*lsamples[i-1][1];
|
||||
}
|
||||
bs2b->history[0].lo = z_lo;
|
||||
bs2b->history[0].hi = z_hi;
|
||||
bs2b->last_sample[0].asis = Left[i-1];
|
||||
bs2b->last_sample[0].lo = lsamples[i-1][0];
|
||||
bs2b->last_sample[0].hi = lsamples[i-1][1];
|
||||
|
||||
/* Process right input */
|
||||
z_lo = bs2b->history[1].lo;
|
||||
z_hi = bs2b->history[1].hi;
|
||||
for(size_t i{0};i < todo;i++)
|
||||
rsamples[0][0] = bs2b->a0_lo*Right[0] +
|
||||
bs2b->b1_lo*bs2b->last_sample[1].lo;
|
||||
rsamples[0][1] = bs2b->a0_hi*Right[0] +
|
||||
bs2b->a1_hi*bs2b->last_sample[1].asis +
|
||||
bs2b->b1_hi*bs2b->last_sample[1].hi;
|
||||
for(i = 1;i < todo;i++)
|
||||
{
|
||||
rsamples[i][0] = a0_lo*Right[i] + z_lo;
|
||||
z_lo = b1_lo*rsamples[i][0];
|
||||
|
||||
rsamples[i][1] = a0_hi*Right[i] + z_hi;
|
||||
z_hi = a1_hi*Right[i] + b1_hi*rsamples[i][1];
|
||||
rsamples[i][0] = bs2b->a0_lo*Right[i] +
|
||||
bs2b->b1_lo*rsamples[i-1][0];
|
||||
rsamples[i][1] = bs2b->a0_hi*Right[i] +
|
||||
bs2b->a1_hi*Right[i-1] +
|
||||
bs2b->b1_hi*rsamples[i-1][1];
|
||||
}
|
||||
bs2b->history[1].lo = z_lo;
|
||||
bs2b->history[1].hi = z_hi;
|
||||
bs2b->last_sample[1].asis = Right[i-1];
|
||||
bs2b->last_sample[1].lo = rsamples[i-1][0];
|
||||
bs2b->last_sample[1].hi = rsamples[i-1][1];
|
||||
|
||||
/* Crossfeed */
|
||||
for(size_t i{0};i < todo;i++)
|
||||
for(i = 0;i < todo;i++)
|
||||
*(Left++) = lsamples[i][1] + rsamples[i][0];
|
||||
for(size_t i{0};i < todo;i++)
|
||||
for(i = 0;i < todo;i++)
|
||||
*(Right++) = rsamples[i][1] + lsamples[i][0];
|
||||
|
||||
base += todo;
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef AL_COMPAT_H
|
||||
#define AL_COMPAT_H
|
||||
|
||||
#include "alstring.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
WCHAR *strdupW(const WCHAR *str);
|
||||
|
||||
/* Opens a file with standard I/O. The filename is expected to be UTF-8. */
|
||||
FILE *al_fopen(const char *fname, const char *mode);
|
||||
|
||||
#define HAVE_DYNLOAD 1
|
||||
|
||||
#else
|
||||
|
||||
#define al_fopen fopen
|
||||
|
||||
#if defined(HAVE_DLFCN_H) && !defined(IN_IDE_PARSER)
|
||||
#define HAVE_DYNLOAD 1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
struct FileMapping {
|
||||
#ifdef _WIN32
|
||||
HANDLE file;
|
||||
HANDLE fmap;
|
||||
#else
|
||||
int fd;
|
||||
#endif
|
||||
void *ptr;
|
||||
size_t len;
|
||||
};
|
||||
struct FileMapping MapFileToMem(const char *fname);
|
||||
void UnmapFileMem(const struct FileMapping *mapping);
|
||||
|
||||
void GetProcBinary(al_string *path, al_string *fname);
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
void *LoadLib(const char *name);
|
||||
void CloseLib(void *handle);
|
||||
void *GetSymbol(void *handle, const char *name);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* AL_COMPAT_H */
|
||||
+468
@@ -0,0 +1,468 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "converter.h"
|
||||
|
||||
#include "fpu_modes.h"
|
||||
#include "mixer/defs.h"
|
||||
|
||||
|
||||
SampleConverter *CreateSampleConverter(enum DevFmtType srcType, enum DevFmtType dstType, ALsizei numchans, ALsizei srcRate, ALsizei dstRate)
|
||||
{
|
||||
SampleConverter *converter;
|
||||
ALsizei step;
|
||||
|
||||
if(numchans <= 0 || srcRate <= 0 || dstRate <= 0)
|
||||
return NULL;
|
||||
|
||||
converter = al_calloc(16, FAM_SIZE(SampleConverter, Chan, numchans));
|
||||
converter->mSrcType = srcType;
|
||||
converter->mDstType = dstType;
|
||||
converter->mNumChannels = numchans;
|
||||
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. */
|
||||
START_MIXER_MODE();
|
||||
step = (ALsizei)mind(((ALdouble)srcRate/dstRate*FRACTIONONE) + 0.5,
|
||||
MAX_PITCH * FRACTIONONE);
|
||||
converter->mIncrement = maxi(step, 1);
|
||||
if(converter->mIncrement == FRACTIONONE)
|
||||
converter->mResample = Resample_copy_C;
|
||||
else
|
||||
{
|
||||
/* TODO: Allow other resamplers. */
|
||||
BsincPrepare(converter->mIncrement, &converter->mState.bsinc, &bsinc12);
|
||||
converter->mResample = SelectResampler(BSinc12Resampler);
|
||||
}
|
||||
END_MIXER_MODE();
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
void DestroySampleConverter(SampleConverter **converter)
|
||||
{
|
||||
if(converter)
|
||||
{
|
||||
al_free(*converter);
|
||||
*converter = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static inline ALfloat Sample_ALbyte(ALbyte val)
|
||||
{ return val * (1.0f/128.0f); }
|
||||
static inline ALfloat Sample_ALubyte(ALubyte val)
|
||||
{ return Sample_ALbyte((ALint)val - 128); }
|
||||
|
||||
static inline ALfloat Sample_ALshort(ALshort val)
|
||||
{ return val * (1.0f/32768.0f); }
|
||||
static inline ALfloat Sample_ALushort(ALushort val)
|
||||
{ return Sample_ALshort((ALint)val - 32768); }
|
||||
|
||||
static inline ALfloat Sample_ALint(ALint val)
|
||||
{ return (val>>7) * (1.0f/16777216.0f); }
|
||||
static inline ALfloat Sample_ALuint(ALuint val)
|
||||
{ return Sample_ALint(val - INT_MAX - 1); }
|
||||
|
||||
static inline ALfloat Sample_ALfloat(ALfloat val)
|
||||
{ return val; }
|
||||
|
||||
#define DECL_TEMPLATE(T) \
|
||||
static inline void Load_##T(ALfloat *restrict dst, const T *restrict src, \
|
||||
ALint srcstep, ALsizei samples) \
|
||||
{ \
|
||||
ALsizei i; \
|
||||
for(i = 0;i < samples;i++) \
|
||||
dst[i] = Sample_##T(src[i*srcstep]); \
|
||||
}
|
||||
|
||||
DECL_TEMPLATE(ALbyte)
|
||||
DECL_TEMPLATE(ALubyte)
|
||||
DECL_TEMPLATE(ALshort)
|
||||
DECL_TEMPLATE(ALushort)
|
||||
DECL_TEMPLATE(ALint)
|
||||
DECL_TEMPLATE(ALuint)
|
||||
DECL_TEMPLATE(ALfloat)
|
||||
|
||||
#undef DECL_TEMPLATE
|
||||
|
||||
static void LoadSamples(ALfloat *dst, const ALvoid *src, ALint srcstep, enum DevFmtType srctype, ALsizei samples)
|
||||
{
|
||||
switch(srctype)
|
||||
{
|
||||
case DevFmtByte:
|
||||
Load_ALbyte(dst, src, srcstep, samples);
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
Load_ALubyte(dst, src, srcstep, samples);
|
||||
break;
|
||||
case DevFmtShort:
|
||||
Load_ALshort(dst, src, srcstep, samples);
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
Load_ALushort(dst, src, srcstep, samples);
|
||||
break;
|
||||
case DevFmtInt:
|
||||
Load_ALint(dst, src, srcstep, samples);
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
Load_ALuint(dst, src, srcstep, samples);
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
Load_ALfloat(dst, src, srcstep, samples);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static inline ALbyte ALbyte_Sample(ALfloat val)
|
||||
{ return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); }
|
||||
static inline ALubyte ALubyte_Sample(ALfloat val)
|
||||
{ return ALbyte_Sample(val)+128; }
|
||||
|
||||
static inline ALshort ALshort_Sample(ALfloat val)
|
||||
{ return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); }
|
||||
static inline ALushort ALushort_Sample(ALfloat val)
|
||||
{ return ALshort_Sample(val)+32768; }
|
||||
|
||||
static inline ALint ALint_Sample(ALfloat val)
|
||||
{ return fastf2i(clampf(val*16777216.0f, -16777216.0f, 16777215.0f)) << 7; }
|
||||
static inline ALuint ALuint_Sample(ALfloat val)
|
||||
{ return ALint_Sample(val)+INT_MAX+1; }
|
||||
|
||||
static inline ALfloat ALfloat_Sample(ALfloat val)
|
||||
{ return val; }
|
||||
|
||||
#define DECL_TEMPLATE(T) \
|
||||
static inline void Store_##T(T *restrict dst, const ALfloat *restrict src, \
|
||||
ALint dststep, ALsizei samples) \
|
||||
{ \
|
||||
ALsizei i; \
|
||||
for(i = 0;i < samples;i++) \
|
||||
dst[i*dststep] = T##_Sample(src[i]); \
|
||||
}
|
||||
|
||||
DECL_TEMPLATE(ALbyte)
|
||||
DECL_TEMPLATE(ALubyte)
|
||||
DECL_TEMPLATE(ALshort)
|
||||
DECL_TEMPLATE(ALushort)
|
||||
DECL_TEMPLATE(ALint)
|
||||
DECL_TEMPLATE(ALuint)
|
||||
DECL_TEMPLATE(ALfloat)
|
||||
|
||||
#undef DECL_TEMPLATE
|
||||
|
||||
static void StoreSamples(ALvoid *dst, const ALfloat *src, ALint dststep, enum DevFmtType dsttype, ALsizei samples)
|
||||
{
|
||||
switch(dsttype)
|
||||
{
|
||||
case DevFmtByte:
|
||||
Store_ALbyte(dst, src, dststep, samples);
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
Store_ALubyte(dst, src, dststep, samples);
|
||||
break;
|
||||
case DevFmtShort:
|
||||
Store_ALshort(dst, src, dststep, samples);
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
Store_ALushort(dst, src, dststep, samples);
|
||||
break;
|
||||
case DevFmtInt:
|
||||
Store_ALint(dst, src, dststep, samples);
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
Store_ALuint(dst, src, dststep, samples);
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
Store_ALfloat(dst, src, dststep, samples);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ALsizei SampleConverterAvailableOut(SampleConverter *converter, ALsizei srcframes)
|
||||
{
|
||||
ALint prepcount = converter->mSrcPrepCount;
|
||||
ALsizei increment = converter->mIncrement;
|
||||
ALsizei DataPosFrac = converter->mFracOffset;
|
||||
ALuint64 DataSize64;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
DataSize64 = prepcount;
|
||||
DataSize64 += srcframes;
|
||||
DataSize64 -= MAX_RESAMPLE_PADDING*2;
|
||||
DataSize64 <<= FRACTIONBITS;
|
||||
DataSize64 -= DataPosFrac;
|
||||
|
||||
/* If we have a full prep, we can generate at least one sample. */
|
||||
return (ALsizei)clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE);
|
||||
}
|
||||
|
||||
|
||||
ALsizei SampleConverterInput(SampleConverter *converter, const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes)
|
||||
{
|
||||
const ALsizei SrcFrameSize = converter->mNumChannels * converter->mSrcTypeSize;
|
||||
const ALsizei DstFrameSize = converter->mNumChannels * converter->mDstTypeSize;
|
||||
const ALsizei increment = converter->mIncrement;
|
||||
ALsizei pos = 0;
|
||||
|
||||
START_MIXER_MODE();
|
||||
while(pos < dstframes && *srcframes > 0)
|
||||
{
|
||||
ALfloat *restrict SrcData = ASSUME_ALIGNED(converter->mSrcSamples, 16);
|
||||
ALfloat *restrict DstData = ASSUME_ALIGNED(converter->mDstSamples, 16);
|
||||
ALint prepcount = converter->mSrcPrepCount;
|
||||
ALsizei DataPosFrac = converter->mFracOffset;
|
||||
ALuint64 DataSize64;
|
||||
ALsizei DstSize;
|
||||
ALint toread;
|
||||
ALsizei chan;
|
||||
|
||||
if(prepcount < 0)
|
||||
{
|
||||
/* Negative prepcount means we need to skip that many input samples. */
|
||||
if(-prepcount >= *srcframes)
|
||||
{
|
||||
converter->mSrcPrepCount = prepcount + *srcframes;
|
||||
*srcframes = 0;
|
||||
break;
|
||||
}
|
||||
*src = (const ALbyte*)*src + SrcFrameSize*-prepcount;
|
||||
*srcframes += prepcount;
|
||||
converter->mSrcPrepCount = 0;
|
||||
continue;
|
||||
}
|
||||
toread = mini(*srcframes, 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(chan = 0;chan < converter->mNumChannels;chan++)
|
||||
LoadSamples(&converter->Chan[chan].mPrevSamples[prepcount],
|
||||
(const ALbyte*)*src + converter->mSrcTypeSize*chan,
|
||||
converter->mNumChannels, converter->mSrcType, toread
|
||||
);
|
||||
|
||||
converter->mSrcPrepCount = prepcount + toread;
|
||||
*srcframes = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
DataSize64 = 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. */
|
||||
DstSize = (ALsizei)clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE);
|
||||
DstSize = mini(DstSize, dstframes-pos);
|
||||
|
||||
for(chan = 0;chan < converter->mNumChannels;chan++)
|
||||
{
|
||||
const ALbyte *SrcSamples = (const ALbyte*)*src + converter->mSrcTypeSize*chan;
|
||||
ALbyte *DstSamples = (ALbyte*)dst + converter->mDstTypeSize*chan;
|
||||
const ALfloat *ResampledData;
|
||||
ALsizei SrcDataEnd;
|
||||
|
||||
/* Load the previous samples into the source data first, then the
|
||||
* new samples from the input buffer.
|
||||
*/
|
||||
memcpy(SrcData, converter->Chan[chan].mPrevSamples,
|
||||
prepcount*sizeof(ALfloat));
|
||||
LoadSamples(SrcData + prepcount, SrcSamples,
|
||||
converter->mNumChannels, converter->mSrcType, toread
|
||||
);
|
||||
|
||||
/* Store as many prep samples for next time as possible, given the
|
||||
* number of output samples being generated.
|
||||
*/
|
||||
SrcDataEnd = (DataPosFrac + increment*DstSize)>>FRACTIONBITS;
|
||||
if(SrcDataEnd >= prepcount+toread)
|
||||
memset(converter->Chan[chan].mPrevSamples, 0,
|
||||
sizeof(converter->Chan[chan].mPrevSamples));
|
||||
else
|
||||
{
|
||||
size_t len = mini(MAX_RESAMPLE_PADDING*2, prepcount+toread-SrcDataEnd);
|
||||
memcpy(converter->Chan[chan].mPrevSamples, &SrcData[SrcDataEnd],
|
||||
len*sizeof(ALfloat));
|
||||
memset(converter->Chan[chan].mPrevSamples+len, 0,
|
||||
sizeof(converter->Chan[chan].mPrevSamples) - len*sizeof(ALfloat));
|
||||
}
|
||||
|
||||
/* Now resample, and store the result in the output buffer. */
|
||||
ResampledData = converter->mResample(&converter->mState,
|
||||
SrcData+MAX_RESAMPLE_PADDING, DataPosFrac, increment,
|
||||
DstData, DstSize
|
||||
);
|
||||
|
||||
StoreSamples(DstSamples, ResampledData, converter->mNumChannels,
|
||||
converter->mDstType, DstSize);
|
||||
}
|
||||
|
||||
/* Update the number of prep samples still available, as well as the
|
||||
* fractional offset.
|
||||
*/
|
||||
DataPosFrac += increment*DstSize;
|
||||
converter->mSrcPrepCount = mini(prepcount + toread - (DataPosFrac>>FRACTIONBITS),
|
||||
MAX_RESAMPLE_PADDING*2);
|
||||
converter->mFracOffset = DataPosFrac & FRACTIONMASK;
|
||||
|
||||
/* Update the src and dst pointers in case there's still more to do. */
|
||||
*src = (const ALbyte*)*src + SrcFrameSize*(DataPosFrac>>FRACTIONBITS);
|
||||
*srcframes -= mini(*srcframes, (DataPosFrac>>FRACTIONBITS));
|
||||
|
||||
dst = (ALbyte*)dst + DstFrameSize*DstSize;
|
||||
pos += DstSize;
|
||||
}
|
||||
END_MIXER_MODE();
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
ChannelConverter *CreateChannelConverter(enum DevFmtType srcType, enum DevFmtChannels srcChans, enum DevFmtChannels dstChans)
|
||||
{
|
||||
ChannelConverter *converter;
|
||||
|
||||
if(srcChans != dstChans && !((srcChans == DevFmtMono && dstChans == DevFmtStereo) ||
|
||||
(srcChans == DevFmtStereo && dstChans == DevFmtMono)))
|
||||
return NULL;
|
||||
|
||||
converter = al_calloc(DEF_ALIGN, sizeof(*converter));
|
||||
converter->mSrcType = srcType;
|
||||
converter->mSrcChans = srcChans;
|
||||
converter->mDstChans = dstChans;
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
void DestroyChannelConverter(ChannelConverter **converter)
|
||||
{
|
||||
if(converter)
|
||||
{
|
||||
al_free(*converter);
|
||||
*converter = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define DECL_TEMPLATE(T) \
|
||||
static void Mono2Stereo##T(ALfloat *restrict dst, const T *src, ALsizei frames)\
|
||||
{ \
|
||||
ALsizei i; \
|
||||
for(i = 0;i < frames;i++) \
|
||||
dst[i*2 + 1] = dst[i*2 + 0] = Sample_##T(src[i]) * 0.707106781187f; \
|
||||
} \
|
||||
\
|
||||
static void Stereo2Mono##T(ALfloat *restrict dst, const T *src, ALsizei frames)\
|
||||
{ \
|
||||
ALsizei i; \
|
||||
for(i = 0;i < frames;i++) \
|
||||
dst[i] = (Sample_##T(src[i*2 + 0])+Sample_##T(src[i*2 + 1])) * \
|
||||
0.707106781187f; \
|
||||
}
|
||||
|
||||
DECL_TEMPLATE(ALbyte)
|
||||
DECL_TEMPLATE(ALubyte)
|
||||
DECL_TEMPLATE(ALshort)
|
||||
DECL_TEMPLATE(ALushort)
|
||||
DECL_TEMPLATE(ALint)
|
||||
DECL_TEMPLATE(ALuint)
|
||||
DECL_TEMPLATE(ALfloat)
|
||||
|
||||
#undef DECL_TEMPLATE
|
||||
|
||||
void ChannelConverterInput(ChannelConverter *converter, const ALvoid *src, ALfloat *dst, ALsizei frames)
|
||||
{
|
||||
if(converter->mSrcChans == converter->mDstChans)
|
||||
{
|
||||
LoadSamples(dst, src, 1, converter->mSrcType,
|
||||
frames*ChannelsFromDevFmt(converter->mSrcChans, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
if(converter->mSrcChans == DevFmtStereo && converter->mDstChans == DevFmtMono)
|
||||
{
|
||||
switch(converter->mSrcType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
Stereo2MonoALbyte(dst, src, frames);
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
Stereo2MonoALubyte(dst, src, frames);
|
||||
break;
|
||||
case DevFmtShort:
|
||||
Stereo2MonoALshort(dst, src, frames);
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
Stereo2MonoALushort(dst, src, frames);
|
||||
break;
|
||||
case DevFmtInt:
|
||||
Stereo2MonoALint(dst, src, frames);
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
Stereo2MonoALuint(dst, src, frames);
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
Stereo2MonoALfloat(dst, src, frames);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else /*if(converter->mSrcChans == DevFmtMono && converter->mDstChans == DevFmtStereo)*/
|
||||
{
|
||||
switch(converter->mSrcType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
Mono2StereoALbyte(dst, src, frames);
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
Mono2StereoALubyte(dst, src, frames);
|
||||
break;
|
||||
case DevFmtShort:
|
||||
Mono2StereoALshort(dst, src, frames);
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
Mono2StereoALushort(dst, src, frames);
|
||||
break;
|
||||
case DevFmtInt:
|
||||
Mono2StereoALint(dst, src, frames);
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
Mono2StereoALuint(dst, src, frames);
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
Mono2StereoALfloat(dst, src, frames);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef CONVERTER_H
|
||||
#define CONVERTER_H
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
|
||||
#ifdef __cpluspluc
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct SampleConverter {
|
||||
enum DevFmtType mSrcType;
|
||||
enum DevFmtType mDstType;
|
||||
ALsizei mNumChannels;
|
||||
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 {
|
||||
alignas(16) ALfloat mPrevSamples[MAX_RESAMPLE_PADDING*2];
|
||||
} Chan[];
|
||||
} SampleConverter;
|
||||
|
||||
SampleConverter *CreateSampleConverter(enum DevFmtType srcType, enum DevFmtType dstType, ALsizei numchans, ALsizei srcRate, ALsizei dstRate);
|
||||
void DestroySampleConverter(SampleConverter **converter);
|
||||
|
||||
ALsizei SampleConverterInput(SampleConverter *converter, const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes);
|
||||
ALsizei SampleConverterAvailableOut(SampleConverter *converter, ALsizei srcframes);
|
||||
|
||||
|
||||
typedef struct ChannelConverter {
|
||||
enum DevFmtType mSrcType;
|
||||
enum DevFmtChannels mSrcChans;
|
||||
enum DevFmtChannels mDstChans;
|
||||
} ChannelConverter;
|
||||
|
||||
ChannelConverter *CreateChannelConverter(enum DevFmtType srcType, enum DevFmtChannels srcChans, enum DevFmtChannels dstChans);
|
||||
void DestroyChannelConverter(ChannelConverter **converter);
|
||||
|
||||
void ChannelConverterInput(ChannelConverter *converter, const ALvoid *src, ALfloat *dst, ALsizei frames);
|
||||
|
||||
#ifdef __cpluspluc
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CONVERTER_H */
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef CPU_CAPS_H
|
||||
#define CPU_CAPS_H
|
||||
|
||||
|
||||
extern int CPUCapFlags;
|
||||
enum {
|
||||
CPU_CAP_SSE = 1<<0,
|
||||
@@ -0,0 +1,321 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by Raul Herraiz.
|
||||
* 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 <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
#include "alu.h"
|
||||
#include "filters/defs.h"
|
||||
|
||||
#define MIN_FREQ 20.0f
|
||||
#define MAX_FREQ 2500.0f
|
||||
#define Q_FACTOR 5.0f
|
||||
|
||||
typedef struct ALautowahState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
|
||||
/* Effect parameters */
|
||||
ALfloat AttackRate;
|
||||
ALfloat ReleaseRate;
|
||||
ALfloat ResonanceGain;
|
||||
ALfloat PeakGain;
|
||||
ALfloat FreqMinNorm;
|
||||
ALfloat BandwidthNorm;
|
||||
ALfloat env_delay;
|
||||
|
||||
/* Filter components derived from the envelope. */
|
||||
struct {
|
||||
ALfloat cos_w0;
|
||||
ALfloat alpha;
|
||||
} Env[BUFFERSIZE];
|
||||
|
||||
struct {
|
||||
/* Effect filters' history. */
|
||||
struct {
|
||||
ALfloat z1, z2;
|
||||
} Filter;
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
|
||||
} Chans[MAX_EFFECT_CHANNELS];
|
||||
|
||||
/* Effects buffers */
|
||||
alignas(16) ALfloat BufferOut[BUFFERSIZE];
|
||||
} ALautowahState;
|
||||
|
||||
static ALvoid ALautowahState_Destruct(ALautowahState *state);
|
||||
static ALboolean ALautowahState_deviceUpdate(ALautowahState *state, ALCdevice *device);
|
||||
static ALvoid ALautowahState_update(ALautowahState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
|
||||
static ALvoid ALautowahState_process(ALautowahState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALautowahState)
|
||||
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALautowahState);
|
||||
|
||||
static void ALautowahState_Construct(ALautowahState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALautowahState, ALeffectState, state);
|
||||
}
|
||||
|
||||
static ALvoid ALautowahState_Destruct(ALautowahState *state)
|
||||
{
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
static ALboolean ALautowahState_deviceUpdate(ALautowahState *state, ALCdevice *UNUSED(device))
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
ALsizei i, j;
|
||||
|
||||
state->AttackRate = 1.0f;
|
||||
state->ReleaseRate = 1.0f;
|
||||
state->ResonanceGain = 10.0f;
|
||||
state->PeakGain = 4.5f;
|
||||
state->FreqMinNorm = 4.5e-4f;
|
||||
state->BandwidthNorm = 0.05f;
|
||||
state->env_delay = 0.0f;
|
||||
|
||||
memset(state->Env, 0, sizeof(state->Env));
|
||||
|
||||
for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
|
||||
{
|
||||
for(j = 0;j < MAX_OUTPUT_CHANNELS;j++)
|
||||
state->Chans[i].CurrentGains[j] = 0.0f;
|
||||
state->Chans[i].Filter.z1 = 0.0f;
|
||||
state->Chans[i].Filter.z2 = 0.0f;
|
||||
}
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
static ALvoid ALautowahState_update(ALautowahState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
|
||||
{
|
||||
const ALCdevice *device = context->Device;
|
||||
ALfloat ReleaseTime;
|
||||
ALsizei i;
|
||||
|
||||
ReleaseTime = clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f);
|
||||
|
||||
state->AttackRate = expf(-1.0f / (props->Autowah.AttackTime*device->Frequency));
|
||||
state->ReleaseRate = expf(-1.0f / (ReleaseTime*device->Frequency));
|
||||
/* 0-20dB Resonance Peak gain */
|
||||
state->ResonanceGain = sqrtf(log10f(props->Autowah.Resonance)*10.0f / 3.0f);
|
||||
state->PeakGain = 1.0f - log10f(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN);
|
||||
state->FreqMinNorm = MIN_FREQ / device->Frequency;
|
||||
state->BandwidthNorm = (MAX_FREQ-MIN_FREQ) / device->Frequency;
|
||||
|
||||
STATIC_CAST(ALeffectState,state)->OutBuffer = device->FOAOut.Buffer;
|
||||
STATIC_CAST(ALeffectState,state)->OutChannels = device->FOAOut.NumChannels;
|
||||
for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
|
||||
ComputePanGains(&device->FOAOut, IdentityMatrixf.m[i], slot->Params.Gain,
|
||||
state->Chans[i].TargetGains);
|
||||
}
|
||||
|
||||
static ALvoid ALautowahState_process(ALautowahState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
|
||||
{
|
||||
const ALfloat attack_rate = state->AttackRate;
|
||||
const ALfloat release_rate = state->ReleaseRate;
|
||||
const ALfloat res_gain = state->ResonanceGain;
|
||||
const ALfloat peak_gain = state->PeakGain;
|
||||
const ALfloat freq_min = state->FreqMinNorm;
|
||||
const ALfloat bandwidth = state->BandwidthNorm;
|
||||
ALfloat env_delay;
|
||||
ALsizei c, i;
|
||||
|
||||
env_delay = state->env_delay;
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
{
|
||||
ALfloat w0, sample, a;
|
||||
|
||||
/* Envelope follower described on the book: Audio Effects, Theory,
|
||||
* Implementation and Application.
|
||||
*/
|
||||
sample = peak_gain * fabsf(SamplesIn[0][i]);
|
||||
a = (sample > env_delay) ? attack_rate : release_rate;
|
||||
env_delay = lerp(sample, env_delay, a);
|
||||
|
||||
/* Calculate the cos and alpha components for this sample's filter. */
|
||||
w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * F_TAU;
|
||||
state->Env[i].cos_w0 = cosf(w0);
|
||||
state->Env[i].alpha = sinf(w0)/(2.0f * Q_FACTOR);
|
||||
}
|
||||
state->env_delay = env_delay;
|
||||
|
||||
for(c = 0;c < MAX_EFFECT_CHANNELS; c++)
|
||||
{
|
||||
/* This effectively inlines BiquadFilter_setParams for a peaking
|
||||
* filter and BiquadFilter_processC. The alpha and cosine components
|
||||
* for the filter coefficients were previously calculated with the
|
||||
* envelope. Because the filter changes for each sample, the
|
||||
* coefficients are transient and don't need to be held.
|
||||
*/
|
||||
ALfloat z1 = state->Chans[c].Filter.z1;
|
||||
ALfloat z2 = state->Chans[c].Filter.z2;
|
||||
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
{
|
||||
const ALfloat alpha = state->Env[i].alpha;
|
||||
const ALfloat cos_w0 = state->Env[i].cos_w0;
|
||||
ALfloat input, output;
|
||||
ALfloat a[3], b[3];
|
||||
|
||||
b[0] = 1.0f + alpha*res_gain;
|
||||
b[1] = -2.0f * cos_w0;
|
||||
b[2] = 1.0f - alpha*res_gain;
|
||||
a[0] = 1.0f + alpha/res_gain;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha/res_gain;
|
||||
|
||||
input = SamplesIn[c][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]);
|
||||
state->BufferOut[i] = output;
|
||||
}
|
||||
state->Chans[c].Filter.z1 = z1;
|
||||
state->Chans[c].Filter.z2 = z2;
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples(state->BufferOut, NumChannels, SamplesOut, state->Chans[c].CurrentGains,
|
||||
state->Chans[c].TargetGains, SamplesToDo, 0, SamplesToDo);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct AutowahStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} AutowahStateFactory;
|
||||
|
||||
static ALeffectState *AutowahStateFactory_create(AutowahStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALautowahState *state;
|
||||
|
||||
NEW_OBJ0(state, ALautowahState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(AutowahStateFactory);
|
||||
|
||||
EffectStateFactory *AutowahStateFactory_getFactory(void)
|
||||
{
|
||||
static AutowahStateFactory AutowahFactory = { { GET_VTABLE2(AutowahStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &AutowahFactory);
|
||||
}
|
||||
|
||||
void ALautowah_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{
|
||||
ALautowah_setParamf(effect, context, param, vals[0]);
|
||||
}
|
||||
|
||||
void ALautowah_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
|
||||
{
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param);
|
||||
}
|
||||
|
||||
void ALautowah_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
|
||||
{
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param);
|
||||
}
|
||||
|
||||
void ALautowah_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
|
||||
{
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param);
|
||||
}
|
||||
void ALautowah_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
|
||||
{
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param);
|
||||
}
|
||||
|
||||
void ALautowah_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
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 ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{
|
||||
ALautowah_getParamf(effect, context, param, vals);
|
||||
}
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALautowah);
|
||||
@@ -0,0 +1,555 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* 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 <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
#include "alu.h"
|
||||
#include "filters/defs.h"
|
||||
|
||||
|
||||
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 WaveForm {
|
||||
WF_Sinusoid,
|
||||
WF_Triangle
|
||||
};
|
||||
|
||||
typedef struct ALchorusState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
|
||||
ALfloat *SampleBuffer;
|
||||
ALsizei BufferLength;
|
||||
ALsizei offset;
|
||||
|
||||
ALsizei lfo_offset;
|
||||
ALsizei lfo_range;
|
||||
ALfloat lfo_scale;
|
||||
ALint lfo_disp;
|
||||
|
||||
/* Gains for left and right sides */
|
||||
struct {
|
||||
ALfloat Current[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat Target[MAX_OUTPUT_CHANNELS];
|
||||
} Gains[2];
|
||||
|
||||
/* effect parameters */
|
||||
enum WaveForm waveform;
|
||||
ALint delay;
|
||||
ALfloat depth;
|
||||
ALfloat feedback;
|
||||
} ALchorusState;
|
||||
|
||||
static ALvoid ALchorusState_Destruct(ALchorusState *state);
|
||||
static ALboolean ALchorusState_deviceUpdate(ALchorusState *state, ALCdevice *Device);
|
||||
static ALvoid ALchorusState_update(ALchorusState *state, const ALCcontext *Context, const ALeffectslot *Slot, const ALeffectProps *props);
|
||||
static ALvoid ALchorusState_process(ALchorusState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALchorusState)
|
||||
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALchorusState);
|
||||
|
||||
|
||||
static void ALchorusState_Construct(ALchorusState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALchorusState, ALeffectState, state);
|
||||
|
||||
state->BufferLength = 0;
|
||||
state->SampleBuffer = NULL;
|
||||
state->offset = 0;
|
||||
state->lfo_offset = 0;
|
||||
state->lfo_range = 1;
|
||||
state->waveform = WF_Triangle;
|
||||
}
|
||||
|
||||
static ALvoid ALchorusState_Destruct(ALchorusState *state)
|
||||
{
|
||||
al_free(state->SampleBuffer);
|
||||
state->SampleBuffer = NULL;
|
||||
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
static ALboolean ALchorusState_deviceUpdate(ALchorusState *state, ALCdevice *Device)
|
||||
{
|
||||
const ALfloat max_delay = maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY);
|
||||
ALsizei maxlen;
|
||||
|
||||
maxlen = NextPowerOf2(float2int(max_delay*2.0f*Device->Frequency) + 1u);
|
||||
if(maxlen <= 0) return AL_FALSE;
|
||||
|
||||
if(maxlen != state->BufferLength)
|
||||
{
|
||||
void *temp = al_calloc(16, maxlen * sizeof(ALfloat));
|
||||
if(!temp) return AL_FALSE;
|
||||
|
||||
al_free(state->SampleBuffer);
|
||||
state->SampleBuffer = temp;
|
||||
|
||||
state->BufferLength = maxlen;
|
||||
}
|
||||
|
||||
memset(state->SampleBuffer, 0, state->BufferLength*sizeof(ALfloat));
|
||||
memset(state->Gains, 0, sizeof(state->Gains));
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
static ALvoid ALchorusState_update(ALchorusState *state, const ALCcontext *Context, const ALeffectslot *Slot, const ALeffectProps *props)
|
||||
{
|
||||
const ALsizei mindelay = MAX_RESAMPLE_PADDING << FRACTIONBITS;
|
||||
const ALCdevice *device = Context->Device;
|
||||
ALfloat frequency = (ALfloat)device->Frequency;
|
||||
ALfloat coeffs[MAX_AMBI_COEFFS];
|
||||
ALfloat rate;
|
||||
ALint phase;
|
||||
|
||||
switch(props->Chorus.Waveform)
|
||||
{
|
||||
case AL_CHORUS_WAVEFORM_TRIANGLE:
|
||||
state->waveform = WF_Triangle;
|
||||
break;
|
||||
case AL_CHORUS_WAVEFORM_SINUSOID:
|
||||
state->waveform = WF_Sinusoid;
|
||||
break;
|
||||
}
|
||||
|
||||
/* The LFO depth is scaled to be relative to the sample delay. Clamp the
|
||||
* delay and depth to allow enough padding for resampling.
|
||||
*/
|
||||
state->delay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 0.5f),
|
||||
mindelay);
|
||||
state->depth = minf(props->Chorus.Depth * state->delay,
|
||||
(ALfloat)(state->delay - mindelay));
|
||||
|
||||
state->feedback = props->Chorus.Feedback;
|
||||
|
||||
/* Gains for left and right sides */
|
||||
CalcAngleCoeffs(-F_PI_2, 0.0f, 0.0f, coeffs);
|
||||
ComputePanGains(&device->Dry, coeffs, Slot->Params.Gain, state->Gains[0].Target);
|
||||
CalcAngleCoeffs( F_PI_2, 0.0f, 0.0f, coeffs);
|
||||
ComputePanGains(&device->Dry, coeffs, Slot->Params.Gain, state->Gains[1].Target);
|
||||
|
||||
phase = props->Chorus.Phase;
|
||||
rate = props->Chorus.Rate;
|
||||
if(!(rate > 0.0f))
|
||||
{
|
||||
state->lfo_offset = 0;
|
||||
state->lfo_range = 1;
|
||||
state->lfo_scale = 0.0f;
|
||||
state->lfo_disp = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 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, (ALfloat)(INT_MAX/360 - 180)));
|
||||
|
||||
state->lfo_offset = float2int((ALfloat)state->lfo_offset/state->lfo_range*
|
||||
lfo_range + 0.5f) % lfo_range;
|
||||
state->lfo_range = lfo_range;
|
||||
switch(state->waveform)
|
||||
{
|
||||
case WF_Triangle:
|
||||
state->lfo_scale = 4.0f / state->lfo_range;
|
||||
break;
|
||||
case WF_Sinusoid:
|
||||
state->lfo_scale = F_TAU / state->lfo_range;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Calculate lfo phase displacement */
|
||||
if(phase < 0) phase = 360 + phase;
|
||||
state->lfo_disp = (state->lfo_range*phase + 180) / 360;
|
||||
}
|
||||
}
|
||||
|
||||
static void GetTriangleDelays(ALint *restrict delays, ALsizei offset, const ALsizei lfo_range,
|
||||
const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay,
|
||||
const ALsizei todo)
|
||||
{
|
||||
ALsizei i;
|
||||
for(i = 0;i < todo;i++)
|
||||
{
|
||||
delays[i] = fastf2i((1.0f - fabsf(2.0f - lfo_scale*offset)) * depth) + delay;
|
||||
offset = (offset+1)%lfo_range;
|
||||
}
|
||||
}
|
||||
|
||||
static void GetSinusoidDelays(ALint *restrict delays, ALsizei offset, const ALsizei lfo_range,
|
||||
const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay,
|
||||
const ALsizei todo)
|
||||
{
|
||||
ALsizei i;
|
||||
for(i = 0;i < todo;i++)
|
||||
{
|
||||
delays[i] = fastf2i(sinf(lfo_scale*offset) * depth) + delay;
|
||||
offset = (offset+1)%lfo_range;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ALvoid ALchorusState_process(ALchorusState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
|
||||
{
|
||||
const ALsizei bufmask = state->BufferLength-1;
|
||||
const ALfloat feedback = state->feedback;
|
||||
const ALsizei avgdelay = (state->delay + (FRACTIONONE>>1)) >> FRACTIONBITS;
|
||||
ALfloat *restrict delaybuf = state->SampleBuffer;
|
||||
ALsizei offset = state->offset;
|
||||
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(state->waveform == WF_Sinusoid)
|
||||
{
|
||||
GetSinusoidDelays(moddelays[0], state->lfo_offset, state->lfo_range, state->lfo_scale,
|
||||
state->depth, state->delay, todo);
|
||||
GetSinusoidDelays(moddelays[1], (state->lfo_offset+state->lfo_disp)%state->lfo_range,
|
||||
state->lfo_range, state->lfo_scale, state->depth, state->delay,
|
||||
todo);
|
||||
}
|
||||
else /*if(state->waveform == WF_Triangle)*/
|
||||
{
|
||||
GetTriangleDelays(moddelays[0], state->lfo_offset, state->lfo_range, state->lfo_scale,
|
||||
state->depth, state->delay, todo);
|
||||
GetTriangleDelays(moddelays[1], (state->lfo_offset+state->lfo_disp)%state->lfo_range,
|
||||
state->lfo_range, state->lfo_scale, state->depth, state->delay,
|
||||
todo);
|
||||
}
|
||||
state->lfo_offset = (state->lfo_offset+todo) % state->lfo_range;
|
||||
|
||||
for(i = 0;i < todo;i++)
|
||||
{
|
||||
ALint delay;
|
||||
ALfloat mu;
|
||||
|
||||
// Feed the buffer's input first (necessary for delays < 1).
|
||||
delaybuf[offset&bufmask] = SamplesIn[0][base+i];
|
||||
|
||||
// Tap for the left output.
|
||||
delay = offset - (moddelays[0][i]>>FRACTIONBITS);
|
||||
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], NumChannels, SamplesOut, state->Gains[c].Current,
|
||||
state->Gains[c].Target, SamplesToDo-base, base, todo);
|
||||
|
||||
base += todo;
|
||||
}
|
||||
|
||||
state->offset = offset;
|
||||
}
|
||||
|
||||
|
||||
typedef struct ChorusStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} ChorusStateFactory;
|
||||
|
||||
static ALeffectState *ChorusStateFactory_create(ChorusStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALchorusState *state;
|
||||
|
||||
NEW_OBJ0(state, ALchorusState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(ChorusStateFactory);
|
||||
|
||||
|
||||
EffectStateFactory *ChorusStateFactory_getFactory(void)
|
||||
{
|
||||
static ChorusStateFactory ChorusFactory = { { GET_VTABLE2(ChorusStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &ChorusFactory);
|
||||
}
|
||||
|
||||
|
||||
void ALchorus_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALchorus_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{ ALchorus_setParami(effect, context, param, vals[0]); }
|
||||
void ALchorus_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALchorus_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALchorus_setParamf(effect, context, param, vals[0]); }
|
||||
|
||||
void ALchorus_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
case AL_CHORUS_WAVEFORM:
|
||||
*val = props->Chorus.Waveform;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_PHASE:
|
||||
*val = props->Chorus.Phase;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALchorus_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{ ALchorus_getParami(effect, context, param, vals); }
|
||||
void ALchorus_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
case AL_CHORUS_RATE:
|
||||
*val = props->Chorus.Rate;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_DEPTH:
|
||||
*val = props->Chorus.Depth;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_FEEDBACK:
|
||||
*val = props->Chorus.Feedback;
|
||||
break;
|
||||
|
||||
case AL_CHORUS_DELAY:
|
||||
*val = props->Chorus.Delay;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALchorus_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALchorus_getParamf(effect, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALchorus);
|
||||
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
typedef struct FlangerStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} FlangerStateFactory;
|
||||
|
||||
ALeffectState *FlangerStateFactory_create(FlangerStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALchorusState *state;
|
||||
|
||||
NEW_OBJ0(state, ALchorusState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(FlangerStateFactory);
|
||||
|
||||
EffectStateFactory *FlangerStateFactory_getFactory(void)
|
||||
{
|
||||
static FlangerStateFactory FlangerFactory = { { GET_VTABLE2(FlangerStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &FlangerFactory);
|
||||
}
|
||||
|
||||
|
||||
void ALflanger_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALflanger_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{ ALflanger_setParami(effect, context, param, vals[0]); }
|
||||
void ALflanger_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALflanger_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALflanger_setParamf(effect, context, param, vals[0]); }
|
||||
|
||||
void ALflanger_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
case AL_FLANGER_WAVEFORM:
|
||||
*val = props->Chorus.Waveform;
|
||||
break;
|
||||
|
||||
case AL_FLANGER_PHASE:
|
||||
*val = props->Chorus.Phase;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALflanger_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{ ALflanger_getParami(effect, context, param, vals); }
|
||||
void ALflanger_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
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);
|
||||
}
|
||||
}
|
||||
void ALflanger_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALflanger_getParamf(effect, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALflanger);
|
||||
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* OpenAL 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.
|
||||
*
|
||||
* 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 <stdlib.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "alError.h"
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alu.h"
|
||||
|
||||
|
||||
#define AMP_ENVELOPE_MIN 0.5f
|
||||
#define AMP_ENVELOPE_MAX 2.0f
|
||||
|
||||
#define ATTACK_TIME 0.1f /* 100ms to rise from min to max */
|
||||
#define RELEASE_TIME 0.2f /* 200ms to drop from max to min */
|
||||
|
||||
|
||||
typedef struct ALcompressorState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
|
||||
/* Effect gains for each channel */
|
||||
ALfloat Gain[MAX_EFFECT_CHANNELS][MAX_OUTPUT_CHANNELS];
|
||||
|
||||
/* Effect parameters */
|
||||
ALboolean Enabled;
|
||||
ALfloat AttackMult;
|
||||
ALfloat ReleaseMult;
|
||||
ALfloat EnvFollower;
|
||||
} ALcompressorState;
|
||||
|
||||
static ALvoid ALcompressorState_Destruct(ALcompressorState *state);
|
||||
static ALboolean ALcompressorState_deviceUpdate(ALcompressorState *state, ALCdevice *device);
|
||||
static ALvoid ALcompressorState_update(ALcompressorState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
|
||||
static ALvoid ALcompressorState_process(ALcompressorState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALcompressorState)
|
||||
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALcompressorState);
|
||||
|
||||
|
||||
static void ALcompressorState_Construct(ALcompressorState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALcompressorState, ALeffectState, state);
|
||||
|
||||
state->Enabled = AL_TRUE;
|
||||
state->AttackMult = 1.0f;
|
||||
state->ReleaseMult = 1.0f;
|
||||
state->EnvFollower = 1.0f;
|
||||
}
|
||||
|
||||
static ALvoid ALcompressorState_Destruct(ALcompressorState *state)
|
||||
{
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
static ALboolean ALcompressorState_deviceUpdate(ALcompressorState *state, ALCdevice *device)
|
||||
{
|
||||
/* Number of samples to do a full attack and release (non-integer sample
|
||||
* counts are okay).
|
||||
*/
|
||||
const ALfloat attackCount = (ALfloat)device->Frequency * ATTACK_TIME;
|
||||
const ALfloat releaseCount = (ALfloat)device->Frequency * RELEASE_TIME;
|
||||
|
||||
/* Calculate per-sample multipliers to attack and release at the desired
|
||||
* rates.
|
||||
*/
|
||||
state->AttackMult = powf(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount);
|
||||
state->ReleaseMult = powf(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
static ALvoid ALcompressorState_update(ALcompressorState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
|
||||
{
|
||||
const ALCdevice *device = context->Device;
|
||||
ALuint i;
|
||||
|
||||
state->Enabled = props->Compressor.OnOff;
|
||||
|
||||
STATIC_CAST(ALeffectState,state)->OutBuffer = device->FOAOut.Buffer;
|
||||
STATIC_CAST(ALeffectState,state)->OutChannels = device->FOAOut.NumChannels;
|
||||
for(i = 0;i < 4;i++)
|
||||
ComputePanGains(&device->FOAOut, IdentityMatrixf.m[i], slot->Params.Gain, state->Gain[i]);
|
||||
}
|
||||
|
||||
static ALvoid ALcompressorState_process(ALcompressorState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
|
||||
{
|
||||
ALsizei i, j, k;
|
||||
ALsizei base;
|
||||
|
||||
for(base = 0;base < SamplesToDo;)
|
||||
{
|
||||
ALfloat gains[256];
|
||||
ALsizei td = mini(256, SamplesToDo-base);
|
||||
ALfloat env = state->EnvFollower;
|
||||
|
||||
/* Generate the per-sample gains from the signal envelope. */
|
||||
if(state->Enabled)
|
||||
{
|
||||
for(i = 0;i < td;++i)
|
||||
{
|
||||
/* Clamp the absolute amplitude to the defined envelope limits,
|
||||
* then attack or release the envelope to reach it.
|
||||
*/
|
||||
ALfloat amplitude = clampf(fabsf(SamplesIn[0][base+i]),
|
||||
AMP_ENVELOPE_MIN, AMP_ENVELOPE_MAX);
|
||||
if(amplitude > env)
|
||||
env = minf(env*state->AttackMult, amplitude);
|
||||
else if(amplitude < env)
|
||||
env = maxf(env*state->ReleaseMult, amplitude);
|
||||
|
||||
/* Apply the reciprocal of the envelope to normalize the volume
|
||||
* (compress the dynamic range).
|
||||
*/
|
||||
gains[i] = 1.0f / env;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Same as above, except the amplitude is forced to 1. This helps
|
||||
* ensure smooth gain changes when the compressor is turned on and
|
||||
* off.
|
||||
*/
|
||||
for(i = 0;i < td;++i)
|
||||
{
|
||||
ALfloat amplitude = 1.0f;
|
||||
if(amplitude > env)
|
||||
env = minf(env*state->AttackMult, amplitude);
|
||||
else if(amplitude < env)
|
||||
env = maxf(env*state->ReleaseMult, amplitude);
|
||||
|
||||
gains[i] = 1.0f / env;
|
||||
}
|
||||
}
|
||||
state->EnvFollower = env;
|
||||
|
||||
/* Now compress the signal amplitude to output. */
|
||||
for(j = 0;j < MAX_EFFECT_CHANNELS;j++)
|
||||
{
|
||||
for(k = 0;k < NumChannels;k++)
|
||||
{
|
||||
ALfloat gain = state->Gain[j][k];
|
||||
if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
|
||||
continue;
|
||||
|
||||
for(i = 0;i < td;i++)
|
||||
SamplesOut[k][base+i] += SamplesIn[j][base+i] * gains[i] * gain;
|
||||
}
|
||||
}
|
||||
|
||||
base += td;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef struct CompressorStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} CompressorStateFactory;
|
||||
|
||||
static ALeffectState *CompressorStateFactory_create(CompressorStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALcompressorState *state;
|
||||
|
||||
NEW_OBJ0(state, ALcompressorState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(CompressorStateFactory);
|
||||
|
||||
EffectStateFactory *CompressorStateFactory_getFactory(void)
|
||||
{
|
||||
static CompressorStateFactory CompressorFactory = { { GET_VTABLE2(CompressorStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &CompressorFactory);
|
||||
}
|
||||
|
||||
|
||||
void ALcompressor_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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 ALcompressor_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{ ALcompressor_setParami(effect, context, param, vals[0]); }
|
||||
void ALcompressor_setParamf(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
|
||||
void ALcompressor_setParamfv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALfloat *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
|
||||
|
||||
void ALcompressor_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
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 ALcompressor_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{ ALcompressor_getParami(effect, context, param, vals); }
|
||||
void ALcompressor_getParamf(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat *UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
|
||||
void ALcompressor_getParamfv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALcompressor);
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2011 by Chris Robinson.
|
||||
* 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 <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
#include "alu.h"
|
||||
#include "filters/defs.h"
|
||||
|
||||
|
||||
typedef struct ALdedicatedState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
|
||||
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
|
||||
} ALdedicatedState;
|
||||
|
||||
static ALvoid ALdedicatedState_Destruct(ALdedicatedState *state);
|
||||
static ALboolean ALdedicatedState_deviceUpdate(ALdedicatedState *state, ALCdevice *device);
|
||||
static ALvoid ALdedicatedState_update(ALdedicatedState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
|
||||
static ALvoid ALdedicatedState_process(ALdedicatedState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALdedicatedState)
|
||||
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALdedicatedState);
|
||||
|
||||
|
||||
static void ALdedicatedState_Construct(ALdedicatedState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALdedicatedState, ALeffectState, state);
|
||||
}
|
||||
|
||||
static ALvoid ALdedicatedState_Destruct(ALdedicatedState *state)
|
||||
{
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
static ALboolean ALdedicatedState_deviceUpdate(ALdedicatedState *state, ALCdevice *UNUSED(device))
|
||||
{
|
||||
ALsizei i;
|
||||
for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
|
||||
state->CurrentGains[i] = 0.0f;
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
static ALvoid ALdedicatedState_update(ALdedicatedState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
|
||||
{
|
||||
const ALCdevice *device = context->Device;
|
||||
ALfloat Gain;
|
||||
ALsizei i;
|
||||
|
||||
for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
|
||||
state->TargetGains[i] = 0.0f;
|
||||
|
||||
Gain = slot->Params.Gain * props->Dedicated.Gain;
|
||||
if(slot->Params.EffectType == AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT)
|
||||
{
|
||||
int idx;
|
||||
if((idx=GetChannelIdxByName(&device->RealOut, LFE)) != -1)
|
||||
{
|
||||
STATIC_CAST(ALeffectState,state)->OutBuffer = device->RealOut.Buffer;
|
||||
STATIC_CAST(ALeffectState,state)->OutChannels = device->RealOut.NumChannels;
|
||||
state->TargetGains[idx] = Gain;
|
||||
}
|
||||
}
|
||||
else if(slot->Params.EffectType == AL_EFFECT_DEDICATED_DIALOGUE)
|
||||
{
|
||||
int idx;
|
||||
/* Dialog goes to the front-center speaker if it exists, otherwise it
|
||||
* plays from the front-center location. */
|
||||
if((idx=GetChannelIdxByName(&device->RealOut, FrontCenter)) != -1)
|
||||
{
|
||||
STATIC_CAST(ALeffectState,state)->OutBuffer = device->RealOut.Buffer;
|
||||
STATIC_CAST(ALeffectState,state)->OutChannels = device->RealOut.NumChannels;
|
||||
state->TargetGains[idx] = Gain;
|
||||
}
|
||||
else
|
||||
{
|
||||
ALfloat coeffs[MAX_AMBI_COEFFS];
|
||||
CalcAngleCoeffs(0.0f, 0.0f, 0.0f, coeffs);
|
||||
|
||||
STATIC_CAST(ALeffectState,state)->OutBuffer = device->Dry.Buffer;
|
||||
STATIC_CAST(ALeffectState,state)->OutChannels = device->Dry.NumChannels;
|
||||
ComputePanGains(&device->Dry, coeffs, Gain, state->TargetGains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ALvoid ALdedicatedState_process(ALdedicatedState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
|
||||
{
|
||||
MixSamples(SamplesIn[0], NumChannels, SamplesOut, state->CurrentGains,
|
||||
state->TargetGains, SamplesToDo, 0, SamplesToDo);
|
||||
}
|
||||
|
||||
|
||||
typedef struct DedicatedStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} DedicatedStateFactory;
|
||||
|
||||
ALeffectState *DedicatedStateFactory_create(DedicatedStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALdedicatedState *state;
|
||||
|
||||
NEW_OBJ0(state, ALdedicatedState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(DedicatedStateFactory);
|
||||
|
||||
|
||||
EffectStateFactory *DedicatedStateFactory_getFactory(void)
|
||||
{
|
||||
static DedicatedStateFactory DedicatedFactory = { { GET_VTABLE2(DedicatedStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &DedicatedFactory);
|
||||
}
|
||||
|
||||
|
||||
void ALdedicated_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
|
||||
void ALdedicated_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
|
||||
void ALdedicated_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
case AL_DEDICATED_GAIN:
|
||||
if(!(val >= 0.0f && 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 ALdedicated_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALdedicated_setParamf(effect, context, param, vals[0]); }
|
||||
|
||||
void ALdedicated_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
|
||||
void ALdedicated_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
|
||||
void ALdedicated_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
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 ALdedicated_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALdedicated_getParamf(effect, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALdedicated);
|
||||
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* 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 <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
#include "alu.h"
|
||||
#include "filters/defs.h"
|
||||
|
||||
|
||||
typedef struct ALdistortionState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
|
||||
/* Effect gains for each channel */
|
||||
ALfloat Gain[MAX_OUTPUT_CHANNELS];
|
||||
|
||||
/* Effect parameters */
|
||||
BiquadFilter lowpass;
|
||||
BiquadFilter bandpass;
|
||||
ALfloat attenuation;
|
||||
ALfloat edge_coeff;
|
||||
|
||||
ALfloat Buffer[2][BUFFERSIZE];
|
||||
} ALdistortionState;
|
||||
|
||||
static ALvoid ALdistortionState_Destruct(ALdistortionState *state);
|
||||
static ALboolean ALdistortionState_deviceUpdate(ALdistortionState *state, ALCdevice *device);
|
||||
static ALvoid ALdistortionState_update(ALdistortionState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
|
||||
static ALvoid ALdistortionState_process(ALdistortionState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALdistortionState)
|
||||
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALdistortionState);
|
||||
|
||||
|
||||
static void ALdistortionState_Construct(ALdistortionState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALdistortionState, ALeffectState, state);
|
||||
}
|
||||
|
||||
static ALvoid ALdistortionState_Destruct(ALdistortionState *state)
|
||||
{
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
static ALboolean ALdistortionState_deviceUpdate(ALdistortionState *state, ALCdevice *UNUSED(device))
|
||||
{
|
||||
BiquadFilter_clear(&state->lowpass);
|
||||
BiquadFilter_clear(&state->bandpass);
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
static ALvoid ALdistortionState_update(ALdistortionState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
|
||||
{
|
||||
const ALCdevice *device = context->Device;
|
||||
ALfloat frequency = (ALfloat)device->Frequency;
|
||||
ALfloat coeffs[MAX_AMBI_COEFFS];
|
||||
ALfloat bandwidth;
|
||||
ALfloat cutoff;
|
||||
ALfloat edge;
|
||||
|
||||
/* Store waveshaper edge settings. */
|
||||
edge = sinf(props->Distortion.Edge * (F_PI_2));
|
||||
edge = minf(edge, 0.99f);
|
||||
state->edge_coeff = 2.0f * edge / (1.0f-edge);
|
||||
|
||||
cutoff = props->Distortion.LowpassCutoff;
|
||||
/* Bandwidth value is constant in octaves. */
|
||||
bandwidth = (cutoff / 2.0f) / (cutoff * 0.67f);
|
||||
/* Multiply sampling frequency by the amount of oversampling done during
|
||||
* processing.
|
||||
*/
|
||||
BiquadFilter_setParams(&state->lowpass, BiquadType_LowPass, 1.0f,
|
||||
cutoff / (frequency*4.0f), calc_rcpQ_from_bandwidth(cutoff / (frequency*4.0f), bandwidth)
|
||||
);
|
||||
|
||||
cutoff = props->Distortion.EQCenter;
|
||||
/* Convert bandwidth in Hz to octaves. */
|
||||
bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
|
||||
BiquadFilter_setParams(&state->bandpass, BiquadType_BandPass, 1.0f,
|
||||
cutoff / (frequency*4.0f), calc_rcpQ_from_bandwidth(cutoff / (frequency*4.0f), bandwidth)
|
||||
);
|
||||
|
||||
CalcAngleCoeffs(0.0f, 0.0f, 0.0f, coeffs);
|
||||
ComputePanGains(&device->Dry, coeffs, slot->Params.Gain*props->Distortion.Gain, state->Gain);
|
||||
}
|
||||
|
||||
static ALvoid ALdistortionState_process(ALdistortionState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
|
||||
{
|
||||
ALfloat (*restrict buffer)[BUFFERSIZE] = state->Buffer;
|
||||
const ALfloat fc = state->edge_coeff;
|
||||
ALsizei base;
|
||||
ALsizei i, k;
|
||||
|
||||
for(base = 0;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);
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
BiquadFilter_process(&state->lowpass, buffer[1], buffer[0], todo);
|
||||
|
||||
/* 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++)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* Third step, do bandpass filtering of distorted signal. */
|
||||
BiquadFilter_process(&state->bandpass, buffer[1], buffer[0], todo);
|
||||
|
||||
todo >>= 2;
|
||||
for(k = 0;k < NumChannels;k++)
|
||||
{
|
||||
/* Fourth step, final, do attenuation and perform decimation,
|
||||
* storing only one sample out of four.
|
||||
*/
|
||||
ALfloat gain = state->Gain[k];
|
||||
if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
|
||||
continue;
|
||||
|
||||
for(i = 0;i < todo;i++)
|
||||
SamplesOut[k][base+i] += gain * buffer[1][i*4];
|
||||
}
|
||||
|
||||
base += todo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef struct DistortionStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} DistortionStateFactory;
|
||||
|
||||
static ALeffectState *DistortionStateFactory_create(DistortionStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALdistortionState *state;
|
||||
|
||||
NEW_OBJ0(state, ALdistortionState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(DistortionStateFactory);
|
||||
|
||||
|
||||
EffectStateFactory *DistortionStateFactory_getFactory(void)
|
||||
{
|
||||
static DistortionStateFactory DistortionFactory = { { GET_VTABLE2(DistortionStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &DistortionFactory);
|
||||
}
|
||||
|
||||
|
||||
void ALdistortion_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
|
||||
void ALdistortion_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
|
||||
void ALdistortion_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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 ALdistortion_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALdistortion_setParamf(effect, context, param, vals[0]); }
|
||||
|
||||
void ALdistortion_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
|
||||
void ALdistortion_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
|
||||
void ALdistortion_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
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 ALdistortion_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALdistortion_getParamf(effect, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALdistortion);
|
||||
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2009 by Chris Robinson.
|
||||
* 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 <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alFilter.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
#include "alu.h"
|
||||
#include "filters/defs.h"
|
||||
|
||||
|
||||
typedef struct ALechoState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
|
||||
ALfloat *SampleBuffer;
|
||||
ALsizei BufferLength;
|
||||
|
||||
// The echo is two tap. The delay is the number of samples from before the
|
||||
// current offset
|
||||
struct {
|
||||
ALsizei delay;
|
||||
} Tap[2];
|
||||
ALsizei Offset;
|
||||
|
||||
/* The panning gains for the two taps */
|
||||
struct {
|
||||
ALfloat Current[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat Target[MAX_OUTPUT_CHANNELS];
|
||||
} Gains[2];
|
||||
|
||||
ALfloat FeedGain;
|
||||
|
||||
BiquadFilter Filter;
|
||||
} ALechoState;
|
||||
|
||||
static ALvoid ALechoState_Destruct(ALechoState *state);
|
||||
static ALboolean ALechoState_deviceUpdate(ALechoState *state, ALCdevice *Device);
|
||||
static ALvoid ALechoState_update(ALechoState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
|
||||
static ALvoid ALechoState_process(ALechoState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALechoState)
|
||||
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALechoState);
|
||||
|
||||
|
||||
static void ALechoState_Construct(ALechoState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALechoState, ALeffectState, state);
|
||||
|
||||
state->BufferLength = 0;
|
||||
state->SampleBuffer = NULL;
|
||||
|
||||
state->Tap[0].delay = 0;
|
||||
state->Tap[1].delay = 0;
|
||||
state->Offset = 0;
|
||||
|
||||
BiquadFilter_clear(&state->Filter);
|
||||
}
|
||||
|
||||
static ALvoid ALechoState_Destruct(ALechoState *state)
|
||||
{
|
||||
al_free(state->SampleBuffer);
|
||||
state->SampleBuffer = NULL;
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
static ALboolean ALechoState_deviceUpdate(ALechoState *state, ALCdevice *Device)
|
||||
{
|
||||
ALsizei maxlen;
|
||||
|
||||
// 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;
|
||||
|
||||
if(maxlen != state->BufferLength)
|
||||
{
|
||||
void *temp = al_calloc(16, maxlen * sizeof(ALfloat));
|
||||
if(!temp) return AL_FALSE;
|
||||
|
||||
al_free(state->SampleBuffer);
|
||||
state->SampleBuffer = temp;
|
||||
state->BufferLength = maxlen;
|
||||
}
|
||||
|
||||
memset(state->SampleBuffer, 0, state->BufferLength*sizeof(ALfloat));
|
||||
memset(state->Gains, 0, sizeof(state->Gains));
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
static ALvoid ALechoState_update(ALechoState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
|
||||
{
|
||||
const ALCdevice *device = context->Device;
|
||||
ALuint frequency = device->Frequency;
|
||||
ALfloat coeffs[MAX_AMBI_COEFFS];
|
||||
ALfloat gainhf, lrpan, spread;
|
||||
|
||||
state->Tap[0].delay = maxi(float2int(props->Echo.Delay*frequency + 0.5f), 1);
|
||||
state->Tap[1].delay = float2int(props->Echo.LRDelay*frequency + 0.5f);
|
||||
state->Tap[1].delay += state->Tap[0].delay;
|
||||
|
||||
spread = props->Echo.Spread;
|
||||
if(spread < 0.0f) lrpan = -1.0f;
|
||||
else lrpan = 1.0f;
|
||||
/* Convert echo spread (where 0 = omni, +/-1 = directional) to coverage
|
||||
* spread (where 0 = point, tau = omni).
|
||||
*/
|
||||
spread = asinf(1.0f - fabsf(spread))*4.0f;
|
||||
|
||||
state->FeedGain = props->Echo.Feedback;
|
||||
|
||||
gainhf = maxf(1.0f - props->Echo.Damping, 0.0625f); /* Limit -24dB */
|
||||
BiquadFilter_setParams(&state->Filter, BiquadType_HighShelf,
|
||||
gainhf, LOWPASSFREQREF/frequency, calc_rcpQ_from_slope(gainhf, 1.0f)
|
||||
);
|
||||
|
||||
/* First tap panning */
|
||||
CalcAngleCoeffs(-F_PI_2*lrpan, 0.0f, spread, coeffs);
|
||||
ComputePanGains(&device->Dry, coeffs, slot->Params.Gain, state->Gains[0].Target);
|
||||
|
||||
/* Second tap panning */
|
||||
CalcAngleCoeffs( F_PI_2*lrpan, 0.0f, spread, coeffs);
|
||||
ComputePanGains(&device->Dry, coeffs, slot->Params.Gain, state->Gains[1].Target);
|
||||
}
|
||||
|
||||
static ALvoid ALechoState_process(ALechoState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
|
||||
{
|
||||
const ALsizei mask = state->BufferLength-1;
|
||||
const ALsizei tap1 = state->Tap[0].delay;
|
||||
const ALsizei tap2 = state->Tap[1].delay;
|
||||
ALfloat *restrict delaybuf = state->SampleBuffer;
|
||||
ALsizei offset = state->Offset;
|
||||
ALfloat z1, z2, in, out;
|
||||
ALsizei base;
|
||||
ALsizei c, i;
|
||||
|
||||
z1 = state->Filter.z1;
|
||||
z2 = state->Filter.z2;
|
||||
for(base = 0;base < SamplesToDo;)
|
||||
{
|
||||
alignas(16) ALfloat temps[2][128];
|
||||
ALsizei td = mini(128, SamplesToDo-base);
|
||||
|
||||
for(i = 0;i < td;i++)
|
||||
{
|
||||
/* Feed the delay buffer's input first. */
|
||||
delaybuf[offset&mask] = SamplesIn[0][i+base];
|
||||
|
||||
/* First tap */
|
||||
temps[0][i] = delaybuf[(offset-tap1) & mask];
|
||||
/* Second tap */
|
||||
temps[1][i] = delaybuf[(offset-tap2) & mask];
|
||||
|
||||
/* Apply damping to the second tap, then add it to the buffer with
|
||||
* feedback attenuation.
|
||||
*/
|
||||
in = temps[1][i];
|
||||
out = in*state->Filter.b0 + z1;
|
||||
z1 = in*state->Filter.b1 - out*state->Filter.a1 + z2;
|
||||
z2 = in*state->Filter.b2 - out*state->Filter.a2;
|
||||
|
||||
delaybuf[offset&mask] += out * state->FeedGain;
|
||||
offset++;
|
||||
}
|
||||
|
||||
for(c = 0;c < 2;c++)
|
||||
MixSamples(temps[c], NumChannels, SamplesOut, state->Gains[c].Current,
|
||||
state->Gains[c].Target, SamplesToDo-base, base, td);
|
||||
|
||||
base += td;
|
||||
}
|
||||
state->Filter.z1 = z1;
|
||||
state->Filter.z2 = z2;
|
||||
|
||||
state->Offset = offset;
|
||||
}
|
||||
|
||||
|
||||
typedef struct EchoStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} EchoStateFactory;
|
||||
|
||||
ALeffectState *EchoStateFactory_create(EchoStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALechoState *state;
|
||||
|
||||
NEW_OBJ0(state, ALechoState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(EchoStateFactory);
|
||||
|
||||
EffectStateFactory *EchoStateFactory_getFactory(void)
|
||||
{
|
||||
static EchoStateFactory EchoFactory = { { GET_VTABLE2(EchoStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &EchoFactory);
|
||||
}
|
||||
|
||||
|
||||
void ALecho_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
|
||||
void ALecho_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
|
||||
void ALecho_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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 ALecho_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALecho_setParamf(effect, context, param, vals[0]); }
|
||||
|
||||
void ALecho_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
|
||||
void ALecho_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
|
||||
void ALecho_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
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 ALecho_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALecho_getParamf(effect, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALecho);
|
||||
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* 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 <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
#include "alu.h"
|
||||
#include "filters/defs.h"
|
||||
|
||||
|
||||
/* The document "Effects Extension Guide.pdf" says that low and high *
|
||||
* frequencies are cutoff frequencies. This is not fully correct, they *
|
||||
* are corner frequencies for low and high shelf filters. If they were *
|
||||
* just cutoff frequencies, there would be no need in cutoff frequency *
|
||||
* gains, which are present. Documentation for "Creative Proteus X2" *
|
||||
* software describes 4-band equalizer functionality in a much better *
|
||||
* way. This equalizer seems to be a predecessor of OpenAL 4-band *
|
||||
* equalizer. With low and high shelf filters we are able to cutoff *
|
||||
* frequencies below and/or above corner frequencies using attenuation *
|
||||
* gains (below 1.0) and amplify all low and/or high frequencies using *
|
||||
* gains above 1.0. *
|
||||
* *
|
||||
* Low-shelf Low Mid Band High Mid Band High-shelf *
|
||||
* corner center center corner *
|
||||
* frequency frequency frequency frequency *
|
||||
* 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz *
|
||||
* *
|
||||
* | | | | *
|
||||
* | | | | *
|
||||
* B -----+ /--+--\ /--+--\ +----- *
|
||||
* O |\ | | | | | | /| *
|
||||
* O | \ - | - - | - / | *
|
||||
* S + | \ | | | | | | / | *
|
||||
* T | | | | | | | | | | *
|
||||
* ---------+---------------+------------------+---------------+-------- *
|
||||
* C | | | | | | | | | | *
|
||||
* U - | / | | | | | | \ | *
|
||||
* T | / - | - - | - \ | *
|
||||
* O |/ | | | | | | \| *
|
||||
* F -----+ \--+--/ \--+--/ +----- *
|
||||
* F | | | | *
|
||||
* | | | | *
|
||||
* *
|
||||
* Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
|
||||
* up to +18dB amplification. Band width varies from 0.01 up to 1.0 in *
|
||||
* octaves for two mid bands. *
|
||||
* *
|
||||
* 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 */
|
||||
|
||||
|
||||
typedef struct ALequalizerState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
|
||||
struct {
|
||||
/* Effect parameters */
|
||||
BiquadFilter filter[4];
|
||||
|
||||
/* Effect gains for each channel */
|
||||
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
|
||||
} Chans[MAX_EFFECT_CHANNELS];
|
||||
|
||||
ALfloat SampleBuffer[MAX_EFFECT_CHANNELS][BUFFERSIZE];
|
||||
} ALequalizerState;
|
||||
|
||||
static ALvoid ALequalizerState_Destruct(ALequalizerState *state);
|
||||
static ALboolean ALequalizerState_deviceUpdate(ALequalizerState *state, ALCdevice *device);
|
||||
static ALvoid ALequalizerState_update(ALequalizerState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
|
||||
static ALvoid ALequalizerState_process(ALequalizerState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALequalizerState)
|
||||
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALequalizerState);
|
||||
|
||||
|
||||
static void ALequalizerState_Construct(ALequalizerState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALequalizerState, ALeffectState, state);
|
||||
}
|
||||
|
||||
static ALvoid ALequalizerState_Destruct(ALequalizerState *state)
|
||||
{
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
static ALboolean ALequalizerState_deviceUpdate(ALequalizerState *state, ALCdevice *UNUSED(device))
|
||||
{
|
||||
ALsizei i, j;
|
||||
|
||||
for(i = 0; i < MAX_EFFECT_CHANNELS;i++)
|
||||
{
|
||||
for(j = 0;j < 4;j++)
|
||||
BiquadFilter_clear(&state->Chans[i].filter[j]);
|
||||
for(j = 0;j < MAX_OUTPUT_CHANNELS;j++)
|
||||
state->Chans[i].CurrentGains[j] = 0.0f;
|
||||
}
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
static ALvoid ALequalizerState_update(ALequalizerState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
|
||||
{
|
||||
const ALCdevice *device = context->Device;
|
||||
ALfloat frequency = (ALfloat)device->Frequency;
|
||||
ALfloat gain, f0norm;
|
||||
ALuint i;
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
gain = maxf(sqrtf(props->Equalizer.LowGain), 0.0625f); /* Limit -24dB */
|
||||
f0norm = props->Equalizer.LowCutoff/frequency;
|
||||
BiquadFilter_setParams(&state->Chans[0].filter[0], BiquadType_LowShelf,
|
||||
gain, f0norm, calc_rcpQ_from_slope(gain, 0.75f)
|
||||
);
|
||||
|
||||
gain = maxf(props->Equalizer.Mid1Gain, 0.0625f);
|
||||
f0norm = props->Equalizer.Mid1Center/frequency;
|
||||
BiquadFilter_setParams(&state->Chans[0].filter[1], BiquadType_Peaking,
|
||||
gain, f0norm, calc_rcpQ_from_bandwidth(
|
||||
f0norm, props->Equalizer.Mid1Width
|
||||
)
|
||||
);
|
||||
|
||||
gain = maxf(props->Equalizer.Mid2Gain, 0.0625f);
|
||||
f0norm = props->Equalizer.Mid2Center/frequency;
|
||||
BiquadFilter_setParams(&state->Chans[0].filter[2], BiquadType_Peaking,
|
||||
gain, f0norm, calc_rcpQ_from_bandwidth(
|
||||
f0norm, props->Equalizer.Mid2Width
|
||||
)
|
||||
);
|
||||
|
||||
gain = maxf(sqrtf(props->Equalizer.HighGain), 0.0625f);
|
||||
f0norm = props->Equalizer.HighCutoff/frequency;
|
||||
BiquadFilter_setParams(&state->Chans[0].filter[3], BiquadType_HighShelf,
|
||||
gain, f0norm, calc_rcpQ_from_slope(gain, 0.75f)
|
||||
);
|
||||
|
||||
/* Copy the filter coefficients for the other input channels. */
|
||||
for(i = 1;i < MAX_EFFECT_CHANNELS;i++)
|
||||
{
|
||||
BiquadFilter_copyParams(&state->Chans[i].filter[0], &state->Chans[0].filter[0]);
|
||||
BiquadFilter_copyParams(&state->Chans[i].filter[1], &state->Chans[0].filter[1]);
|
||||
BiquadFilter_copyParams(&state->Chans[i].filter[2], &state->Chans[0].filter[2]);
|
||||
BiquadFilter_copyParams(&state->Chans[i].filter[3], &state->Chans[0].filter[3]);
|
||||
}
|
||||
|
||||
STATIC_CAST(ALeffectState,state)->OutBuffer = device->FOAOut.Buffer;
|
||||
STATIC_CAST(ALeffectState,state)->OutChannels = device->FOAOut.NumChannels;
|
||||
for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
|
||||
ComputePanGains(&device->FOAOut, IdentityMatrixf.m[i], slot->Params.Gain,
|
||||
state->Chans[i].TargetGains);
|
||||
}
|
||||
|
||||
static ALvoid ALequalizerState_process(ALequalizerState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
|
||||
{
|
||||
ALfloat (*restrict temps)[BUFFERSIZE] = state->SampleBuffer;
|
||||
ALsizei c;
|
||||
|
||||
for(c = 0;c < MAX_EFFECT_CHANNELS;c++)
|
||||
{
|
||||
BiquadFilter_process(&state->Chans[c].filter[0], temps[0], SamplesIn[c], SamplesToDo);
|
||||
BiquadFilter_process(&state->Chans[c].filter[1], temps[1], temps[0], SamplesToDo);
|
||||
BiquadFilter_process(&state->Chans[c].filter[2], temps[2], temps[1], SamplesToDo);
|
||||
BiquadFilter_process(&state->Chans[c].filter[3], temps[3], temps[2], SamplesToDo);
|
||||
|
||||
MixSamples(temps[3], NumChannels, SamplesOut,
|
||||
state->Chans[c].CurrentGains, state->Chans[c].TargetGains,
|
||||
SamplesToDo, 0, SamplesToDo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef struct EqualizerStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} EqualizerStateFactory;
|
||||
|
||||
ALeffectState *EqualizerStateFactory_create(EqualizerStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALequalizerState *state;
|
||||
|
||||
NEW_OBJ0(state, ALequalizerState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(EqualizerStateFactory);
|
||||
|
||||
EffectStateFactory *EqualizerStateFactory_getFactory(void)
|
||||
{
|
||||
static EqualizerStateFactory EqualizerFactory = { { GET_VTABLE2(EqualizerStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &EqualizerFactory);
|
||||
}
|
||||
|
||||
|
||||
void ALequalizer_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
|
||||
void ALequalizer_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
|
||||
void ALequalizer_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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 ALequalizer_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALequalizer_setParamf(effect, context, param, vals[0]); }
|
||||
|
||||
void ALequalizer_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
|
||||
void ALequalizer_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
|
||||
void ALequalizer_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
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 ALequalizer_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALequalizer_getParamf(effect, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALequalizer);
|
||||
@@ -0,0 +1,329 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by Raul Herraiz.
|
||||
* 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 <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
#include "alu.h"
|
||||
#include "filters/defs.h"
|
||||
|
||||
#include "alcomplex.h"
|
||||
|
||||
#define HIL_SIZE 1024
|
||||
#define OVERSAMP (1<<2)
|
||||
|
||||
#define HIL_STEP (HIL_SIZE / OVERSAMP)
|
||||
#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1))
|
||||
|
||||
|
||||
typedef struct ALfshifterState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
|
||||
/* Effect parameters */
|
||||
ALsizei count;
|
||||
ALsizei PhaseStep;
|
||||
ALsizei Phase;
|
||||
ALdouble ld_sign;
|
||||
|
||||
/*Effects buffers*/
|
||||
ALfloat InFIFO[HIL_SIZE];
|
||||
ALcomplex OutFIFO[HIL_SIZE];
|
||||
ALcomplex OutputAccum[HIL_SIZE];
|
||||
ALcomplex Analytic[HIL_SIZE];
|
||||
ALcomplex Outdata[BUFFERSIZE];
|
||||
|
||||
alignas(16) ALfloat BufferOut[BUFFERSIZE];
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
|
||||
} ALfshifterState;
|
||||
|
||||
static ALvoid ALfshifterState_Destruct(ALfshifterState *state);
|
||||
static ALboolean ALfshifterState_deviceUpdate(ALfshifterState *state, ALCdevice *device);
|
||||
static ALvoid ALfshifterState_update(ALfshifterState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
|
||||
static ALvoid ALfshifterState_process(ALfshifterState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALfshifterState)
|
||||
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALfshifterState);
|
||||
|
||||
/* Define a Hann window, used to filter the HIL input and output. */
|
||||
alignas(16) static ALdouble HannWindow[HIL_SIZE];
|
||||
|
||||
static void InitHannWindow(void)
|
||||
{
|
||||
ALsizei i;
|
||||
|
||||
/* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
|
||||
for(i = 0;i < HIL_SIZE>>1;i++)
|
||||
{
|
||||
ALdouble val = sin(M_PI * (ALdouble)i / (ALdouble)(HIL_SIZE-1));
|
||||
HannWindow[i] = HannWindow[HIL_SIZE-1-i] = val * val;
|
||||
}
|
||||
}
|
||||
|
||||
static alonce_flag HannInitOnce = AL_ONCE_FLAG_INIT;
|
||||
|
||||
static void ALfshifterState_Construct(ALfshifterState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALfshifterState, ALeffectState, state);
|
||||
|
||||
alcall_once(&HannInitOnce, InitHannWindow);
|
||||
}
|
||||
|
||||
static ALvoid ALfshifterState_Destruct(ALfshifterState *state)
|
||||
{
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
static ALboolean ALfshifterState_deviceUpdate(ALfshifterState *state, ALCdevice *UNUSED(device))
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
state->count = FIFO_LATENCY;
|
||||
state->PhaseStep = 0;
|
||||
state->Phase = 0;
|
||||
state->ld_sign = 1.0;
|
||||
|
||||
memset(state->InFIFO, 0, sizeof(state->InFIFO));
|
||||
memset(state->OutFIFO, 0, sizeof(state->OutFIFO));
|
||||
memset(state->OutputAccum, 0, sizeof(state->OutputAccum));
|
||||
memset(state->Analytic, 0, sizeof(state->Analytic));
|
||||
|
||||
memset(state->CurrentGains, 0, sizeof(state->CurrentGains));
|
||||
memset(state->TargetGains, 0, sizeof(state->TargetGains));
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
static ALvoid ALfshifterState_update(ALfshifterState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
|
||||
{
|
||||
const ALCdevice *device = context->Device;
|
||||
ALfloat coeffs[MAX_AMBI_COEFFS];
|
||||
ALfloat step;
|
||||
|
||||
step = props->Fshifter.Frequency / (ALfloat)device->Frequency;
|
||||
state->PhaseStep = fastf2i(minf(step, 0.5f) * FRACTIONONE);
|
||||
|
||||
switch(props->Fshifter.LeftDirection)
|
||||
{
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN:
|
||||
state->ld_sign = -1.0;
|
||||
break;
|
||||
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_UP:
|
||||
state->ld_sign = 1.0;
|
||||
break;
|
||||
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_OFF:
|
||||
state->Phase = 0;
|
||||
state->PhaseStep = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
CalcAngleCoeffs(0.0f, 0.0f, 0.0f, coeffs);
|
||||
ComputePanGains(&device->Dry, coeffs, slot->Params.Gain, state->TargetGains);
|
||||
}
|
||||
|
||||
static ALvoid ALfshifterState_process(ALfshifterState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
|
||||
{
|
||||
static const ALcomplex complex_zero = { 0.0, 0.0 };
|
||||
ALfloat *restrict BufferOut = state->BufferOut;
|
||||
ALsizei j, k, base;
|
||||
|
||||
for(base = 0;base < SamplesToDo;)
|
||||
{
|
||||
ALsizei todo = mini(HIL_SIZE-state->count, SamplesToDo-base);
|
||||
|
||||
ASSUME(todo > 0);
|
||||
|
||||
/* Fill FIFO buffer with samples data */
|
||||
k = state->count;
|
||||
for(j = 0;j < todo;j++,k++)
|
||||
{
|
||||
state->InFIFO[k] = SamplesIn[0][base+j];
|
||||
state->Outdata[base+j] = state->OutFIFO[k-FIFO_LATENCY];
|
||||
}
|
||||
state->count += todo;
|
||||
base += todo;
|
||||
|
||||
/* Check whether FIFO buffer is filled */
|
||||
if(state->count < HIL_SIZE) continue;
|
||||
|
||||
state->count = FIFO_LATENCY;
|
||||
|
||||
/* Real signal windowing and store in Analytic buffer */
|
||||
for(k = 0;k < HIL_SIZE;k++)
|
||||
{
|
||||
state->Analytic[k].Real = state->InFIFO[k] * HannWindow[k];
|
||||
state->Analytic[k].Imag = 0.0;
|
||||
}
|
||||
|
||||
/* Processing signal by Discrete Hilbert Transform (analytical signal). */
|
||||
complex_hilbert(state->Analytic, HIL_SIZE);
|
||||
|
||||
/* Windowing and add to output accumulator */
|
||||
for(k = 0;k < HIL_SIZE;k++)
|
||||
{
|
||||
state->OutputAccum[k].Real += 2.0/OVERSAMP*HannWindow[k]*state->Analytic[k].Real;
|
||||
state->OutputAccum[k].Imag += 2.0/OVERSAMP*HannWindow[k]*state->Analytic[k].Imag;
|
||||
}
|
||||
|
||||
/* Shift accumulator, input & output FIFO */
|
||||
for(k = 0;k < HIL_STEP;k++) state->OutFIFO[k] = state->OutputAccum[k];
|
||||
for(j = 0;k < HIL_SIZE;k++,j++) state->OutputAccum[j] = state->OutputAccum[k];
|
||||
for(;j < HIL_SIZE;j++) state->OutputAccum[j] = complex_zero;
|
||||
for(k = 0;k < FIFO_LATENCY;k++)
|
||||
state->InFIFO[k] = state->InFIFO[k+HIL_STEP];
|
||||
}
|
||||
|
||||
/* Process frequency shifter using the analytic signal obtained. */
|
||||
for(k = 0;k < SamplesToDo;k++)
|
||||
{
|
||||
ALdouble phase = state->Phase * ((1.0/FRACTIONONE) * 2.0*M_PI);
|
||||
BufferOut[k] = (ALfloat)(state->Outdata[k].Real*cos(phase) +
|
||||
state->Outdata[k].Imag*sin(phase)*state->ld_sign);
|
||||
|
||||
state->Phase += state->PhaseStep;
|
||||
state->Phase &= FRACTIONMASK;
|
||||
}
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples(BufferOut, NumChannels, SamplesOut, state->CurrentGains, state->TargetGains,
|
||||
maxi(SamplesToDo, 512), 0, SamplesToDo);
|
||||
}
|
||||
|
||||
typedef struct FshifterStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} FshifterStateFactory;
|
||||
|
||||
static ALeffectState *FshifterStateFactory_create(FshifterStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALfshifterState *state;
|
||||
|
||||
NEW_OBJ0(state, ALfshifterState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(FshifterStateFactory);
|
||||
|
||||
EffectStateFactory *FshifterStateFactory_getFactory(void)
|
||||
{
|
||||
static FshifterStateFactory FshifterFactory = { { GET_VTABLE2(FshifterStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &FshifterFactory);
|
||||
}
|
||||
|
||||
void ALfshifter_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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 ALfshifter_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{
|
||||
ALfshifter_setParamf(effect, context, param, vals[0]);
|
||||
}
|
||||
|
||||
void ALfshifter_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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 ALfshifter_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{
|
||||
ALfshifter_setParami(effect, context, param, vals[0]);
|
||||
}
|
||||
|
||||
void ALfshifter_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
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 ALfshifter_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{
|
||||
ALfshifter_getParami(effect, context, param, vals);
|
||||
}
|
||||
|
||||
void ALfshifter_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
case AL_FREQUENCY_SHIFTER_FREQUENCY:
|
||||
*val = props->Fshifter.Frequency;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", param);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ALfshifter_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{
|
||||
ALfshifter_getParamf(effect, context, param, vals);
|
||||
}
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALfshifter);
|
||||
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2009 by Chris Robinson.
|
||||
* 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 <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
#include "alu.h"
|
||||
#include "filters/defs.h"
|
||||
|
||||
|
||||
#define MAX_UPDATE_SAMPLES 128
|
||||
|
||||
typedef struct ALmodulatorState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
|
||||
void (*GetSamples)(ALfloat*, ALsizei, const ALsizei, ALsizei);
|
||||
|
||||
ALsizei index;
|
||||
ALsizei step;
|
||||
|
||||
struct {
|
||||
BiquadFilter Filter;
|
||||
|
||||
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
|
||||
} Chans[MAX_EFFECT_CHANNELS];
|
||||
} ALmodulatorState;
|
||||
|
||||
static ALvoid ALmodulatorState_Destruct(ALmodulatorState *state);
|
||||
static ALboolean ALmodulatorState_deviceUpdate(ALmodulatorState *state, ALCdevice *device);
|
||||
static ALvoid ALmodulatorState_update(ALmodulatorState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
|
||||
static ALvoid ALmodulatorState_process(ALmodulatorState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALmodulatorState)
|
||||
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALmodulatorState);
|
||||
|
||||
|
||||
#define WAVEFORM_FRACBITS 24
|
||||
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
|
||||
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
|
||||
|
||||
static inline ALfloat Sin(ALsizei index)
|
||||
{
|
||||
return sinf((ALfloat)index * (F_TAU / WAVEFORM_FRACONE));
|
||||
}
|
||||
|
||||
static inline ALfloat Saw(ALsizei index)
|
||||
{
|
||||
return (ALfloat)index*(2.0f/WAVEFORM_FRACONE) - 1.0f;
|
||||
}
|
||||
|
||||
static inline ALfloat Square(ALsizei index)
|
||||
{
|
||||
return (ALfloat)(((index>>(WAVEFORM_FRACBITS-2))&2) - 1);
|
||||
}
|
||||
|
||||
static inline ALfloat One(ALsizei UNUSED(index))
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
#define DECL_TEMPLATE(func) \
|
||||
static void Modulate##func(ALfloat *restrict dst, ALsizei index, \
|
||||
const ALsizei step, ALsizei todo) \
|
||||
{ \
|
||||
ALsizei i; \
|
||||
for(i = 0;i < todo;i++) \
|
||||
{ \
|
||||
index += step; \
|
||||
index &= WAVEFORM_FRACMASK; \
|
||||
dst[i] = func(index); \
|
||||
} \
|
||||
}
|
||||
|
||||
DECL_TEMPLATE(Sin)
|
||||
DECL_TEMPLATE(Saw)
|
||||
DECL_TEMPLATE(Square)
|
||||
DECL_TEMPLATE(One)
|
||||
|
||||
#undef DECL_TEMPLATE
|
||||
|
||||
|
||||
static void ALmodulatorState_Construct(ALmodulatorState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALmodulatorState, ALeffectState, state);
|
||||
|
||||
state->index = 0;
|
||||
state->step = 1;
|
||||
}
|
||||
|
||||
static ALvoid ALmodulatorState_Destruct(ALmodulatorState *state)
|
||||
{
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
static ALboolean ALmodulatorState_deviceUpdate(ALmodulatorState *state, ALCdevice *UNUSED(device))
|
||||
{
|
||||
ALsizei i, j;
|
||||
for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
|
||||
{
|
||||
BiquadFilter_clear(&state->Chans[i].Filter);
|
||||
for(j = 0;j < MAX_OUTPUT_CHANNELS;j++)
|
||||
state->Chans[i].CurrentGains[j] = 0.0f;
|
||||
}
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
static ALvoid ALmodulatorState_update(ALmodulatorState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
|
||||
{
|
||||
const ALCdevice *device = context->Device;
|
||||
ALfloat f0norm;
|
||||
ALsizei i;
|
||||
|
||||
state->step = fastf2i(props->Modulator.Frequency / (ALfloat)device->Frequency *
|
||||
WAVEFORM_FRACONE);
|
||||
state->step = clampi(state->step, 0, WAVEFORM_FRACONE-1);
|
||||
|
||||
if(state->step == 0)
|
||||
state->GetSamples = ModulateOne;
|
||||
else if(props->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID)
|
||||
state->GetSamples = ModulateSin;
|
||||
else if(props->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH)
|
||||
state->GetSamples = ModulateSaw;
|
||||
else /*if(Slot->Params.EffectProps.Modulator.Waveform == AL_RING_MODULATOR_SQUARE)*/
|
||||
state->GetSamples = ModulateSquare;
|
||||
|
||||
f0norm = props->Modulator.HighPassCutoff / (ALfloat)device->Frequency;
|
||||
f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
|
||||
/* Bandwidth value is constant in octaves. */
|
||||
BiquadFilter_setParams(&state->Chans[0].Filter, BiquadType_HighPass, 1.0f,
|
||||
f0norm, calc_rcpQ_from_bandwidth(f0norm, 0.75f));
|
||||
for(i = 1;i < MAX_EFFECT_CHANNELS;i++)
|
||||
BiquadFilter_copyParams(&state->Chans[i].Filter, &state->Chans[0].Filter);
|
||||
|
||||
STATIC_CAST(ALeffectState,state)->OutBuffer = device->FOAOut.Buffer;
|
||||
STATIC_CAST(ALeffectState,state)->OutChannels = device->FOAOut.NumChannels;
|
||||
for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
|
||||
ComputePanGains(&device->FOAOut, IdentityMatrixf.m[i], slot->Params.Gain,
|
||||
state->Chans[i].TargetGains);
|
||||
}
|
||||
|
||||
static ALvoid ALmodulatorState_process(ALmodulatorState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
|
||||
{
|
||||
const ALsizei step = state->step;
|
||||
ALsizei base;
|
||||
|
||||
for(base = 0;base < SamplesToDo;)
|
||||
{
|
||||
alignas(16) ALfloat modsamples[MAX_UPDATE_SAMPLES];
|
||||
ALsizei td = mini(MAX_UPDATE_SAMPLES, SamplesToDo-base);
|
||||
ALsizei c, i;
|
||||
|
||||
state->GetSamples(modsamples, state->index, step, td);
|
||||
state->index += (step*td) & WAVEFORM_FRACMASK;
|
||||
state->index &= WAVEFORM_FRACMASK;
|
||||
|
||||
for(c = 0;c < MAX_EFFECT_CHANNELS;c++)
|
||||
{
|
||||
alignas(16) ALfloat temps[MAX_UPDATE_SAMPLES];
|
||||
|
||||
BiquadFilter_process(&state->Chans[c].Filter, temps, &SamplesIn[c][base], td);
|
||||
for(i = 0;i < td;i++)
|
||||
temps[i] *= modsamples[i];
|
||||
|
||||
MixSamples(temps, NumChannels, SamplesOut, state->Chans[c].CurrentGains,
|
||||
state->Chans[c].TargetGains, SamplesToDo-base, base, td);
|
||||
}
|
||||
|
||||
base += td;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef struct ModulatorStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} ModulatorStateFactory;
|
||||
|
||||
static ALeffectState *ModulatorStateFactory_create(ModulatorStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALmodulatorState *state;
|
||||
|
||||
NEW_OBJ0(state, ALmodulatorState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(ModulatorStateFactory);
|
||||
|
||||
EffectStateFactory *ModulatorStateFactory_getFactory(void)
|
||||
{
|
||||
static ModulatorStateFactory ModulatorFactory = { { GET_VTABLE2(ModulatorStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &ModulatorFactory);
|
||||
}
|
||||
|
||||
|
||||
void ALmodulator_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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 ALmodulator_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALmodulator_setParamf(effect, context, param, vals[0]); }
|
||||
void ALmodulator_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
case AL_RING_MODULATOR_FREQUENCY:
|
||||
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
|
||||
ALmodulator_setParamf(effect, context, param, (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 ALmodulator_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{ ALmodulator_setParami(effect, context, param, vals[0]); }
|
||||
|
||||
void ALmodulator_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
case AL_RING_MODULATOR_FREQUENCY:
|
||||
*val = (ALint)props->Modulator.Frequency;
|
||||
break;
|
||||
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
|
||||
*val = (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 ALmodulator_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{ ALmodulator_getParami(effect, context, param, vals); }
|
||||
void ALmodulator_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
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 ALmodulator_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALmodulator_getParamf(effect, context, param, vals); }
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALmodulator);
|
||||
@@ -0,0 +1,179 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
|
||||
|
||||
typedef struct ALnullState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
} ALnullState;
|
||||
|
||||
/* Forward-declare "virtual" functions to define the vtable with. */
|
||||
static ALvoid ALnullState_Destruct(ALnullState *state);
|
||||
static ALboolean ALnullState_deviceUpdate(ALnullState *state, ALCdevice *device);
|
||||
static ALvoid ALnullState_update(ALnullState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
|
||||
static ALvoid ALnullState_process(ALnullState *state, ALsizei samplesToDo, const ALfloat (*restrict samplesIn)[BUFFERSIZE], ALfloat (*restrict samplesOut)[BUFFERSIZE], ALsizei mumChannels);
|
||||
static void *ALnullState_New(size_t size);
|
||||
static void ALnullState_Delete(void *ptr);
|
||||
|
||||
/* Define the ALeffectState vtable for this type. */
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALnullState);
|
||||
|
||||
|
||||
/* This constructs the effect state. It's called when the object is first
|
||||
* created. Make sure to call the parent Construct function first, and set the
|
||||
* vtable!
|
||||
*/
|
||||
static void ALnullState_Construct(ALnullState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALnullState, ALeffectState, state);
|
||||
}
|
||||
|
||||
/* This destructs (not free!) the effect state. It's called only when the
|
||||
* effect slot is no longer used. Make sure to call the parent Destruct
|
||||
* function before returning!
|
||||
*/
|
||||
static ALvoid ALnullState_Destruct(ALnullState *state)
|
||||
{
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
/* This updates the device-dependant effect state. This is called on
|
||||
* initialization and any time the device parameters (eg. playback frequency,
|
||||
* format) have been changed.
|
||||
*/
|
||||
static ALboolean ALnullState_deviceUpdate(ALnullState* UNUSED(state), ALCdevice* UNUSED(device))
|
||||
{
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
/* This updates the effect state. This is called any time the effect is
|
||||
* (re)loaded into a slot.
|
||||
*/
|
||||
static ALvoid ALnullState_update(ALnullState* UNUSED(state), const ALCcontext* UNUSED(context), const ALeffectslot* UNUSED(slot), const ALeffectProps* UNUSED(props))
|
||||
{
|
||||
}
|
||||
|
||||
/* This processes the effect state, for the given number of samples from the
|
||||
* input to the output buffer. The result should be added to the output buffer,
|
||||
* not replace it.
|
||||
*/
|
||||
static ALvoid ALnullState_process(ALnullState* UNUSED(state), ALsizei UNUSED(samplesToDo), const ALfloatBUFFERSIZE*restrict UNUSED(samplesIn), ALfloatBUFFERSIZE*restrict UNUSED(samplesOut), ALsizei UNUSED(numChannels))
|
||||
{
|
||||
}
|
||||
|
||||
/* This allocates memory to store the object, before it gets constructed.
|
||||
* DECLARE_DEFAULT_ALLOCATORS can be used to declare a default method.
|
||||
*/
|
||||
static void *ALnullState_New(size_t size)
|
||||
{
|
||||
return al_malloc(16, size);
|
||||
}
|
||||
|
||||
/* This frees the memory used by the object, after it has been destructed.
|
||||
* DECLARE_DEFAULT_ALLOCATORS can be used to declare a default method.
|
||||
*/
|
||||
static void ALnullState_Delete(void *ptr)
|
||||
{
|
||||
al_free(ptr);
|
||||
}
|
||||
|
||||
|
||||
typedef struct NullStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} NullStateFactory;
|
||||
|
||||
/* Creates ALeffectState objects of the appropriate type. */
|
||||
ALeffectState *NullStateFactory_create(NullStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALnullState *state;
|
||||
|
||||
NEW_OBJ0(state, ALnullState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
/* Define the EffectStateFactory vtable for this type. */
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(NullStateFactory);
|
||||
|
||||
EffectStateFactory *NullStateFactory_getFactory(void)
|
||||
{
|
||||
static NullStateFactory NullFactory = { { GET_VTABLE2(NullStateFactory, EffectStateFactory) } };
|
||||
return STATIC_CAST(EffectStateFactory, &NullFactory);
|
||||
}
|
||||
|
||||
|
||||
void ALnull_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALnull_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint* UNUSED(vals))
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer-vector property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALnull_setParamf(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat UNUSED(val))
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALnull_setParamfv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALfloat* UNUSED(vals))
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid null effect float-vector property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
|
||||
void ALnull_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint* UNUSED(val))
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALnull_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint* UNUSED(vals))
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer-vector property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALnull_getParamf(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat* UNUSED(val))
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALnull_getParamfv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat* UNUSED(vals))
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid null effect float-vector property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALnull);
|
||||
@@ -0,0 +1,441 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by Raul Herraiz.
|
||||
* 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 <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
#include "alu.h"
|
||||
#include "filters/defs.h"
|
||||
|
||||
#include "alcomplex.h"
|
||||
|
||||
|
||||
#define STFT_SIZE 1024
|
||||
#define STFT_HALF_SIZE (STFT_SIZE>>1)
|
||||
#define OVERSAMP (1<<2)
|
||||
|
||||
#define STFT_STEP (STFT_SIZE / OVERSAMP)
|
||||
#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1))
|
||||
|
||||
|
||||
typedef struct ALphasor {
|
||||
ALdouble Amplitude;
|
||||
ALdouble Phase;
|
||||
} ALphasor;
|
||||
|
||||
typedef struct ALFrequencyDomain {
|
||||
ALdouble Amplitude;
|
||||
ALdouble Frequency;
|
||||
} ALfrequencyDomain;
|
||||
|
||||
|
||||
typedef struct ALpshifterState {
|
||||
DERIVE_FROM_TYPE(ALeffectState);
|
||||
|
||||
/* Effect parameters */
|
||||
ALsizei count;
|
||||
ALsizei PitchShiftI;
|
||||
ALfloat PitchShift;
|
||||
ALfloat FreqPerBin;
|
||||
|
||||
/*Effects buffers*/
|
||||
ALfloat InFIFO[STFT_SIZE];
|
||||
ALfloat OutFIFO[STFT_STEP];
|
||||
ALdouble LastPhase[STFT_HALF_SIZE+1];
|
||||
ALdouble SumPhase[STFT_HALF_SIZE+1];
|
||||
ALdouble OutputAccum[STFT_SIZE];
|
||||
|
||||
ALcomplex FFTbuffer[STFT_SIZE];
|
||||
|
||||
ALfrequencyDomain Analysis_buffer[STFT_HALF_SIZE+1];
|
||||
ALfrequencyDomain Syntesis_buffer[STFT_HALF_SIZE+1];
|
||||
|
||||
alignas(16) ALfloat BufferOut[BUFFERSIZE];
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
|
||||
} ALpshifterState;
|
||||
|
||||
static ALvoid ALpshifterState_Destruct(ALpshifterState *state);
|
||||
static ALboolean ALpshifterState_deviceUpdate(ALpshifterState *state, ALCdevice *device);
|
||||
static ALvoid ALpshifterState_update(ALpshifterState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
|
||||
static ALvoid ALpshifterState_process(ALpshifterState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
|
||||
DECLARE_DEFAULT_ALLOCATORS(ALpshifterState)
|
||||
|
||||
DEFINE_ALEFFECTSTATE_VTABLE(ALpshifterState);
|
||||
|
||||
|
||||
/* Define a Hann window, used to filter the STFT input and output. */
|
||||
alignas(16) static ALdouble HannWindow[STFT_SIZE];
|
||||
|
||||
static void InitHannWindow(void)
|
||||
{
|
||||
ALsizei i;
|
||||
|
||||
/* Create lookup table of the Hann window for the desired size, i.e. STFT_SIZE */
|
||||
for(i = 0;i < STFT_SIZE>>1;i++)
|
||||
{
|
||||
ALdouble val = sin(M_PI * (ALdouble)i / (ALdouble)(STFT_SIZE-1));
|
||||
HannWindow[i] = HannWindow[STFT_SIZE-1-i] = val * val;
|
||||
}
|
||||
}
|
||||
static alonce_flag HannInitOnce = AL_ONCE_FLAG_INIT;
|
||||
|
||||
|
||||
static inline ALint double2int(ALdouble d)
|
||||
{
|
||||
#if ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
|
||||
!defined(__SSE2_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2)
|
||||
ALint sign, shift;
|
||||
ALint64 mant;
|
||||
union {
|
||||
ALdouble d;
|
||||
ALint64 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&I64(0xfffffffffffff)) | I64(0x10000000000000);
|
||||
if(LIKELY(shift < 0))
|
||||
return (ALint)(mant >> -shift) * sign;
|
||||
return (ALint)(mant << shift) * sign;
|
||||
|
||||
#else
|
||||
|
||||
return (ALint)d;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* Converts ALcomplex to ALphasor */
|
||||
static inline ALphasor rect2polar(ALcomplex number)
|
||||
{
|
||||
ALphasor polar;
|
||||
|
||||
polar.Amplitude = sqrt(number.Real*number.Real + number.Imag*number.Imag);
|
||||
polar.Phase = atan2(number.Imag, number.Real);
|
||||
|
||||
return polar;
|
||||
}
|
||||
|
||||
/* Converts ALphasor to ALcomplex */
|
||||
static inline ALcomplex polar2rect(ALphasor number)
|
||||
{
|
||||
ALcomplex cartesian;
|
||||
|
||||
cartesian.Real = number.Amplitude * cos(number.Phase);
|
||||
cartesian.Imag = number.Amplitude * sin(number.Phase);
|
||||
|
||||
return cartesian;
|
||||
}
|
||||
|
||||
|
||||
static void ALpshifterState_Construct(ALpshifterState *state)
|
||||
{
|
||||
ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
|
||||
SET_VTABLE2(ALpshifterState, ALeffectState, state);
|
||||
|
||||
alcall_once(&HannInitOnce, InitHannWindow);
|
||||
}
|
||||
|
||||
static ALvoid ALpshifterState_Destruct(ALpshifterState *state)
|
||||
{
|
||||
ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
|
||||
}
|
||||
|
||||
static ALboolean ALpshifterState_deviceUpdate(ALpshifterState *state, ALCdevice *device)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
state->count = FIFO_LATENCY;
|
||||
state->PitchShiftI = FRACTIONONE;
|
||||
state->PitchShift = 1.0f;
|
||||
state->FreqPerBin = device->Frequency / (ALfloat)STFT_SIZE;
|
||||
|
||||
memset(state->InFIFO, 0, sizeof(state->InFIFO));
|
||||
memset(state->OutFIFO, 0, sizeof(state->OutFIFO));
|
||||
memset(state->FFTbuffer, 0, sizeof(state->FFTbuffer));
|
||||
memset(state->LastPhase, 0, sizeof(state->LastPhase));
|
||||
memset(state->SumPhase, 0, sizeof(state->SumPhase));
|
||||
memset(state->OutputAccum, 0, sizeof(state->OutputAccum));
|
||||
memset(state->Analysis_buffer, 0, sizeof(state->Analysis_buffer));
|
||||
memset(state->Syntesis_buffer, 0, sizeof(state->Syntesis_buffer));
|
||||
|
||||
memset(state->CurrentGains, 0, sizeof(state->CurrentGains));
|
||||
memset(state->TargetGains, 0, sizeof(state->TargetGains));
|
||||
|
||||
return AL_TRUE;
|
||||
}
|
||||
|
||||
static ALvoid ALpshifterState_update(ALpshifterState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
|
||||
{
|
||||
const ALCdevice *device = context->Device;
|
||||
ALfloat coeffs[MAX_AMBI_COEFFS];
|
||||
float pitch;
|
||||
|
||||
pitch = powf(2.0f,
|
||||
(ALfloat)(props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune) / 1200.0f
|
||||
);
|
||||
state->PitchShiftI = fastf2i(pitch*FRACTIONONE);
|
||||
state->PitchShift = state->PitchShiftI * (1.0f/FRACTIONONE);
|
||||
|
||||
CalcAngleCoeffs(0.0f, 0.0f, 0.0f, coeffs);
|
||||
ComputePanGains(&device->Dry, coeffs, slot->Params.Gain, state->TargetGains);
|
||||
}
|
||||
|
||||
static ALvoid ALpshifterState_process(ALpshifterState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
|
||||
{
|
||||
/* Pitch shifter engine based on the work of Stephan Bernsee.
|
||||
* http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
|
||||
*/
|
||||
|
||||
static const ALdouble expected = M_PI*2.0 / OVERSAMP;
|
||||
const ALdouble freq_per_bin = state->FreqPerBin;
|
||||
ALfloat *restrict bufferOut = state->BufferOut;
|
||||
ALsizei count = state->count;
|
||||
ALsizei i, j, k;
|
||||
|
||||
for(i = 0;i < SamplesToDo;)
|
||||
{
|
||||
do {
|
||||
/* Fill FIFO buffer with samples data */
|
||||
state->InFIFO[count] = SamplesIn[0][i];
|
||||
bufferOut[i] = state->OutFIFO[count - FIFO_LATENCY];
|
||||
|
||||
count++;
|
||||
} while(++i < SamplesToDo && count < STFT_SIZE);
|
||||
|
||||
/* Check whether FIFO buffer is filled */
|
||||
if(count < STFT_SIZE) break;
|
||||
count = FIFO_LATENCY;
|
||||
|
||||
/* Real signal windowing and store in FFTbuffer */
|
||||
for(k = 0;k < STFT_SIZE;k++)
|
||||
{
|
||||
state->FFTbuffer[k].Real = state->InFIFO[k] * HannWindow[k];
|
||||
state->FFTbuffer[k].Imag = 0.0;
|
||||
}
|
||||
|
||||
/* ANALYSIS */
|
||||
/* Apply FFT to FFTbuffer data */
|
||||
complex_fft(state->FFTbuffer, STFT_SIZE, -1.0);
|
||||
|
||||
/* Analyze the obtained data. Since the real FFT is symmetric, only
|
||||
* STFT_HALF_SIZE+1 samples are needed.
|
||||
*/
|
||||
for(k = 0;k < STFT_HALF_SIZE+1;k++)
|
||||
{
|
||||
ALphasor component;
|
||||
ALdouble tmp;
|
||||
ALint qpd;
|
||||
|
||||
/* Compute amplitude and phase */
|
||||
component = rect2polar(state->FFTbuffer[k]);
|
||||
|
||||
/* Compute phase difference and subtract expected phase difference */
|
||||
tmp = (component.Phase - state->LastPhase[k]) - k*expected;
|
||||
|
||||
/* Map delta phase into +/- Pi interval */
|
||||
qpd = double2int(tmp / M_PI);
|
||||
tmp -= M_PI * (qpd + (qpd%2));
|
||||
|
||||
/* Get deviation from bin frequency from the +/- Pi interval */
|
||||
tmp /= expected;
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
state->Analysis_buffer[k].Amplitude = 2.0 * component.Amplitude;
|
||||
state->Analysis_buffer[k].Frequency = (k + tmp) * freq_per_bin;
|
||||
|
||||
/* Store actual phase[k] for the calculations in the next frame*/
|
||||
state->LastPhase[k] = component.Phase;
|
||||
}
|
||||
|
||||
/* PROCESSING */
|
||||
/* pitch shifting */
|
||||
for(k = 0;k < STFT_HALF_SIZE+1;k++)
|
||||
{
|
||||
state->Syntesis_buffer[k].Amplitude = 0.0;
|
||||
state->Syntesis_buffer[k].Frequency = 0.0;
|
||||
}
|
||||
|
||||
for(k = 0;k < STFT_HALF_SIZE+1;k++)
|
||||
{
|
||||
j = (k*state->PitchShiftI) >> FRACTIONBITS;
|
||||
if(j >= STFT_HALF_SIZE+1) break;
|
||||
|
||||
state->Syntesis_buffer[j].Amplitude += state->Analysis_buffer[k].Amplitude;
|
||||
state->Syntesis_buffer[j].Frequency = state->Analysis_buffer[k].Frequency *
|
||||
state->PitchShift;
|
||||
}
|
||||
|
||||
/* SYNTHESIS */
|
||||
/* Synthesis the processing data */
|
||||
for(k = 0;k < STFT_HALF_SIZE+1;k++)
|
||||
{
|
||||
ALphasor component;
|
||||
ALdouble tmp;
|
||||
|
||||
/* Compute bin deviation from scaled freq */
|
||||
tmp = state->Syntesis_buffer[k].Frequency/freq_per_bin - k;
|
||||
|
||||
/* Calculate actual delta phase and accumulate it to get bin phase */
|
||||
state->SumPhase[k] += (k + tmp) * expected;
|
||||
|
||||
component.Amplitude = state->Syntesis_buffer[k].Amplitude;
|
||||
component.Phase = state->SumPhase[k];
|
||||
|
||||
/* Compute phasor component to cartesian complex number and storage it into FFTbuffer*/
|
||||
state->FFTbuffer[k] = polar2rect(component);
|
||||
}
|
||||
/* zero negative frequencies for recontruct a real signal */
|
||||
for(k = STFT_HALF_SIZE+1;k < STFT_SIZE;k++)
|
||||
{
|
||||
state->FFTbuffer[k].Real = 0.0;
|
||||
state->FFTbuffer[k].Imag = 0.0;
|
||||
}
|
||||
|
||||
/* Apply iFFT to buffer data */
|
||||
complex_fft(state->FFTbuffer, STFT_SIZE, 1.0);
|
||||
|
||||
/* Windowing and add to output */
|
||||
for(k = 0;k < STFT_SIZE;k++)
|
||||
state->OutputAccum[k] += HannWindow[k] * state->FFTbuffer[k].Real /
|
||||
(0.5 * STFT_HALF_SIZE * OVERSAMP);
|
||||
|
||||
/* Shift accumulator, input & output FIFO */
|
||||
for(k = 0;k < STFT_STEP;k++) state->OutFIFO[k] = (ALfloat)state->OutputAccum[k];
|
||||
for(j = 0;k < STFT_SIZE;k++,j++) state->OutputAccum[j] = state->OutputAccum[k];
|
||||
for(;j < STFT_SIZE;j++) state->OutputAccum[j] = 0.0;
|
||||
for(k = 0;k < FIFO_LATENCY;k++)
|
||||
state->InFIFO[k] = state->InFIFO[k+STFT_STEP];
|
||||
}
|
||||
state->count = count;
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples(bufferOut, NumChannels, SamplesOut, state->CurrentGains, state->TargetGains,
|
||||
maxi(SamplesToDo, 512), 0, SamplesToDo);
|
||||
}
|
||||
|
||||
typedef struct PshifterStateFactory {
|
||||
DERIVE_FROM_TYPE(EffectStateFactory);
|
||||
} PshifterStateFactory;
|
||||
|
||||
static ALeffectState *PshifterStateFactory_create(PshifterStateFactory *UNUSED(factory))
|
||||
{
|
||||
ALpshifterState *state;
|
||||
|
||||
NEW_OBJ0(state, ALpshifterState)();
|
||||
if(!state) return NULL;
|
||||
|
||||
return STATIC_CAST(ALeffectState, state);
|
||||
}
|
||||
|
||||
DEFINE_EFFECTSTATEFACTORY_VTABLE(PshifterStateFactory);
|
||||
|
||||
EffectStateFactory *PshifterStateFactory_getFactory(void)
|
||||
{
|
||||
static PshifterStateFactory PshifterFactory = { { GET_VTABLE2(PshifterStateFactory, EffectStateFactory) } };
|
||||
|
||||
return STATIC_CAST(EffectStateFactory, &PshifterFactory);
|
||||
}
|
||||
|
||||
|
||||
void ALpshifter_setParamf(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat UNUSED(val))
|
||||
{
|
||||
alSetError( context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param );
|
||||
}
|
||||
|
||||
void ALpshifter_setParamfv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALfloat *UNUSED(vals))
|
||||
{
|
||||
alSetError( context, AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", param );
|
||||
}
|
||||
|
||||
void ALpshifter_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
|
||||
{
|
||||
ALeffectProps *props = &effect->Props;
|
||||
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);
|
||||
}
|
||||
}
|
||||
void ALpshifter_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
|
||||
{
|
||||
ALpshifter_setParami(effect, context, param, vals[0]);
|
||||
}
|
||||
|
||||
void ALpshifter_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
|
||||
{
|
||||
const ALeffectProps *props = &effect->Props;
|
||||
switch(param)
|
||||
{
|
||||
case AL_PITCH_SHIFTER_COARSE_TUNE:
|
||||
*val = (ALint)props->Pshifter.CoarseTune;
|
||||
break;
|
||||
case AL_PITCH_SHIFTER_FINE_TUNE:
|
||||
*val = (ALint)props->Pshifter.FineTune;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
void ALpshifter_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
|
||||
{
|
||||
ALpshifter_getParami(effect, context, param, vals);
|
||||
}
|
||||
|
||||
void ALpshifter_getParamf(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat *UNUSED(val))
|
||||
{
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param);
|
||||
}
|
||||
|
||||
void ALpshifter_getParamfv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat *UNUSED(vals))
|
||||
{
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param);
|
||||
}
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(ALpshifter);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,112 @@
|
||||
#ifndef ALC_FILTER_H
|
||||
#define ALC_FILTER_H
|
||||
|
||||
#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).
|
||||
*/
|
||||
|
||||
typedef enum BiquadType {
|
||||
/** EFX-style low-pass filter, specifying a gain and reference frequency. */
|
||||
BiquadType_HighShelf,
|
||||
/** EFX-style high-pass filter, specifying a gain and reference frequency. */
|
||||
BiquadType_LowShelf,
|
||||
/** Peaking filter, specifying a gain and reference frequency. */
|
||||
BiquadType_Peaking,
|
||||
|
||||
/** Low-pass cut-off filter, specifying a cut-off frequency. */
|
||||
BiquadType_LowPass,
|
||||
/** High-pass cut-off filter, specifying a cut-off frequency. */
|
||||
BiquadType_HighPass,
|
||||
/** Band-pass filter, specifying a center frequency. */
|
||||
BiquadType_BandPass,
|
||||
} BiquadType;
|
||||
|
||||
typedef struct BiquadFilter {
|
||||
ALfloat z1, z2; /* Last two delayed components for direct form II. */
|
||||
ALfloat b0, b1, b2; /* Transfer function coefficients "b" (numerator) */
|
||||
ALfloat a1, a2; /* Transfer function coefficients "a" (denominator; a0 is
|
||||
* pre-applied). */
|
||||
} BiquadFilter;
|
||||
/* Currently only a C-based filter process method is implemented. */
|
||||
#define BiquadFilter_process BiquadFilter_processC
|
||||
|
||||
/**
|
||||
* 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 ALfloat calc_rcpQ_from_slope(ALfloat gain, ALfloat slope)
|
||||
{
|
||||
return sqrtf((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f);
|
||||
}
|
||||
/**
|
||||
* 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 ALfloat calc_rcpQ_from_bandwidth(ALfloat f0norm, ALfloat bandwidth)
|
||||
{
|
||||
ALfloat w0 = F_TAU * f0norm;
|
||||
return 2.0f*sinhf(logf(2.0f)/2.0f*bandwidth*w0/sinf(w0));
|
||||
}
|
||||
|
||||
inline void BiquadFilter_clear(BiquadFilter *filter)
|
||||
{
|
||||
filter->z1 = 0.0f;
|
||||
filter->z2 = 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter state for the specified filter type and its parameters.
|
||||
*
|
||||
* \param filter The filter object to prepare.
|
||||
* \param type The type of filter for the object to apply.
|
||||
* \param gain The gain for the reference frequency response. Only used by the
|
||||
* Shelf and Peaking filter types.
|
||||
* \param f0norm The normalized reference frequency (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 depending on the available data.
|
||||
*/
|
||||
void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain, ALfloat f0norm, ALfloat rcpQ);
|
||||
|
||||
inline void BiquadFilter_copyParams(BiquadFilter *restrict dst, const BiquadFilter *restrict src)
|
||||
{
|
||||
dst->b0 = src->b0;
|
||||
dst->b1 = src->b1;
|
||||
dst->b2 = src->b2;
|
||||
dst->a1 = src->a1;
|
||||
dst->a2 = src->a2;
|
||||
}
|
||||
|
||||
void BiquadFilter_processC(BiquadFilter *filter, ALfloat *restrict dst, const ALfloat *restrict src, ALsizei numsamples);
|
||||
|
||||
inline void BiquadFilter_passthru(BiquadFilter *filter, ALsizei numsamples)
|
||||
{
|
||||
if(LIKELY(numsamples >= 2))
|
||||
{
|
||||
filter->z1 = 0.0f;
|
||||
filter->z2 = 0.0f;
|
||||
}
|
||||
else if(numsamples == 1)
|
||||
{
|
||||
filter->z1 = filter->z2;
|
||||
filter->z2 = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ALC_FILTER_H */
|
||||
@@ -1,35 +1,39 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "biquad.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/al.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include "alMain.h"
|
||||
#include "defs.h"
|
||||
|
||||
#include "opthelpers.h"
|
||||
extern inline void BiquadFilter_clear(BiquadFilter *filter);
|
||||
extern inline void BiquadFilter_copyParams(BiquadFilter *restrict dst, const BiquadFilter *restrict src);
|
||||
extern inline void BiquadFilter_passthru(BiquadFilter *filter, ALsizei numsamples);
|
||||
extern inline ALfloat calc_rcpQ_from_slope(ALfloat gain, ALfloat slope);
|
||||
extern inline ALfloat calc_rcpQ_from_bandwidth(ALfloat f0norm, ALfloat bandwidth);
|
||||
|
||||
|
||||
template<typename Real>
|
||||
void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ)
|
||||
void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain, ALfloat f0norm, ALfloat rcpQ)
|
||||
{
|
||||
ALfloat alpha, sqrtgain_alpha_2;
|
||||
ALfloat w0, sin_w0, cos_w0;
|
||||
ALfloat a[3] = { 1.0f, 0.0f, 0.0f };
|
||||
ALfloat b[3] = { 1.0f, 0.0f, 0.0f };
|
||||
|
||||
// Limit gain to -100dB
|
||||
assert(gain > 0.00001f);
|
||||
|
||||
const Real w0{al::MathDefs<Real>::Tau() * f0norm};
|
||||
const Real sin_w0{std::sin(w0)};
|
||||
const Real cos_w0{std::cos(w0)};
|
||||
const Real alpha{sin_w0/2.0f * rcpQ};
|
||||
|
||||
Real sqrtgain_alpha_2;
|
||||
Real a[3]{ 1.0f, 0.0f, 0.0f };
|
||||
Real b[3]{ 1.0f, 0.0f, 0.0f };
|
||||
w0 = F_TAU * f0norm;
|
||||
sin_w0 = sinf(w0);
|
||||
cos_w0 = cosf(w0);
|
||||
alpha = sin_w0/2.0f * rcpQ;
|
||||
|
||||
/* Calculate filter coefficients depending on filter type */
|
||||
switch(type)
|
||||
{
|
||||
case BiquadType::HighShelf:
|
||||
sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha;
|
||||
case BiquadType_HighShelf:
|
||||
sqrtgain_alpha_2 = 2.0f * sqrtf(gain) * alpha;
|
||||
b[0] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
|
||||
b[1] = -2.0f*gain*((gain-1.0f) + (gain+1.0f)*cos_w0 );
|
||||
b[2] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
|
||||
@@ -37,8 +41,8 @@ void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Rea
|
||||
a[1] = 2.0f* ((gain-1.0f) - (gain+1.0f)*cos_w0 );
|
||||
a[2] = (gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
|
||||
break;
|
||||
case BiquadType::LowShelf:
|
||||
sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha;
|
||||
case BiquadType_LowShelf:
|
||||
sqrtgain_alpha_2 = 2.0f * sqrtf(gain) * alpha;
|
||||
b[0] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
|
||||
b[1] = 2.0f*gain*((gain-1.0f) - (gain+1.0f)*cos_w0 );
|
||||
b[2] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
|
||||
@@ -46,7 +50,8 @@ void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Rea
|
||||
a[1] = -2.0f* ((gain-1.0f) + (gain+1.0f)*cos_w0 );
|
||||
a[2] = (gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
|
||||
break;
|
||||
case BiquadType::Peaking:
|
||||
case BiquadType_Peaking:
|
||||
gain = sqrtf(gain);
|
||||
b[0] = 1.0f + alpha * gain;
|
||||
b[1] = -2.0f * cos_w0;
|
||||
b[2] = 1.0f - alpha * gain;
|
||||
@@ -55,7 +60,7 @@ void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Rea
|
||||
a[2] = 1.0f - alpha / gain;
|
||||
break;
|
||||
|
||||
case BiquadType::LowPass:
|
||||
case BiquadType_LowPass:
|
||||
b[0] = (1.0f - cos_w0) / 2.0f;
|
||||
b[1] = 1.0f - cos_w0;
|
||||
b[2] = (1.0f - cos_w0) / 2.0f;
|
||||
@@ -63,7 +68,7 @@ void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Rea
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha;
|
||||
break;
|
||||
case BiquadType::HighPass:
|
||||
case BiquadType_HighPass:
|
||||
b[0] = (1.0f + cos_w0) / 2.0f;
|
||||
b[1] = -(1.0f + cos_w0);
|
||||
b[2] = (1.0f + cos_w0) / 2.0f;
|
||||
@@ -71,9 +76,9 @@ void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Rea
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha;
|
||||
break;
|
||||
case BiquadType::BandPass:
|
||||
case BiquadType_BandPass:
|
||||
b[0] = alpha;
|
||||
b[1] = 0.0f;
|
||||
b[1] = 0;
|
||||
b[2] = -alpha;
|
||||
a[0] = 1.0f + alpha;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
@@ -81,23 +86,26 @@ void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Rea
|
||||
break;
|
||||
}
|
||||
|
||||
mA1 = a[1] / a[0];
|
||||
mA2 = a[2] / a[0];
|
||||
mB0 = b[0] / a[0];
|
||||
mB1 = b[1] / a[0];
|
||||
mB2 = b[2] / a[0];
|
||||
filter->a1 = a[1] / a[0];
|
||||
filter->a2 = a[2] / a[0];
|
||||
filter->b0 = b[0] / a[0];
|
||||
filter->b1 = b[1] / a[0];
|
||||
filter->b2 = b[2] / a[0];
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BiquadFilterR<Real>::process(const al::span<const Real> src, Real *dst)
|
||||
|
||||
void BiquadFilter_processC(BiquadFilter *filter, ALfloat *restrict dst, const ALfloat *restrict src, ALsizei numsamples)
|
||||
{
|
||||
const Real b0{mB0};
|
||||
const Real b1{mB1};
|
||||
const Real b2{mB2};
|
||||
const Real a1{mA1};
|
||||
const Real a2{mA2};
|
||||
Real z1{mZ1};
|
||||
Real z2{mZ2};
|
||||
const ALfloat a1 = filter->a1;
|
||||
const ALfloat a2 = filter->a2;
|
||||
const ALfloat b0 = filter->b0;
|
||||
const ALfloat b1 = filter->b1;
|
||||
const ALfloat b2 = filter->b2;
|
||||
ALfloat z1 = filter->z1;
|
||||
ALfloat z2 = filter->z2;
|
||||
ALsizei i;
|
||||
|
||||
ASSUME(numsamples > 0);
|
||||
|
||||
/* Processing loop is Transposed Direct Form II. This requires less storage
|
||||
* compared to Direct Form I (only two delay components, instead of a four-
|
||||
@@ -107,57 +115,15 @@ void BiquadFilterR<Real>::process(const al::span<const Real> src, Real *dst)
|
||||
*
|
||||
* See: http://www.earlevel.com/main/2003/02/28/biquads/
|
||||
*/
|
||||
auto proc_sample = [b0,b1,b2,a1,a2,&z1,&z2](Real input) noexcept -> Real
|
||||
for(i = 0;i < numsamples;i++)
|
||||
{
|
||||
const Real output{input*b0 + z1};
|
||||
ALfloat input = src[i];
|
||||
ALfloat output = input*b0 + z1;
|
||||
z1 = input*b1 - output*a1 + z2;
|
||||
z2 = input*b2 - output*a2;
|
||||
return output;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
dst[i] = output;
|
||||
}
|
||||
|
||||
mZ1 = z1;
|
||||
mZ2 = z2;
|
||||
filter->z1 = z1;
|
||||
filter->z2 = z2;
|
||||
}
|
||||
|
||||
template<typename Real>
|
||||
void BiquadFilterR<Real>::dualProcess(BiquadFilterR &other, const al::span<const Real> src,
|
||||
Real *dst)
|
||||
{
|
||||
const Real b00{mB0};
|
||||
const Real b01{mB1};
|
||||
const Real b02{mB2};
|
||||
const Real a01{mA1};
|
||||
const Real a02{mA2};
|
||||
const Real b10{other.mB0};
|
||||
const Real b11{other.mB1};
|
||||
const Real b12{other.mB2};
|
||||
const Real a11{other.mA1};
|
||||
const Real a12{other.mA2};
|
||||
Real z01{mZ1};
|
||||
Real z02{mZ2};
|
||||
Real z11{other.mZ1};
|
||||
Real z12{other.mZ2};
|
||||
|
||||
auto proc_sample = [b00,b01,b02,a01,a02,b10,b11,b12,a11,a12,&z01,&z02,&z11,&z12](Real input) noexcept -> Real
|
||||
{
|
||||
const Real tmpout{input*b00 + z01};
|
||||
z01 = input*b01 - tmpout*a01 + z02;
|
||||
z02 = input*b02 - tmpout*a02;
|
||||
input = tmpout;
|
||||
|
||||
const Real output{input*b10 + z11};
|
||||
z11 = input*b11 - output*a11 + z12;
|
||||
z12 = input*b12 - output*a12;
|
||||
return output;
|
||||
};
|
||||
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
|
||||
|
||||
mZ1 = z01;
|
||||
mZ2 = z02;
|
||||
other.mZ1 = z11;
|
||||
other.mZ2 = z12;
|
||||
}
|
||||
|
||||
template class BiquadFilterR<float>;
|
||||
template class BiquadFilterR<double>;
|
||||
@@ -0,0 +1,426 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "nfc.h"
|
||||
#include "alMain.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/* Near-field control filters are the basis for handling the near-field effect.
|
||||
* The near-field effect is a bass-boost present in the directional components
|
||||
* of a recorded signal, created as a result of the wavefront curvature (itself
|
||||
* a function of sound distance). Proper reproduction dictates this be
|
||||
* compensated for using a bass-cut given the playback speaker distance, to
|
||||
* avoid excessive bass in the playback.
|
||||
*
|
||||
* For real-time rendered audio, emulating the near-field effect based on the
|
||||
* sound source's distance, and subsequently compensating for it at output
|
||||
* based on the speaker distances, can create a more realistic perception of
|
||||
* sound distance beyond a simple 1/r attenuation.
|
||||
*
|
||||
* These filters do just that. Each one applies a low-shelf filter, created as
|
||||
* the combination of a bass-boost for a given sound source distance (near-
|
||||
* field emulation) along with a bass-cut for a given control/speaker distance
|
||||
* (near-field compensation).
|
||||
*
|
||||
* Note that it is necessary to apply a cut along with the boost, since the
|
||||
* boost alone is unstable in higher-order ambisonics as it causes an infinite
|
||||
* DC gain (even first-order ambisonics requires there to be no DC offset for
|
||||
* the boost to work). Consequently, ambisonics requires a control parameter to
|
||||
* be used to avoid an unstable boost-only filter. NFC-HOA defines this control
|
||||
* as a reference delay, calculated with:
|
||||
*
|
||||
* reference_delay = control_distance / speed_of_sound
|
||||
*
|
||||
* This means w0 (for input) or w1 (for output) should be set to:
|
||||
*
|
||||
* wN = 1 / (reference_delay * sample_rate)
|
||||
*
|
||||
* when dealing with NFC-HOA content. For FOA input content, which does not
|
||||
* specify a reference_delay variable, w0 should be set to 0 to apply only
|
||||
* near-field compensation for output. It's important that w1 be a finite,
|
||||
* positive, non-0 value or else the bass-boost will become unstable again.
|
||||
* Also, w0 should not be too large compared to w1, to avoid excessively loud
|
||||
* low frequencies.
|
||||
*/
|
||||
|
||||
static const float B[4][3] = {
|
||||
{ 0.0f },
|
||||
{ 1.0f },
|
||||
{ 3.0f, 3.0f },
|
||||
{ 3.6778f, 6.4595f, 2.3222f },
|
||||
/*{ 4.2076f, 11.4877f, 5.7924f, 9.1401f }*/
|
||||
};
|
||||
|
||||
static void NfcFilterCreate1(struct NfcFilter1 *nfc, const float w0, const float w1)
|
||||
{
|
||||
float b_00, g_0;
|
||||
float r;
|
||||
|
||||
nfc->base_gain = 1.0f;
|
||||
nfc->gain = 1.0f;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
b_00 = B[1][0] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc->gain *= g_0;
|
||||
nfc->b1 = 2.0f * b_00 / g_0;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_00 = B[1][0] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc->base_gain /= g_0;
|
||||
nfc->gain /= g_0;
|
||||
nfc->a1 = 2.0f * b_00 / g_0;
|
||||
}
|
||||
|
||||
static void NfcFilterAdjust1(struct NfcFilter1 *nfc, const float w0)
|
||||
{
|
||||
float b_00, g_0;
|
||||
float r;
|
||||
|
||||
r = 0.5f * w0;
|
||||
b_00 = B[1][0] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc->gain = nfc->base_gain * g_0;
|
||||
nfc->b1 = 2.0f * b_00 / g_0;
|
||||
}
|
||||
|
||||
|
||||
static void NfcFilterCreate2(struct NfcFilter2 *nfc, const float w0, const float w1)
|
||||
{
|
||||
float b_10, b_11, g_1;
|
||||
float r;
|
||||
|
||||
nfc->base_gain = 1.0f;
|
||||
nfc->gain = 1.0f;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
b_10 = B[2][0] * r;
|
||||
b_11 = B[2][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc->gain *= g_1;
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[2][0] * r;
|
||||
b_11 = B[2][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc->base_gain /= g_1;
|
||||
nfc->gain /= g_1;
|
||||
nfc->a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->a2 = 4.0f * b_11 / g_1;
|
||||
}
|
||||
|
||||
static void NfcFilterAdjust2(struct NfcFilter2 *nfc, const float w0)
|
||||
{
|
||||
float b_10, b_11, g_1;
|
||||
float r;
|
||||
|
||||
r = 0.5f * w0;
|
||||
b_10 = B[2][0] * r;
|
||||
b_11 = B[2][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc->gain = nfc->base_gain * g_1;
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
}
|
||||
|
||||
|
||||
static void NfcFilterCreate3(struct NfcFilter3 *nfc, const float w0, const float w1)
|
||||
{
|
||||
float b_10, b_11, g_1;
|
||||
float b_00, g_0;
|
||||
float r;
|
||||
|
||||
nfc->base_gain = 1.0f;
|
||||
nfc->gain = 1.0f;
|
||||
|
||||
/* Calculate bass-boost coefficients. */
|
||||
r = 0.5f * w0;
|
||||
b_10 = B[3][0] * r;
|
||||
b_11 = B[3][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc->gain *= g_1;
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
|
||||
b_00 = B[3][2] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc->gain *= g_0;
|
||||
nfc->b3 = 2.0f * b_00 / g_0;
|
||||
|
||||
/* Calculate bass-cut coefficients. */
|
||||
r = 0.5f * w1;
|
||||
b_10 = B[3][0] * r;
|
||||
b_11 = B[3][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc->base_gain /= g_1;
|
||||
nfc->gain /= g_1;
|
||||
nfc->a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->a2 = 4.0f * b_11 / g_1;
|
||||
|
||||
b_00 = B[3][2] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc->base_gain /= g_0;
|
||||
nfc->gain /= g_0;
|
||||
nfc->a3 = 2.0f * b_00 / g_0;
|
||||
}
|
||||
|
||||
static void NfcFilterAdjust3(struct NfcFilter3 *nfc, const float w0)
|
||||
{
|
||||
float b_10, b_11, g_1;
|
||||
float b_00, g_0;
|
||||
float r;
|
||||
|
||||
r = 0.5f * w0;
|
||||
b_10 = B[3][0] * r;
|
||||
b_11 = B[3][1] * r * r;
|
||||
g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc->gain = nfc->base_gain * g_1;
|
||||
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
|
||||
nfc->b2 = 4.0f * b_11 / g_1;
|
||||
|
||||
b_00 = B[3][2] * r;
|
||||
g_0 = 1.0f + b_00;
|
||||
|
||||
nfc->gain *= g_0;
|
||||
nfc->b3 = 2.0f * b_00 / g_0;
|
||||
}
|
||||
|
||||
|
||||
void NfcFilterCreate(NfcFilter *nfc, const float w0, const float w1)
|
||||
{
|
||||
memset(nfc, 0, sizeof(*nfc));
|
||||
NfcFilterCreate1(&nfc->first, w0, w1);
|
||||
NfcFilterCreate2(&nfc->second, w0, w1);
|
||||
NfcFilterCreate3(&nfc->third, w0, w1);
|
||||
}
|
||||
|
||||
void NfcFilterAdjust(NfcFilter *nfc, const float w0)
|
||||
{
|
||||
NfcFilterAdjust1(&nfc->first, w0);
|
||||
NfcFilterAdjust2(&nfc->second, w0);
|
||||
NfcFilterAdjust3(&nfc->third, w0);
|
||||
}
|
||||
|
||||
|
||||
void NfcFilterProcess1(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count)
|
||||
{
|
||||
const float gain = nfc->first.gain;
|
||||
const float b1 = nfc->first.b1;
|
||||
const float a1 = nfc->first.a1;
|
||||
float z1 = nfc->first.z[0];
|
||||
int i;
|
||||
|
||||
ASSUME(count > 0);
|
||||
|
||||
for(i = 0;i < count;i++)
|
||||
{
|
||||
float y = src[i]*gain - a1*z1;
|
||||
float out = y + b1*z1;
|
||||
z1 += y;
|
||||
|
||||
dst[i] = out;
|
||||
}
|
||||
nfc->first.z[0] = z1;
|
||||
}
|
||||
|
||||
void NfcFilterProcess2(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count)
|
||||
{
|
||||
const float gain = nfc->second.gain;
|
||||
const float b1 = nfc->second.b1;
|
||||
const float b2 = nfc->second.b2;
|
||||
const float a1 = nfc->second.a1;
|
||||
const float a2 = nfc->second.a2;
|
||||
float z1 = nfc->second.z[0];
|
||||
float z2 = nfc->second.z[1];
|
||||
int i;
|
||||
|
||||
ASSUME(count > 0);
|
||||
|
||||
for(i = 0;i < count;i++)
|
||||
{
|
||||
float y = src[i]*gain - a1*z1 - a2*z2;
|
||||
float out = y + b1*z1 + b2*z2;
|
||||
z2 += z1;
|
||||
z1 += y;
|
||||
|
||||
dst[i] = out;
|
||||
}
|
||||
nfc->second.z[0] = z1;
|
||||
nfc->second.z[1] = z2;
|
||||
}
|
||||
|
||||
void NfcFilterProcess3(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count)
|
||||
{
|
||||
const float gain = nfc->third.gain;
|
||||
const float b1 = nfc->third.b1;
|
||||
const float b2 = nfc->third.b2;
|
||||
const float b3 = nfc->third.b3;
|
||||
const float a1 = nfc->third.a1;
|
||||
const float a2 = nfc->third.a2;
|
||||
const float a3 = nfc->third.a3;
|
||||
float z1 = nfc->third.z[0];
|
||||
float z2 = nfc->third.z[1];
|
||||
float z3 = nfc->third.z[2];
|
||||
int i;
|
||||
|
||||
ASSUME(count > 0);
|
||||
|
||||
for(i = 0;i < count;i++)
|
||||
{
|
||||
float y = src[i]*gain - a1*z1 - a2*z2;
|
||||
float out = y + b1*z1 + b2*z2;
|
||||
z2 += z1;
|
||||
z1 += y;
|
||||
|
||||
y = out - a3*z3;
|
||||
out = y + b3*z3;
|
||||
z3 += y;
|
||||
|
||||
dst[i] = out;
|
||||
}
|
||||
nfc->third.z[0] = z1;
|
||||
nfc->third.z[1] = z2;
|
||||
nfc->third.z[2] = z3;
|
||||
}
|
||||
|
||||
#if 0 /* Original methods the above are derived from. */
|
||||
static void NfcFilterCreate(NfcFilter *nfc, const ALsizei order, const float src_dist, const float ctl_dist, const float rate)
|
||||
{
|
||||
static const float B[4][5] = {
|
||||
{ },
|
||||
{ 1.0f },
|
||||
{ 3.0f, 3.0f },
|
||||
{ 3.6778f, 6.4595f, 2.3222f },
|
||||
{ 4.2076f, 11.4877f, 5.7924f, 9.1401f }
|
||||
};
|
||||
float w0 = SPEEDOFSOUNDMETRESPERSEC / (src_dist * rate);
|
||||
float w1 = SPEEDOFSOUNDMETRESPERSEC / (ctl_dist * rate);
|
||||
ALsizei i;
|
||||
float r;
|
||||
|
||||
nfc->g = 1.0f;
|
||||
nfc->coeffs[0] = 1.0f;
|
||||
|
||||
/* NOTE: Slight adjustment from the literature to raise the center
|
||||
* frequency a bit (0.5 -> 1.0).
|
||||
*/
|
||||
r = 1.0f * w0;
|
||||
for(i = 0; i < (order-1);i += 2)
|
||||
{
|
||||
float b_10 = B[order][i ] * r;
|
||||
float b_11 = B[order][i+1] * r * r;
|
||||
float g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc->b[i] = b_10;
|
||||
nfc->b[i + 1] = b_11;
|
||||
nfc->coeffs[0] *= g_1;
|
||||
nfc->coeffs[i+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1;
|
||||
nfc->coeffs[i+2] = (4.0f * b_11) / g_1;
|
||||
}
|
||||
if(i < order)
|
||||
{
|
||||
float b_00 = B[order][i] * r;
|
||||
float g_0 = 1.0f + b_00;
|
||||
|
||||
nfc->b[i] = b_00;
|
||||
nfc->coeffs[0] *= g_0;
|
||||
nfc->coeffs[i+1] = (2.0f * b_00) / g_0;
|
||||
}
|
||||
|
||||
r = 1.0f * w1;
|
||||
for(i = 0;i < (order-1);i += 2)
|
||||
{
|
||||
float b_10 = B[order][i ] * r;
|
||||
float b_11 = B[order][i+1] * r * r;
|
||||
float g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc->g /= g_1;
|
||||
nfc->coeffs[0] /= g_1;
|
||||
nfc->coeffs[order+i+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1;
|
||||
nfc->coeffs[order+i+2] = (4.0f * b_11) / g_1;
|
||||
}
|
||||
if(i < order)
|
||||
{
|
||||
float b_00 = B[order][i] * r;
|
||||
float g_0 = 1.0f + b_00;
|
||||
|
||||
nfc->g /= g_0;
|
||||
nfc->coeffs[0] /= g_0;
|
||||
nfc->coeffs[order+i+1] = (2.0f * b_00) / g_0;
|
||||
}
|
||||
|
||||
for(i = 0; i < MAX_AMBI_ORDER; i++)
|
||||
nfc->history[i] = 0.0f;
|
||||
}
|
||||
|
||||
static void NfcFilterAdjust(NfcFilter *nfc, const float distance)
|
||||
{
|
||||
int i;
|
||||
|
||||
nfc->coeffs[0] = nfc->g;
|
||||
|
||||
for(i = 0;i < (nfc->order-1);i += 2)
|
||||
{
|
||||
float b_10 = nfc->b[i] / distance;
|
||||
float b_11 = nfc->b[i+1] / (distance * distance);
|
||||
float g_1 = 1.0f + b_10 + b_11;
|
||||
|
||||
nfc->coeffs[0] *= g_1;
|
||||
nfc->coeffs[i+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1;
|
||||
nfc->coeffs[i+2] = (4.0f * b_11) / g_1;
|
||||
}
|
||||
if(i < nfc->order)
|
||||
{
|
||||
float b_00 = nfc->b[i] / distance;
|
||||
float g_0 = 1.0f + b_00;
|
||||
|
||||
nfc->coeffs[0] *= g_0;
|
||||
nfc->coeffs[i+1] = (2.0f * b_00) / g_0;
|
||||
}
|
||||
}
|
||||
|
||||
static float NfcFilterProcess(const float in, NfcFilter *nfc)
|
||||
{
|
||||
int i;
|
||||
float out = in * nfc->coeffs[0];
|
||||
|
||||
for(i = 0;i < (nfc->order-1);i += 2)
|
||||
{
|
||||
float y = out - (nfc->coeffs[nfc->order+i+1] * nfc->history[i]) -
|
||||
(nfc->coeffs[nfc->order+i+2] * nfc->history[i+1]) + 1.0e-30f;
|
||||
out = y + (nfc->coeffs[i+1]*nfc->history[i]) + (nfc->coeffs[i+2]*nfc->history[i+1]);
|
||||
|
||||
nfc->history[i+1] += nfc->history[i];
|
||||
nfc->history[i] += y;
|
||||
}
|
||||
if(i < nfc->order)
|
||||
{
|
||||
float y = out - (nfc->coeffs[nfc->order+i+1] * nfc->history[i]) + 1.0e-30f;
|
||||
|
||||
out = y + (nfc->coeffs[i+1] * nfc->history[i]);
|
||||
nfc->history[i] += y;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef FILTER_NFC_H
|
||||
#define FILTER_NFC_H
|
||||
|
||||
struct NfcFilter1 {
|
||||
float base_gain, gain;
|
||||
float b1, a1;
|
||||
float z[1];
|
||||
};
|
||||
struct NfcFilter2 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, a1, a2;
|
||||
float z[2];
|
||||
};
|
||||
struct NfcFilter3 {
|
||||
float base_gain, gain;
|
||||
float b1, b2, b3, a1, a2, a3;
|
||||
float z[3];
|
||||
};
|
||||
|
||||
typedef struct NfcFilter {
|
||||
struct NfcFilter1 first;
|
||||
struct NfcFilter2 second;
|
||||
struct NfcFilter3 third;
|
||||
} NfcFilter;
|
||||
|
||||
|
||||
/* NOTE:
|
||||
* w0 = speed_of_sound / (source_distance * sample_rate);
|
||||
* w1 = speed_of_sound / (control_distance * sample_rate);
|
||||
*
|
||||
* Generally speaking, the control distance should be approximately the average
|
||||
* speaker distance, or based on the reference delay if outputing NFC-HOA. It
|
||||
* must not be negative, 0, or infinite. The source distance should not be too
|
||||
* small relative to the control distance.
|
||||
*/
|
||||
|
||||
void NfcFilterCreate(NfcFilter *nfc, const float w0, const float w1);
|
||||
void NfcFilterAdjust(NfcFilter *nfc, const float w0);
|
||||
|
||||
/* Near-field control filter for first-order ambisonic channels (1-3). */
|
||||
void NfcFilterProcess1(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count);
|
||||
|
||||
/* Near-field control filter for second-order ambisonic channels (4-8). */
|
||||
void NfcFilterProcess2(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count);
|
||||
|
||||
/* Near-field control filter for third-order ambisonic channels (9-15). */
|
||||
void NfcFilterProcess3(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count);
|
||||
|
||||
#endif /* FILTER_NFC_H */
|
||||
@@ -0,0 +1,109 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "splitter.h"
|
||||
|
||||
#include "math_defs.h"
|
||||
|
||||
|
||||
void bandsplit_init(BandSplitter *splitter, ALfloat f0norm)
|
||||
{
|
||||
ALfloat w = f0norm * F_TAU;
|
||||
ALfloat cw = cosf(w);
|
||||
if(cw > FLT_EPSILON)
|
||||
splitter->coeff = (sinf(w) - 1.0f) / cw;
|
||||
else
|
||||
splitter->coeff = cw * -0.5f;
|
||||
|
||||
splitter->lp_z1 = 0.0f;
|
||||
splitter->lp_z2 = 0.0f;
|
||||
splitter->hp_z1 = 0.0f;
|
||||
}
|
||||
|
||||
void bandsplit_clear(BandSplitter *splitter)
|
||||
{
|
||||
splitter->lp_z1 = 0.0f;
|
||||
splitter->lp_z2 = 0.0f;
|
||||
splitter->hp_z1 = 0.0f;
|
||||
}
|
||||
|
||||
void bandsplit_process(BandSplitter *splitter, ALfloat *restrict hpout, ALfloat *restrict lpout,
|
||||
const ALfloat *input, ALsizei count)
|
||||
{
|
||||
ALfloat lp_coeff, hp_coeff, lp_y, hp_y, d;
|
||||
ALfloat lp_z1, lp_z2, hp_z1;
|
||||
ALsizei i;
|
||||
|
||||
ASSUME(count > 0);
|
||||
|
||||
hp_coeff = splitter->coeff;
|
||||
lp_coeff = splitter->coeff*0.5f + 0.5f;
|
||||
lp_z1 = splitter->lp_z1;
|
||||
lp_z2 = splitter->lp_z2;
|
||||
hp_z1 = splitter->hp_z1;
|
||||
for(i = 0;i < count;i++)
|
||||
{
|
||||
ALfloat in = input[i];
|
||||
|
||||
/* Low-pass sample processing. */
|
||||
d = (in - lp_z1) * lp_coeff;
|
||||
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[i] = lp_y;
|
||||
|
||||
/* All-pass sample processing. */
|
||||
hp_y = in*hp_coeff + hp_z1;
|
||||
hp_z1 = in - hp_y*hp_coeff;
|
||||
|
||||
/* High-pass generated from removing low-passed output. */
|
||||
hpout[i] = hp_y - lp_y;
|
||||
}
|
||||
splitter->lp_z1 = lp_z1;
|
||||
splitter->lp_z2 = lp_z2;
|
||||
splitter->hp_z1 = hp_z1;
|
||||
}
|
||||
|
||||
|
||||
void splitterap_init(SplitterAllpass *splitter, ALfloat f0norm)
|
||||
{
|
||||
ALfloat w = f0norm * F_TAU;
|
||||
ALfloat cw = cosf(w);
|
||||
if(cw > FLT_EPSILON)
|
||||
splitter->coeff = (sinf(w) - 1.0f) / cw;
|
||||
else
|
||||
splitter->coeff = cw * -0.5f;
|
||||
|
||||
splitter->z1 = 0.0f;
|
||||
}
|
||||
|
||||
void splitterap_clear(SplitterAllpass *splitter)
|
||||
{
|
||||
splitter->z1 = 0.0f;
|
||||
}
|
||||
|
||||
void splitterap_process(SplitterAllpass *splitter, ALfloat *restrict samples, ALsizei count)
|
||||
{
|
||||
ALfloat coeff, in, out;
|
||||
ALfloat z1;
|
||||
ALsizei i;
|
||||
|
||||
ASSUME(count > 0);
|
||||
|
||||
coeff = splitter->coeff;
|
||||
z1 = splitter->z1;
|
||||
for(i = 0;i < count;i++)
|
||||
{
|
||||
in = samples[i];
|
||||
|
||||
out = in*coeff + z1;
|
||||
z1 = in - out*coeff;
|
||||
|
||||
samples[i] = out;
|
||||
}
|
||||
splitter->z1 = z1;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef FILTER_SPLITTER_H
|
||||
#define FILTER_SPLITTER_H
|
||||
|
||||
#include "alMain.h"
|
||||
|
||||
|
||||
/* Band splitter. Splits a signal into two phase-matching frequency bands. */
|
||||
typedef struct BandSplitter {
|
||||
ALfloat coeff;
|
||||
ALfloat lp_z1;
|
||||
ALfloat lp_z2;
|
||||
ALfloat hp_z1;
|
||||
} BandSplitter;
|
||||
|
||||
void bandsplit_init(BandSplitter *splitter, ALfloat f0norm);
|
||||
void bandsplit_clear(BandSplitter *splitter);
|
||||
void bandsplit_process(BandSplitter *splitter, ALfloat *restrict hpout, ALfloat *restrict lpout,
|
||||
const ALfloat *input, ALsizei count);
|
||||
|
||||
/* The all-pass portion of the band splitter. Applies the same phase shift
|
||||
* without splitting the signal.
|
||||
*/
|
||||
typedef struct SplitterAllpass {
|
||||
ALfloat coeff;
|
||||
ALfloat z1;
|
||||
} SplitterAllpass;
|
||||
|
||||
void splitterap_init(SplitterAllpass *splitter, ALfloat f0norm);
|
||||
void splitterap_clear(SplitterAllpass *splitter);
|
||||
void splitterap_process(SplitterAllpass *splitter, ALfloat *restrict samples, ALsizei count);
|
||||
|
||||
|
||||
typedef struct FrontStablizer {
|
||||
SplitterAllpass APFilter[MAX_OUTPUT_CHANNELS];
|
||||
BandSplitter LFilter, RFilter;
|
||||
alignas(16) ALfloat LSplit[2][BUFFERSIZE];
|
||||
alignas(16) ALfloat RSplit[2][BUFFERSIZE];
|
||||
} FrontStablizer;
|
||||
|
||||
#endif /* FILTER_SPLITTER_H */
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef FPU_MODES_H
|
||||
#define FPU_MODES_H
|
||||
|
||||
#ifdef HAVE_FENV_H
|
||||
#include <fenv.h>
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct FPUCtl {
|
||||
#if defined(__GNUC__) && defined(HAVE_SSE)
|
||||
unsigned int sse_state;
|
||||
#elif defined(HAVE___CONTROL87_2)
|
||||
unsigned int state;
|
||||
unsigned int sse_state;
|
||||
#elif defined(HAVE__CONTROLFP)
|
||||
unsigned int state;
|
||||
#endif
|
||||
} FPUCtl;
|
||||
void SetMixerFPUMode(FPUCtl *ctl);
|
||||
void RestoreFPUMode(const FPUCtl *ctl);
|
||||
|
||||
#ifdef __GNUC__
|
||||
/* Use an alternate macro set with GCC to avoid accidental continue or break
|
||||
* statements within the mixer mode.
|
||||
*/
|
||||
#define START_MIXER_MODE() __extension__({ FPUCtl _oldMode; SetMixerFPUMode(&_oldMode)
|
||||
#define END_MIXER_MODE() RestoreFPUMode(&_oldMode); })
|
||||
#else
|
||||
#define START_MIXER_MODE() do { FPUCtl _oldMode; SetMixerFPUMode(&_oldMode)
|
||||
#define END_MIXER_MODE() RestoreFPUMode(&_oldMode); } while(0)
|
||||
#endif
|
||||
#define LEAVE_MIXER_MODE() RestoreFPUMode(&_oldMode)
|
||||
|
||||
#endif /* FPU_MODES_H */
|
||||
+1189
File diff suppressed because it is too large
Load Diff
+1465
File diff suppressed because it is too large
Load Diff
+84
@@ -0,0 +1,84 @@
|
||||
#ifndef ALC_HRTF_H
|
||||
#define ALC_HRTF_H
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alstring.h"
|
||||
#include "atomic.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 HrtfEntry;
|
||||
|
||||
struct Hrtf {
|
||||
RefCount ref;
|
||||
|
||||
ALuint sampleRate;
|
||||
ALsizei irSize;
|
||||
|
||||
ALfloat distance;
|
||||
ALubyte evCount;
|
||||
|
||||
const ALubyte *azCount;
|
||||
const ALushort *evOffset;
|
||||
const ALfloat (*coeffs)[2];
|
||||
const ALubyte (*delays)[2];
|
||||
};
|
||||
|
||||
|
||||
typedef struct HrtfState {
|
||||
alignas(16) ALfloat History[HRTF_HISTORY_LENGTH];
|
||||
alignas(16) ALfloat Values[HRIR_LENGTH][2];
|
||||
} HrtfState;
|
||||
|
||||
typedef struct HrtfParams {
|
||||
alignas(16) ALfloat Coeffs[HRIR_LENGTH][2];
|
||||
ALsizei Delay[2];
|
||||
ALfloat Gain;
|
||||
} HrtfParams;
|
||||
|
||||
typedef struct DirectHrtfState {
|
||||
/* HRTF filter state for dry buffer content */
|
||||
ALsizei Offset;
|
||||
ALsizei IrSize;
|
||||
struct {
|
||||
alignas(16) ALfloat Values[HRIR_LENGTH][2];
|
||||
alignas(16) ALfloat Coeffs[HRIR_LENGTH][2];
|
||||
} Chan[];
|
||||
} DirectHrtfState;
|
||||
|
||||
struct AngularPoint {
|
||||
ALfloat Elev;
|
||||
ALfloat Azim;
|
||||
};
|
||||
|
||||
|
||||
void FreeHrtfs(void);
|
||||
|
||||
vector_EnumeratedHrtf EnumerateHrtf(const_al_string devname);
|
||||
void FreeHrtfList(vector_EnumeratedHrtf *list);
|
||||
struct Hrtf *GetLoadedHrtf(struct HrtfEntry *entry);
|
||||
void Hrtf_IncRef(struct Hrtf *hrtf);
|
||||
void Hrtf_DecRef(struct Hrtf *hrtf);
|
||||
|
||||
void GetHrtfCoeffs(const struct Hrtf *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat spread, ALfloat (*coeffs)[2], ALsizei *delays);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void BuildBFormatHrtf(const struct Hrtf *Hrtf, DirectHrtfState *state, ALsizei NumChannels, const struct AngularPoint *AmbiPoints, const ALfloat (*restrict AmbiMatrix)[MAX_AMBI_COEFFS], ALsizei AmbiCount, const ALfloat *restrict AmbiOrderHFGain);
|
||||
|
||||
#endif /* ALC_HRTF_H */
|
||||
@@ -0,0 +1,87 @@
|
||||
#ifndef INPROGEXT_H
|
||||
#define INPROGEXT_H
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef ALC_SOFT_loopback2
|
||||
#define ALC_SOFT_loopback2 1
|
||||
#define ALC_AMBISONIC_LAYOUT_SOFT 0xfff0
|
||||
#define ALC_AMBISONIC_SCALING_SOFT 0xfff1
|
||||
#define ALC_AMBISONIC_ORDER_SOFT 0xfff2
|
||||
#define ALC_MAX_AMBISONIC_ORDER_SOFT 0xfff3
|
||||
|
||||
#define ALC_BFORMAT3D_SOFT 0x1508
|
||||
|
||||
/* Ambisonic layouts */
|
||||
#define ALC_ACN_SOFT 0xfff4
|
||||
#define ALC_FUMA_SOFT 0xfff5
|
||||
|
||||
/* Ambisonic scalings (normalization) */
|
||||
/*#define ALC_FUMA_SOFT*/
|
||||
#define ALC_SN3D_SOFT 0xfff6
|
||||
#define ALC_N3D_SOFT 0xfff7
|
||||
#endif
|
||||
|
||||
#ifndef AL_SOFT_map_buffer
|
||||
#define AL_SOFT_map_buffer 1
|
||||
typedef unsigned int ALbitfieldSOFT;
|
||||
#define AL_MAP_READ_BIT_SOFT 0x00000001
|
||||
#define AL_MAP_WRITE_BIT_SOFT 0x00000002
|
||||
#define AL_MAP_PERSISTENT_BIT_SOFT 0x00000004
|
||||
#define AL_PRESERVE_DATA_BIT_SOFT 0x00000008
|
||||
typedef void (AL_APIENTRY*LPALBUFFERSTORAGESOFT)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags);
|
||||
typedef void* (AL_APIENTRY*LPALMAPBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access);
|
||||
typedef void (AL_APIENTRY*LPALUNMAPBUFFERSOFT)(ALuint buffer);
|
||||
typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length);
|
||||
#ifdef AL_ALEXT_PROTOTYPES
|
||||
AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags);
|
||||
AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access);
|
||||
AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer);
|
||||
AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length);
|
||||
#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
|
||||
#endif
|
||||
|
||||
#ifndef AL_SOFT_buffer_layers
|
||||
#define AL_SOFT_buffer_layers
|
||||
typedef void (AL_APIENTRY*LPALSOURCEQUEUEBUFFERLAYERSSOFT)(ALuint src, ALsizei nb, const ALuint *buffers);
|
||||
#ifdef AL_ALEXT_PROTOTYPES
|
||||
AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, const ALuint *buffers);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* INPROGEXT_H */
|
||||
@@ -0,0 +1,69 @@
|
||||
#ifndef LOGGING_H
|
||||
#define LOGGING_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define DECL_FORMAT(x, y, z) __attribute__((format(x, (y), (z))))
|
||||
#else
|
||||
#define DECL_FORMAT(x, y, z)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern FILE *LogFile;
|
||||
|
||||
#if defined(__GNUC__) && !defined(_WIN32)
|
||||
#define AL_PRINT(T, MSG, ...) fprintf(LogFile, "AL lib: %s %s: "MSG, T, __FUNCTION__ , ## __VA_ARGS__)
|
||||
#else
|
||||
void al_print(const char *type, const char *func, const char *fmt, ...) DECL_FORMAT(printf, 3,4);
|
||||
#define AL_PRINT(T, ...) al_print((T), __FUNCTION__, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#define LOG_ANDROID(T, MSG, ...) __android_log_print(T, "openal", "AL lib: %s: "MSG, __FUNCTION__ , ## __VA_ARGS__)
|
||||
#else
|
||||
#define LOG_ANDROID(T, MSG, ...) ((void)0)
|
||||
#endif
|
||||
|
||||
enum LogLevel {
|
||||
NoLog,
|
||||
LogError,
|
||||
LogWarning,
|
||||
LogTrace,
|
||||
LogRef
|
||||
};
|
||||
extern enum LogLevel LogLevel;
|
||||
|
||||
#define TRACEREF(...) do { \
|
||||
if(LogLevel >= LogRef) \
|
||||
AL_PRINT("(--)", __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define TRACE(...) do { \
|
||||
if(LogLevel >= LogTrace) \
|
||||
AL_PRINT("(II)", __VA_ARGS__); \
|
||||
LOG_ANDROID(ANDROID_LOG_DEBUG, __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define WARN(...) do { \
|
||||
if(LogLevel >= LogWarning) \
|
||||
AL_PRINT("(WW)", __VA_ARGS__); \
|
||||
LOG_ANDROID(ANDROID_LOG_WARN, __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define ERR(...) do { \
|
||||
if(LogLevel >= LogError) \
|
||||
AL_PRINT("(EE)", __VA_ARGS__); \
|
||||
LOG_ANDROID(ANDROID_LOG_ERROR, __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* LOGGING_H */
|
||||
+543
@@ -0,0 +1,543 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "mastering.h"
|
||||
#include "alu.h"
|
||||
#include "almalloc.h"
|
||||
#include "static_assert.h"
|
||||
#include "math_defs.h"
|
||||
|
||||
|
||||
/* Early MSVC lacks round/roundf */
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1800
|
||||
static double round(double val)
|
||||
{
|
||||
if(val < 0.0)
|
||||
return ceil(val-0.5);
|
||||
return floor(val+0.5);
|
||||
}
|
||||
#define roundf(f) ((float)round((float)(f)))
|
||||
#endif
|
||||
|
||||
|
||||
/* These structures assume BUFFERSIZE is a power of 2. */
|
||||
static_assert((BUFFERSIZE & (BUFFERSIZE-1)) == 0, "BUFFERSIZE is not a power of 2");
|
||||
|
||||
typedef struct SlidingHold {
|
||||
ALfloat Values[BUFFERSIZE];
|
||||
ALsizei Expiries[BUFFERSIZE];
|
||||
ALsizei LowerIndex;
|
||||
ALsizei UpperIndex;
|
||||
ALsizei Length;
|
||||
} 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/
|
||||
*/
|
||||
typedef struct Compressor {
|
||||
ALsizei NumChans;
|
||||
ALuint SampleRate;
|
||||
|
||||
struct {
|
||||
ALuint Knee : 1;
|
||||
ALuint Attack : 1;
|
||||
ALuint Release : 1;
|
||||
ALuint PostGain : 1;
|
||||
ALuint Declip : 1;
|
||||
} Auto;
|
||||
|
||||
ALsizei LookAhead;
|
||||
|
||||
ALfloat PreGain;
|
||||
ALfloat PostGain;
|
||||
|
||||
ALfloat Threshold;
|
||||
ALfloat Slope;
|
||||
ALfloat Knee;
|
||||
|
||||
ALfloat Attack;
|
||||
ALfloat Release;
|
||||
|
||||
alignas(16) ALfloat SideChain[2*BUFFERSIZE];
|
||||
alignas(16) ALfloat CrestFactor[BUFFERSIZE];
|
||||
|
||||
SlidingHold *Hold;
|
||||
ALfloat (*Delay)[BUFFERSIZE];
|
||||
ALsizei DelayIndex;
|
||||
|
||||
ALfloat CrestCoeff;
|
||||
ALfloat GainEstimate;
|
||||
ALfloat AdaptCoeff;
|
||||
|
||||
ALfloat LastPeakSq;
|
||||
ALfloat LastRmsSq;
|
||||
ALfloat LastRelease;
|
||||
ALfloat LastAttack;
|
||||
ALfloat LastGainDev;
|
||||
} Compressor;
|
||||
|
||||
|
||||
/* This sliding hold follows the input level with an instant attack and a
|
||||
* fixed duration hold before an instant release to the next highest level.
|
||||
* It is a sliding window maximum (descending maxima) implementation based on
|
||||
* Richard Harter's ascending minima algorithm available at:
|
||||
*
|
||||
* http://www.richardhartersworld.com/cri/2001/slidingmin.html
|
||||
*/
|
||||
static ALfloat UpdateSlidingHold(SlidingHold *Hold, const ALsizei i, const ALfloat in)
|
||||
{
|
||||
const ALsizei mask = BUFFERSIZE - 1;
|
||||
const ALsizei length = Hold->Length;
|
||||
ALfloat *restrict values = Hold->Values;
|
||||
ALsizei *restrict expiries = Hold->Expiries;
|
||||
ALsizei lowerIndex = Hold->LowerIndex;
|
||||
ALsizei upperIndex = Hold->UpperIndex;
|
||||
|
||||
if(i >= expiries[upperIndex])
|
||||
upperIndex = (upperIndex + 1) & mask;
|
||||
|
||||
if(in >= values[upperIndex])
|
||||
{
|
||||
values[upperIndex] = in;
|
||||
expiries[upperIndex] = i + length;
|
||||
lowerIndex = upperIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
do {
|
||||
do {
|
||||
if(!(in >= values[lowerIndex]))
|
||||
goto found_place;
|
||||
} while(lowerIndex--);
|
||||
lowerIndex = mask;
|
||||
} while(1);
|
||||
found_place:
|
||||
|
||||
lowerIndex = (lowerIndex + 1) & mask;
|
||||
values[lowerIndex] = in;
|
||||
expiries[lowerIndex] = i + length;
|
||||
}
|
||||
|
||||
Hold->LowerIndex = lowerIndex;
|
||||
Hold->UpperIndex = upperIndex;
|
||||
|
||||
return values[upperIndex];
|
||||
}
|
||||
|
||||
static void ShiftSlidingHold(SlidingHold *Hold, const ALsizei n)
|
||||
{
|
||||
const ALsizei lowerIndex = Hold->LowerIndex;
|
||||
ALsizei *restrict expiries = Hold->Expiries;
|
||||
ALsizei i = Hold->UpperIndex;
|
||||
|
||||
if(lowerIndex < i)
|
||||
{
|
||||
for(;i < BUFFERSIZE;i++)
|
||||
expiries[i] -= n;
|
||||
i = 0;
|
||||
}
|
||||
for(;i < lowerIndex;i++)
|
||||
expiries[i] -= n;
|
||||
|
||||
expiries[i] -= n;
|
||||
}
|
||||
|
||||
/* Multichannel compression is linked via the absolute maximum of all
|
||||
* channels.
|
||||
*/
|
||||
static void LinkChannels(Compressor *Comp, const ALsizei SamplesToDo, ALfloat (*restrict OutBuffer)[BUFFERSIZE])
|
||||
{
|
||||
const ALsizei index = Comp->LookAhead;
|
||||
const ALsizei numChans = Comp->NumChans;
|
||||
ALfloat *restrict sideChain = Comp->SideChain;
|
||||
ALsizei c, i;
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(numChans > 0);
|
||||
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
sideChain[index + i] = 0.0f;
|
||||
|
||||
for(c = 0;c < numChans;c++)
|
||||
{
|
||||
ALsizei offset = index;
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
{
|
||||
sideChain[offset] = maxf(sideChain[offset], fabsf(OutBuffer[c][i]));
|
||||
++offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This calculates the squared crest factor of the control signal for the
|
||||
* basic automation of the attack/release times. As suggested by the paper,
|
||||
* it uses an instantaneous squared peak detector and a squared RMS detector
|
||||
* both with 200ms release times.
|
||||
*/
|
||||
static void CrestDetector(Compressor *Comp, const ALsizei SamplesToDo)
|
||||
{
|
||||
const ALfloat a_crest = Comp->CrestCoeff;
|
||||
const ALsizei index = Comp->LookAhead;
|
||||
const ALfloat *restrict sideChain = Comp->SideChain;
|
||||
ALfloat *restrict crestFactor = Comp->CrestFactor;
|
||||
ALfloat y2_peak = Comp->LastPeakSq;
|
||||
ALfloat y2_rms = Comp->LastRmsSq;
|
||||
ALsizei i;
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
{
|
||||
ALfloat x_abs = sideChain[index + i];
|
||||
ALfloat x2 = maxf(0.000001f, x_abs * x_abs);
|
||||
|
||||
y2_peak = maxf(x2, lerp(x2, y2_peak, a_crest));
|
||||
y2_rms = lerp(x2, y2_rms, a_crest);
|
||||
crestFactor[i] = y2_peak / y2_rms;
|
||||
}
|
||||
|
||||
Comp->LastPeakSq = y2_peak;
|
||||
Comp->LastRmsSq = y2_rms;
|
||||
}
|
||||
|
||||
/* The side-chain starts with a simple peak detector (based on the absolute
|
||||
* value of the incoming signal) and performs most of its operations in the
|
||||
* log domain.
|
||||
*/
|
||||
static void PeakDetector(Compressor *Comp, const ALsizei SamplesToDo)
|
||||
{
|
||||
const ALsizei index = Comp->LookAhead;
|
||||
ALfloat *restrict sideChain = Comp->SideChain;
|
||||
ALsizei i;
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
{
|
||||
const ALuint offset = index + i;
|
||||
const ALfloat x_abs = sideChain[offset];
|
||||
|
||||
sideChain[offset] = logf(maxf(0.000001f, x_abs));
|
||||
}
|
||||
}
|
||||
|
||||
/* An optional hold can be used to extend the peak detector so it can more
|
||||
* solidly detect fast transients. This is best used when operating as a
|
||||
* limiter.
|
||||
*/
|
||||
static void PeakHoldDetector(Compressor *Comp, const ALsizei SamplesToDo)
|
||||
{
|
||||
const ALsizei index = Comp->LookAhead;
|
||||
ALfloat *restrict sideChain = Comp->SideChain;
|
||||
SlidingHold *hold = Comp->Hold;
|
||||
ALsizei i;
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
{
|
||||
const ALsizei offset = index + i;
|
||||
const ALfloat x_abs = sideChain[offset];
|
||||
const ALfloat x_G = logf(maxf(0.000001f, x_abs));
|
||||
|
||||
sideChain[offset] = UpdateSlidingHold(hold, i, x_G);
|
||||
}
|
||||
|
||||
ShiftSlidingHold(hold, SamplesToDo);
|
||||
}
|
||||
|
||||
/* This is the heart of the feed-forward compressor. It operates in the log
|
||||
* domain (to better match human hearing) and can apply some basic automation
|
||||
* to knee width, attack/release times, make-up/post gain, and clipping
|
||||
* reduction.
|
||||
*/
|
||||
static void GainCompressor(Compressor *Comp, const ALsizei SamplesToDo)
|
||||
{
|
||||
const bool autoKnee = Comp->Auto.Knee;
|
||||
const bool autoAttack = Comp->Auto.Attack;
|
||||
const bool autoRelease = Comp->Auto.Release;
|
||||
const bool autoPostGain = Comp->Auto.PostGain;
|
||||
const bool autoDeclip = Comp->Auto.Declip;
|
||||
const ALsizei lookAhead = Comp->LookAhead;
|
||||
const ALfloat threshold = Comp->Threshold;
|
||||
const ALfloat slope = Comp->Slope;
|
||||
const ALfloat attack = Comp->Attack;
|
||||
const ALfloat release = Comp->Release;
|
||||
const ALfloat c_est = Comp->GainEstimate;
|
||||
const ALfloat a_adp = Comp->AdaptCoeff;
|
||||
const ALfloat *restrict crestFactor = Comp->CrestFactor;
|
||||
ALfloat *restrict sideChain = Comp->SideChain;
|
||||
ALfloat postGain = Comp->PostGain;
|
||||
ALfloat knee = Comp->Knee;
|
||||
ALfloat t_att = attack;
|
||||
ALfloat t_rel = release - attack;
|
||||
ALfloat a_att = expf(-1.0f / t_att);
|
||||
ALfloat a_rel = expf(-1.0f / t_rel);
|
||||
ALfloat y_1 = Comp->LastRelease;
|
||||
ALfloat y_L = Comp->LastAttack;
|
||||
ALfloat c_dev = Comp->LastGainDev;
|
||||
ALsizei i;
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
{
|
||||
const ALfloat y2_crest = crestFactor[i];
|
||||
const ALfloat x_G = sideChain[lookAhead + i];
|
||||
const ALfloat x_over = x_G - threshold;
|
||||
ALfloat knee_h;
|
||||
ALfloat y_G;
|
||||
ALfloat x_L;
|
||||
|
||||
if(autoKnee)
|
||||
knee = maxf(0.0f, 2.5f * (c_dev + c_est));
|
||||
knee_h = 0.5f * knee;
|
||||
|
||||
/* This is the gain computer. It applies a static compression curve
|
||||
* to the control signal.
|
||||
*/
|
||||
if(x_over <= -knee_h)
|
||||
y_G = 0.0f;
|
||||
else if(fabsf(x_over) < knee_h)
|
||||
y_G = (x_over + knee_h) * (x_over + knee_h) / (2.0f * knee);
|
||||
else
|
||||
y_G = x_over;
|
||||
|
||||
x_L = -slope * y_G;
|
||||
|
||||
if(autoAttack)
|
||||
{
|
||||
t_att = 2.0f * attack / y2_crest;
|
||||
a_att = expf(-1.0f / t_att);
|
||||
}
|
||||
|
||||
if(autoRelease)
|
||||
{
|
||||
t_rel = 2.0f * release / y2_crest - t_att;
|
||||
a_rel = expf(-1.0f / t_rel);
|
||||
}
|
||||
|
||||
/* Gain smoothing (ballistics) is done via a smooth decoupled peak
|
||||
* detector. The attack time is subtracted from the release time
|
||||
* above to compensate for the chained operating mode.
|
||||
*/
|
||||
y_1 = maxf(x_L, lerp(x_L, y_1, a_rel));
|
||||
y_L = lerp(y_1, y_L, a_att);
|
||||
|
||||
/* Knee width and make-up gain automation make use of a smoothed
|
||||
* measurement of deviation between the control signal and estimate.
|
||||
* The estimate is also used to bias the measurement to hot-start its
|
||||
* average.
|
||||
*/
|
||||
c_dev = lerp(-y_L - c_est, c_dev, a_adp);
|
||||
|
||||
if(autoPostGain)
|
||||
{
|
||||
/* Clipping reduction is only viable when make-up gain is being
|
||||
* automated. It modifies the deviation to further attenuate the
|
||||
* control signal when clipping is detected. The adaptation
|
||||
* time is sufficiently long enough to suppress further clipping
|
||||
* at the same output level.
|
||||
*/
|
||||
if(autoDeclip)
|
||||
c_dev = maxf(c_dev, sideChain[i] - y_L - threshold - c_est);
|
||||
|
||||
postGain = -(c_dev + c_est);
|
||||
}
|
||||
|
||||
sideChain[i] = expf(postGain - y_L);
|
||||
}
|
||||
|
||||
Comp->LastRelease = y_1;
|
||||
Comp->LastAttack = y_L;
|
||||
Comp->LastGainDev = c_dev;
|
||||
}
|
||||
|
||||
/* Combined with the hold time, a look-ahead delay can improve handling of
|
||||
* fast transients by allowing the envelope time to converge prior to
|
||||
* reaching the offending impulse. This is best used when operating as a
|
||||
* limiter.
|
||||
*/
|
||||
static void SignalDelay(Compressor *Comp, const ALsizei SamplesToDo, ALfloat (*restrict OutBuffer)[BUFFERSIZE])
|
||||
{
|
||||
const ALsizei mask = BUFFERSIZE - 1;
|
||||
const ALsizei numChans = Comp->NumChans;
|
||||
const ALsizei indexIn = Comp->DelayIndex;
|
||||
const ALsizei indexOut = Comp->DelayIndex - Comp->LookAhead;
|
||||
ALfloat (*restrict delay)[BUFFERSIZE] = Comp->Delay;
|
||||
ALsizei c, i;
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(numChans > 0);
|
||||
|
||||
for(c = 0;c < numChans;c++)
|
||||
{
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
{
|
||||
ALfloat sig = OutBuffer[c][i];
|
||||
|
||||
OutBuffer[c][i] = delay[c][(indexOut + i) & mask];
|
||||
delay[c][(indexIn + i) & mask] = sig;
|
||||
}
|
||||
}
|
||||
|
||||
Comp->DelayIndex = (indexIn + SamplesToDo) & mask;
|
||||
}
|
||||
|
||||
/* 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 INFINITY 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.
|
||||
*/
|
||||
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)
|
||||
{
|
||||
Compressor *Comp;
|
||||
ALsizei lookAhead;
|
||||
ALsizei hold;
|
||||
size_t size;
|
||||
|
||||
lookAhead = (ALsizei)clampf(roundf(LookAheadTime*SampleRate), 0.0f, BUFFERSIZE-1);
|
||||
hold = (ALsizei)clampf(roundf(HoldTime*SampleRate), 0.0f, BUFFERSIZE-1);
|
||||
/* The sliding hold implementation doesn't handle a length of 1. A 1-sample
|
||||
* hold is useless anyway, it would only ever give back what was just given
|
||||
* to it.
|
||||
*/
|
||||
if(hold == 1)
|
||||
hold = 0;
|
||||
|
||||
size = sizeof(*Comp);
|
||||
if(lookAhead > 0)
|
||||
{
|
||||
size += sizeof(*Comp->Delay) * NumChans;
|
||||
if(hold > 0)
|
||||
size += sizeof(*Comp->Hold);
|
||||
}
|
||||
|
||||
Comp = al_calloc(16, size);
|
||||
Comp->NumChans = NumChans;
|
||||
Comp->SampleRate = SampleRate;
|
||||
Comp->Auto.Knee = AutoKnee;
|
||||
Comp->Auto.Attack = AutoAttack;
|
||||
Comp->Auto.Release = AutoRelease;
|
||||
Comp->Auto.PostGain = AutoPostGain;
|
||||
Comp->Auto.Declip = AutoPostGain && AutoDeclip;
|
||||
Comp->LookAhead = lookAhead;
|
||||
Comp->PreGain = powf(10.0f, PreGainDb / 20.0f);
|
||||
Comp->PostGain = PostGainDb * logf(10.0f) / 20.0f;
|
||||
Comp->Threshold = ThresholdDb * logf(10.0f) / 20.0f;
|
||||
Comp->Slope = 1.0f / maxf(1.0f, Ratio) - 1.0f;
|
||||
Comp->Knee = maxf(0.0f, KneeDb * logf(10.0f) / 20.0f);
|
||||
Comp->Attack = maxf(1.0f, AttackTime * SampleRate);
|
||||
Comp->Release = maxf(1.0f, ReleaseTime * SampleRate);
|
||||
|
||||
/* Knee width automation actually treats the compressor as a limiter. By
|
||||
* varying the knee width, it can effectively be seen as applying
|
||||
* compression over a wide range of ratios.
|
||||
*/
|
||||
if(AutoKnee)
|
||||
Comp->Slope = -1.0f;
|
||||
|
||||
if(lookAhead > 0)
|
||||
{
|
||||
if(hold > 0)
|
||||
{
|
||||
Comp->Hold = (SlidingHold*)(Comp + 1);
|
||||
Comp->Hold->Values[0] = -HUGE_VALF;
|
||||
Comp->Hold->Expiries[0] = hold;
|
||||
Comp->Hold->Length = hold;
|
||||
Comp->Delay = (ALfloat(*)[BUFFERSIZE])(Comp->Hold + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Comp->Delay = (ALfloat(*)[BUFFERSIZE])(Comp + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Comp->CrestCoeff = expf(-1.0f / (0.200f * SampleRate)); // 200ms
|
||||
Comp->GainEstimate = Comp->Threshold * -0.5f * Comp->Slope;
|
||||
Comp->AdaptCoeff = expf(-1.0f / (2.0f * SampleRate)); // 2s
|
||||
|
||||
return Comp;
|
||||
}
|
||||
|
||||
void ApplyCompression(Compressor *Comp, const ALsizei SamplesToDo, ALfloat (*restrict OutBuffer)[BUFFERSIZE])
|
||||
{
|
||||
const ALsizei numChans = Comp->NumChans;
|
||||
const ALfloat preGain = Comp->PreGain;
|
||||
ALfloat *restrict sideChain;
|
||||
ALsizei c, i;
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
ASSUME(numChans > 0);
|
||||
|
||||
if(preGain != 1.0f)
|
||||
{
|
||||
for(c = 0;c < numChans;c++)
|
||||
{
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
OutBuffer[c][i] *= preGain;
|
||||
}
|
||||
}
|
||||
|
||||
LinkChannels(Comp, SamplesToDo, OutBuffer);
|
||||
|
||||
if(Comp->Auto.Attack || Comp->Auto.Release)
|
||||
CrestDetector(Comp, SamplesToDo);
|
||||
|
||||
if(Comp->Hold)
|
||||
PeakHoldDetector(Comp, SamplesToDo);
|
||||
else
|
||||
PeakDetector(Comp, SamplesToDo);
|
||||
|
||||
GainCompressor(Comp, SamplesToDo);
|
||||
|
||||
if(Comp->Delay)
|
||||
SignalDelay(Comp, SamplesToDo, OutBuffer);
|
||||
|
||||
sideChain = Comp->SideChain;
|
||||
for(c = 0;c < numChans;c++)
|
||||
{
|
||||
for(i = 0;i < SamplesToDo;i++)
|
||||
OutBuffer[c][i] *= sideChain[i];
|
||||
}
|
||||
|
||||
memmove(sideChain, sideChain+SamplesToDo, Comp->LookAhead*sizeof(ALfloat));
|
||||
}
|
||||
|
||||
|
||||
ALsizei GetCompressorLookAhead(const Compressor *Comp)
|
||||
{ return Comp->LookAhead; }
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef MASTERING_H
|
||||
#define MASTERING_H
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
/* For BUFFERSIZE. */
|
||||
#include "alMain.h"
|
||||
|
||||
struct Compressor;
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
struct 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);
|
||||
|
||||
void ApplyCompression(struct Compressor *Comp, const ALsizei SamplesToDo,
|
||||
ALfloat (*restrict OutBuffer)[BUFFERSIZE]);
|
||||
|
||||
ALsizei GetCompressorLookAhead(const struct Compressor *Comp);
|
||||
|
||||
#endif /* MASTERING_H */
|
||||
@@ -0,0 +1,119 @@
|
||||
#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;
|
||||
|
||||
/* C resamplers */
|
||||
const ALfloat *Resample_copy_C(const InterpState *state, const ALfloat *restrict src, ALsizei frac, ALint increment, ALfloat *restrict dst, ALsizei dstlen);
|
||||
const ALfloat *Resample_point_C(const InterpState *state, const ALfloat *restrict src, ALsizei frac, ALint increment, ALfloat *restrict dst, ALsizei dstlen);
|
||||
const ALfloat *Resample_lerp_C(const InterpState *state, const ALfloat *restrict src, ALsizei frac, ALint increment, ALfloat *restrict dst, ALsizei dstlen);
|
||||
const ALfloat *Resample_cubic_C(const InterpState *state, const ALfloat *restrict src, ALsizei frac, ALint increment, ALfloat *restrict dst, ALsizei dstlen);
|
||||
const ALfloat *Resample_bsinc_C(const InterpState *state, const ALfloat *restrict src, ALsizei frac, ALint increment, ALfloat *restrict dst, ALsizei dstlen);
|
||||
|
||||
|
||||
/* C mixers */
|
||||
void MixHrtf_C(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, ALsizei OutPos,
|
||||
const ALsizei IrSize, struct MixHrtfParams *hrtfparams,
|
||||
struct HrtfState *hrtfstate, ALsizei BufferSize);
|
||||
void MixHrtfBlend_C(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, ALsizei OutPos,
|
||||
const ALsizei IrSize, const HrtfParams *oldparams,
|
||||
MixHrtfParams *newparams, HrtfState *hrtfstate,
|
||||
ALsizei BufferSize);
|
||||
void MixDirectHrtf_C(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, const ALsizei IrSize,
|
||||
const ALfloat (*restrict Coeffs)[2], ALfloat (*restrict Values)[2],
|
||||
ALsizei BufferSize);
|
||||
void Mix_C(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
|
||||
ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
|
||||
ALsizei BufferSize);
|
||||
void MixRow_C(ALfloat *OutBuffer, const ALfloat *Gains,
|
||||
const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans,
|
||||
ALsizei InPos, ALsizei BufferSize);
|
||||
|
||||
/* SSE mixers */
|
||||
void MixHrtf_SSE(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, ALsizei OutPos,
|
||||
const ALsizei IrSize, struct MixHrtfParams *hrtfparams,
|
||||
struct HrtfState *hrtfstate, ALsizei BufferSize);
|
||||
void MixHrtfBlend_SSE(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, ALsizei OutPos,
|
||||
const ALsizei IrSize, const HrtfParams *oldparams,
|
||||
MixHrtfParams *newparams, HrtfState *hrtfstate,
|
||||
ALsizei BufferSize);
|
||||
void MixDirectHrtf_SSE(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, const ALsizei IrSize,
|
||||
const ALfloat (*restrict Coeffs)[2], ALfloat (*restrict Values)[2],
|
||||
ALsizei BufferSize);
|
||||
void Mix_SSE(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
|
||||
ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
|
||||
ALsizei BufferSize);
|
||||
void MixRow_SSE(ALfloat *OutBuffer, const ALfloat *Gains,
|
||||
const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans,
|
||||
ALsizei InPos, ALsizei BufferSize);
|
||||
|
||||
/* SSE resamplers */
|
||||
inline void InitiatePositionArrays(ALsizei frac, ALint increment, ALsizei *restrict frac_arr, ALsizei *restrict pos_arr, ALsizei size)
|
||||
{
|
||||
ALsizei i;
|
||||
|
||||
pos_arr[0] = 0;
|
||||
frac_arr[0] = frac;
|
||||
for(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;
|
||||
}
|
||||
}
|
||||
|
||||
const ALfloat *Resample_lerp_SSE2(const InterpState *state, const ALfloat *restrict src,
|
||||
ALsizei frac, ALint increment, ALfloat *restrict dst,
|
||||
ALsizei numsamples);
|
||||
const ALfloat *Resample_lerp_SSE41(const InterpState *state, const ALfloat *restrict src,
|
||||
ALsizei frac, ALint increment, ALfloat *restrict dst,
|
||||
ALsizei numsamples);
|
||||
|
||||
const ALfloat *Resample_bsinc_SSE(const InterpState *state, const ALfloat *restrict src,
|
||||
ALsizei frac, ALint increment, ALfloat *restrict dst,
|
||||
ALsizei dstlen);
|
||||
|
||||
/* Neon mixers */
|
||||
void MixHrtf_Neon(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, ALsizei OutPos,
|
||||
const ALsizei IrSize, struct MixHrtfParams *hrtfparams,
|
||||
struct HrtfState *hrtfstate, ALsizei BufferSize);
|
||||
void MixHrtfBlend_Neon(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, ALsizei OutPos,
|
||||
const ALsizei IrSize, const HrtfParams *oldparams,
|
||||
MixHrtfParams *newparams, HrtfState *hrtfstate,
|
||||
ALsizei BufferSize);
|
||||
void MixDirectHrtf_Neon(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, const ALsizei IrSize,
|
||||
const ALfloat (*restrict Coeffs)[2], ALfloat (*restrict Values)[2],
|
||||
ALsizei BufferSize);
|
||||
void Mix_Neon(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
|
||||
ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
|
||||
ALsizei BufferSize);
|
||||
void MixRow_Neon(ALfloat *OutBuffer, const ALfloat *Gains,
|
||||
const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans,
|
||||
ALsizei InPos, ALsizei BufferSize);
|
||||
|
||||
/* Neon resamplers */
|
||||
const ALfloat *Resample_lerp_Neon(const InterpState *state, const ALfloat *restrict src,
|
||||
ALsizei frac, ALint increment, ALfloat *restrict dst,
|
||||
ALsizei numsamples);
|
||||
const ALfloat *Resample_bsinc_Neon(const InterpState *state, const ALfloat *restrict src,
|
||||
ALsizei frac, ALint increment, ALfloat *restrict dst,
|
||||
ALsizei dstlen);
|
||||
|
||||
#endif /* MIXER_DEFS_H */
|
||||
@@ -0,0 +1,128 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alSource.h"
|
||||
|
||||
#include "hrtf.h"
|
||||
#include "align.h"
|
||||
#include "alu.h"
|
||||
#include "defs.h"
|
||||
|
||||
|
||||
static inline void ApplyCoeffs(ALsizei Offset, ALfloat (*restrict Values)[2],
|
||||
const ALsizei irSize,
|
||||
const ALfloat (*restrict Coeffs)[2],
|
||||
ALfloat left, ALfloat right);
|
||||
|
||||
|
||||
void MixHrtf(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, ALsizei OutPos,
|
||||
const ALsizei IrSize, MixHrtfParams *hrtfparams, HrtfState *hrtfstate,
|
||||
ALsizei BufferSize)
|
||||
{
|
||||
const ALfloat (*Coeffs)[2] = ASSUME_ALIGNED(hrtfparams->Coeffs, 16);
|
||||
const ALsizei Delay[2] = { hrtfparams->Delay[0], hrtfparams->Delay[1] };
|
||||
const ALfloat gainstep = hrtfparams->GainStep;
|
||||
const ALfloat gain = hrtfparams->Gain;
|
||||
ALfloat g, stepcount = 0.0f;
|
||||
ALfloat left, right;
|
||||
ALsizei i;
|
||||
|
||||
ASSUME(IrSize >= 4);
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
LeftOut += OutPos;
|
||||
RightOut += OutPos;
|
||||
for(i = 0;i < BufferSize;i++)
|
||||
{
|
||||
hrtfstate->History[Offset&HRTF_HISTORY_MASK] = *(data++);
|
||||
|
||||
g = gain + gainstep*stepcount;
|
||||
left = hrtfstate->History[(Offset-Delay[0])&HRTF_HISTORY_MASK]*g;
|
||||
right = hrtfstate->History[(Offset-Delay[1])&HRTF_HISTORY_MASK]*g;
|
||||
|
||||
hrtfstate->Values[(Offset+IrSize-1)&HRIR_MASK][0] = 0.0f;
|
||||
hrtfstate->Values[(Offset+IrSize-1)&HRIR_MASK][1] = 0.0f;
|
||||
|
||||
ApplyCoeffs(Offset, hrtfstate->Values, IrSize, Coeffs, left, right);
|
||||
*(LeftOut++) += hrtfstate->Values[Offset&HRIR_MASK][0];
|
||||
*(RightOut++) += hrtfstate->Values[Offset&HRIR_MASK][1];
|
||||
|
||||
stepcount += 1.0f;
|
||||
Offset++;
|
||||
}
|
||||
hrtfparams->Gain = gain + gainstep*stepcount;
|
||||
}
|
||||
|
||||
void MixHrtfBlend(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, ALsizei OutPos,
|
||||
const ALsizei IrSize, const HrtfParams *oldparams,
|
||||
MixHrtfParams *newparams, HrtfState *hrtfstate,
|
||||
ALsizei BufferSize)
|
||||
{
|
||||
const ALfloat (*OldCoeffs)[2] = ASSUME_ALIGNED(oldparams->Coeffs, 16);
|
||||
const ALsizei OldDelay[2] = { oldparams->Delay[0], oldparams->Delay[1] };
|
||||
const ALfloat oldGain = oldparams->Gain;
|
||||
const ALfloat oldGainStep = -oldGain / (ALfloat)BufferSize;
|
||||
const ALfloat (*NewCoeffs)[2] = ASSUME_ALIGNED(newparams->Coeffs, 16);
|
||||
const ALsizei NewDelay[2] = { newparams->Delay[0], newparams->Delay[1] };
|
||||
const ALfloat newGain = newparams->Gain;
|
||||
const ALfloat newGainStep = newparams->GainStep;
|
||||
ALfloat g, stepcount = 0.0f;
|
||||
ALfloat left, right;
|
||||
ALsizei i;
|
||||
|
||||
ASSUME(IrSize >= 4);
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
LeftOut += OutPos;
|
||||
RightOut += OutPos;
|
||||
for(i = 0;i < BufferSize;i++)
|
||||
{
|
||||
hrtfstate->Values[(Offset+IrSize-1)&HRIR_MASK][0] = 0.0f;
|
||||
hrtfstate->Values[(Offset+IrSize-1)&HRIR_MASK][1] = 0.0f;
|
||||
|
||||
hrtfstate->History[Offset&HRTF_HISTORY_MASK] = *(data++);
|
||||
|
||||
g = oldGain + oldGainStep*stepcount;
|
||||
left = hrtfstate->History[(Offset-OldDelay[0])&HRTF_HISTORY_MASK]*g;
|
||||
right = hrtfstate->History[(Offset-OldDelay[1])&HRTF_HISTORY_MASK]*g;
|
||||
ApplyCoeffs(Offset, hrtfstate->Values, IrSize, OldCoeffs, left, right);
|
||||
|
||||
g = newGain + newGainStep*stepcount;
|
||||
left = hrtfstate->History[(Offset-NewDelay[0])&HRTF_HISTORY_MASK]*g;
|
||||
right = hrtfstate->History[(Offset-NewDelay[1])&HRTF_HISTORY_MASK]*g;
|
||||
ApplyCoeffs(Offset, hrtfstate->Values, IrSize, NewCoeffs, left, right);
|
||||
|
||||
*(LeftOut++) += hrtfstate->Values[Offset&HRIR_MASK][0];
|
||||
*(RightOut++) += hrtfstate->Values[Offset&HRIR_MASK][1];
|
||||
|
||||
stepcount += 1.0f;
|
||||
Offset++;
|
||||
}
|
||||
newparams->Gain = newGain + newGainStep*stepcount;
|
||||
}
|
||||
|
||||
void MixDirectHrtf(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, const ALsizei IrSize,
|
||||
const ALfloat (*restrict Coeffs)[2], ALfloat (*restrict Values)[2],
|
||||
ALsizei BufferSize)
|
||||
{
|
||||
ALfloat insample;
|
||||
ALsizei i;
|
||||
|
||||
ASSUME(IrSize >= 4);
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
for(i = 0;i < BufferSize;i++)
|
||||
{
|
||||
Values[(Offset+IrSize)&HRIR_MASK][0] = 0.0f;
|
||||
Values[(Offset+IrSize)&HRIR_MASK][1] = 0.0f;
|
||||
Offset++;
|
||||
|
||||
insample = *(data++);
|
||||
ApplyCoeffs(Offset, Values, IrSize, Coeffs, insample, insample);
|
||||
*(LeftOut++) += Values[Offset&HRIR_MASK][0];
|
||||
*(RightOut++) += Values[Offset&HRIR_MASK][1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "alSource.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "defs.h"
|
||||
|
||||
|
||||
static inline ALfloat do_point(const InterpState* UNUSED(state), const ALfloat *restrict vals, ALsizei UNUSED(frac))
|
||||
{ return vals[0]; }
|
||||
static inline ALfloat do_lerp(const InterpState* UNUSED(state), const ALfloat *restrict vals, ALsizei frac)
|
||||
{ return lerp(vals[0], vals[1], frac * (1.0f/FRACTIONONE)); }
|
||||
static inline ALfloat do_cubic(const InterpState* UNUSED(state), const ALfloat *restrict vals, ALsizei frac)
|
||||
{ return cubic(vals[0], vals[1], vals[2], vals[3], frac * (1.0f/FRACTIONONE)); }
|
||||
static inline ALfloat do_bsinc(const InterpState *state, const ALfloat *restrict vals, ALsizei frac)
|
||||
{
|
||||
const ALfloat *fil, *scd, *phd, *spd;
|
||||
ALsizei j_f, pi;
|
||||
ALfloat pf, r;
|
||||
|
||||
ASSUME(state->bsinc.m > 0);
|
||||
|
||||
// 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
|
||||
|
||||
fil = ASSUME_ALIGNED(state->bsinc.filter + state->bsinc.m*pi*4, 16);
|
||||
scd = ASSUME_ALIGNED(fil + state->bsinc.m, 16);
|
||||
phd = ASSUME_ALIGNED(scd + state->bsinc.m, 16);
|
||||
spd = ASSUME_ALIGNED(phd + state->bsinc.m, 16);
|
||||
|
||||
// Apply the scale and phase interpolated filter.
|
||||
r = 0.0f;
|
||||
for(j_f = 0;j_f < state->bsinc.m;j_f++)
|
||||
r += (fil[j_f] + state->bsinc.sf*scd[j_f] + pf*(phd[j_f] + state->bsinc.sf*spd[j_f])) * vals[j_f];
|
||||
return r;
|
||||
}
|
||||
|
||||
const ALfloat *Resample_copy_C(const InterpState* UNUSED(state),
|
||||
const ALfloat *restrict src, ALsizei UNUSED(frac), ALint UNUSED(increment),
|
||||
ALfloat *restrict dst, ALsizei numsamples)
|
||||
{
|
||||
#if defined(HAVE_SSE) || defined(HAVE_NEON)
|
||||
/* Avoid copying the source data if it's aligned like the destination. */
|
||||
if((((intptr_t)src)&15) == (((intptr_t)dst)&15))
|
||||
return src;
|
||||
#endif
|
||||
memcpy(dst, src, numsamples*sizeof(ALfloat));
|
||||
return dst;
|
||||
}
|
||||
|
||||
#define DECL_TEMPLATE(Tag, Sampler, O) \
|
||||
const ALfloat *Resample_##Tag##_C(const InterpState *state, \
|
||||
const ALfloat *restrict src, ALsizei frac, ALint increment, \
|
||||
ALfloat *restrict dst, ALsizei numsamples) \
|
||||
{ \
|
||||
const InterpState istate = *state; \
|
||||
ALsizei i; \
|
||||
\
|
||||
ASSUME(numsamples > 0); \
|
||||
\
|
||||
src -= O; \
|
||||
for(i = 0;i < numsamples;i++) \
|
||||
{ \
|
||||
dst[i] = Sampler(&istate, src, frac); \
|
||||
\
|
||||
frac += increment; \
|
||||
src += frac>>FRACTIONBITS; \
|
||||
frac &= FRACTIONMASK; \
|
||||
} \
|
||||
return dst; \
|
||||
}
|
||||
|
||||
DECL_TEMPLATE(point, do_point, 0)
|
||||
DECL_TEMPLATE(lerp, do_lerp, 0)
|
||||
DECL_TEMPLATE(cubic, do_cubic, 1)
|
||||
DECL_TEMPLATE(bsinc, do_bsinc, istate.bsinc.l)
|
||||
|
||||
#undef DECL_TEMPLATE
|
||||
|
||||
|
||||
static inline void ApplyCoeffs(ALsizei Offset, ALfloat (*restrict Values)[2],
|
||||
const ALsizei IrSize,
|
||||
const ALfloat (*restrict Coeffs)[2],
|
||||
ALfloat left, ALfloat right)
|
||||
{
|
||||
ALsizei c;
|
||||
for(c = 0;c < IrSize;c++)
|
||||
{
|
||||
const ALsizei off = (Offset+c)&HRIR_MASK;
|
||||
Values[off][0] += Coeffs[c][0] * left;
|
||||
Values[off][1] += Coeffs[c][1] * right;
|
||||
}
|
||||
}
|
||||
|
||||
#define MixHrtf MixHrtf_C
|
||||
#define MixHrtfBlend MixHrtfBlend_C
|
||||
#define MixDirectHrtf MixDirectHrtf_C
|
||||
#include "hrtf_inc.c"
|
||||
|
||||
|
||||
void Mix_C(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
|
||||
ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
|
||||
ALsizei BufferSize)
|
||||
{
|
||||
const ALfloat delta = (Counter > 0) ? 1.0f/(ALfloat)Counter : 0.0f;
|
||||
ALsizei c;
|
||||
|
||||
ASSUME(OutChans > 0);
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
for(c = 0;c < OutChans;c++)
|
||||
{
|
||||
ALsizei pos = 0;
|
||||
ALfloat gain = CurrentGains[c];
|
||||
const ALfloat diff = TargetGains[c] - gain;
|
||||
|
||||
if(fabsf(diff) > FLT_EPSILON)
|
||||
{
|
||||
ALsizei minsize = mini(BufferSize, Counter);
|
||||
const ALfloat step = diff * delta;
|
||||
ALfloat step_count = 0.0f;
|
||||
for(;pos < minsize;pos++)
|
||||
{
|
||||
OutBuffer[c][OutPos+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(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
|
||||
continue;
|
||||
for(;pos < BufferSize;pos++)
|
||||
OutBuffer[c][OutPos+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.
|
||||
*/
|
||||
void MixRow_C(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans, ALsizei InPos, ALsizei BufferSize)
|
||||
{
|
||||
ALsizei c, i;
|
||||
|
||||
ASSUME(InChans > 0);
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
for(c = 0;c < InChans;c++)
|
||||
{
|
||||
const ALfloat gain = Gains[c];
|
||||
if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
|
||||
continue;
|
||||
|
||||
for(i = 0;i < BufferSize;i++)
|
||||
OutBuffer[i] += data[c][InPos+i] * gain;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <arm_neon.h>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "hrtf.h"
|
||||
#include "defs.h"
|
||||
|
||||
|
||||
const ALfloat *Resample_lerp_Neon(const InterpState* UNUSED(state),
|
||||
const ALfloat *restrict src, ALsizei frac, ALint increment,
|
||||
ALfloat *restrict dst, ALsizei numsamples)
|
||||
{
|
||||
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(numsamples > 0);
|
||||
|
||||
InitiatePositionArrays(frac, increment, frac_, pos_, 4);
|
||||
frac4 = vld1q_s32(frac_);
|
||||
pos4 = vld1q_s32(pos_);
|
||||
|
||||
todo = numsamples & ~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 < numsamples;++i)
|
||||
{
|
||||
dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE));
|
||||
|
||||
frac += increment;
|
||||
pos += frac>>FRACTIONBITS;
|
||||
frac &= FRACTIONMASK;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
const ALfloat *Resample_bsinc_Neon(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);
|
||||
|
||||
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 = ASSUME_ALIGNED(filter + offset, 16); offset += m;
|
||||
scd = ASSUME_ALIGNED(filter + offset, 16); offset += m;
|
||||
phd = ASSUME_ALIGNED(filter + offset, 16); offset += m;
|
||||
spd = ASSUME_ALIGNED(filter + offset, 16);
|
||||
|
||||
// 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, ALfloat (*restrict Values)[2],
|
||||
const ALsizei IrSize,
|
||||
const ALfloat (*restrict Coeffs)[2],
|
||||
ALfloat left, ALfloat right)
|
||||
{
|
||||
ALsizei c;
|
||||
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);
|
||||
}
|
||||
Values = ASSUME_ALIGNED(Values, 16);
|
||||
Coeffs = ASSUME_ALIGNED(Coeffs, 16);
|
||||
for(c = 0;c < IrSize;c += 2)
|
||||
{
|
||||
const ALsizei o0 = (Offset+c)&HRIR_MASK;
|
||||
const ALsizei o1 = (o0+1)&HRIR_MASK;
|
||||
float32x4_t vals = vcombine_f32(vld1_f32((float32_t*)&Values[o0][0]),
|
||||
vld1_f32((float32_t*)&Values[o1][0]));
|
||||
float32x4_t coefs = vld1q_f32((float32_t*)&Coeffs[c][0]);
|
||||
|
||||
vals = vmlaq_f32(vals, coefs, leftright4);
|
||||
|
||||
vst1_f32((float32_t*)&Values[o0][0], vget_low_f32(vals));
|
||||
vst1_f32((float32_t*)&Values[o1][0], vget_high_f32(vals));
|
||||
}
|
||||
}
|
||||
|
||||
#define MixHrtf MixHrtf_Neon
|
||||
#define MixHrtfBlend MixHrtfBlend_Neon
|
||||
#define MixDirectHrtf MixDirectHrtf_Neon
|
||||
#include "hrtf_inc.c"
|
||||
|
||||
|
||||
void Mix_Neon(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
|
||||
ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
|
||||
ALsizei BufferSize)
|
||||
{
|
||||
const ALfloat delta = (Counter > 0) ? 1.0f/(ALfloat)Counter : 0.0f;
|
||||
ALsizei c;
|
||||
|
||||
ASSUME(OutChans > 0);
|
||||
ASSUME(BufferSize > 0);
|
||||
data = ASSUME_ALIGNED(data, 16);
|
||||
OutBuffer = ASSUME_ALIGNED(OutBuffer, 16);
|
||||
|
||||
for(c = 0;c < OutChans;c++)
|
||||
{
|
||||
ALsizei pos = 0;
|
||||
ALfloat gain = CurrentGains[c];
|
||||
const ALfloat diff = TargetGains[c] - gain;
|
||||
|
||||
if(fabsf(diff) > FLT_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(&OutBuffer[c][OutPos+pos]);
|
||||
dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4));
|
||||
step_count4 = vaddq_f32(step_count4, four4);
|
||||
vst1q_f32(&OutBuffer[c][OutPos+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++)
|
||||
{
|
||||
OutBuffer[c][OutPos+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++)
|
||||
OutBuffer[c][OutPos+pos] += data[pos]*gain;
|
||||
}
|
||||
|
||||
if(!(fabsf(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(&OutBuffer[c][OutPos+pos]);
|
||||
dry4 = vmlaq_f32(dry4, val4, gain4);
|
||||
vst1q_f32(&OutBuffer[c][OutPos+pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
}
|
||||
for(;pos < BufferSize;pos++)
|
||||
OutBuffer[c][OutPos+pos] += data[pos]*gain;
|
||||
}
|
||||
}
|
||||
|
||||
void MixRow_Neon(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans, ALsizei InPos, ALsizei BufferSize)
|
||||
{
|
||||
ALsizei c;
|
||||
|
||||
ASSUME(InChans > 0);
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
for(c = 0;c < InChans;c++)
|
||||
{
|
||||
ALsizei pos = 0;
|
||||
const ALfloat gain = Gains[c];
|
||||
if(!(fabsf(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(&data[c][InPos+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] += data[c][InPos+pos]*gain;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <xmmintrin.h>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
|
||||
#include "alSource.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "defs.h"
|
||||
|
||||
|
||||
const ALfloat *Resample_bsinc_SSE(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;
|
||||
const __m128 *fil, *scd, *phd, *spd;
|
||||
ALsizei pi, i, j, offset;
|
||||
ALfloat pf;
|
||||
__m128 r4;
|
||||
|
||||
ASSUME(m > 0);
|
||||
ASSUME(dstlen > 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 __m128*)ASSUME_ALIGNED(filter + offset, 16); offset += m;
|
||||
scd = (const __m128*)ASSUME_ALIGNED(filter + offset, 16); offset += m;
|
||||
phd = (const __m128*)ASSUME_ALIGNED(filter + offset, 16); offset += m;
|
||||
spd = (const __m128*)ASSUME_ALIGNED(filter + offset, 16);
|
||||
|
||||
// Apply the scale and phase interpolated filter.
|
||||
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(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, ALfloat (*restrict Values)[2],
|
||||
const ALsizei IrSize,
|
||||
const ALfloat (*restrict Coeffs)[2],
|
||||
ALfloat left, ALfloat right)
|
||||
{
|
||||
const __m128 lrlr = _mm_setr_ps(left, right, left, right);
|
||||
__m128 vals = _mm_setzero_ps();
|
||||
__m128 coeffs;
|
||||
ALsizei i;
|
||||
|
||||
Values = ASSUME_ALIGNED(Values, 16);
|
||||
Coeffs = ASSUME_ALIGNED(Coeffs, 16);
|
||||
if((Offset&1))
|
||||
{
|
||||
const ALsizei o0 = Offset&HRIR_MASK;
|
||||
const ALsizei o1 = (Offset+IrSize-1)&HRIR_MASK;
|
||||
__m128 imp0, imp1;
|
||||
|
||||
coeffs = _mm_load_ps(&Coeffs[0][0]);
|
||||
vals = _mm_loadl_pi(vals, (__m64*)&Values[o0][0]);
|
||||
imp0 = _mm_mul_ps(lrlr, coeffs);
|
||||
vals = _mm_add_ps(imp0, vals);
|
||||
_mm_storel_pi((__m64*)&Values[o0][0], vals);
|
||||
for(i = 1;i < IrSize-1;i += 2)
|
||||
{
|
||||
const ALsizei o2 = (Offset+i)&HRIR_MASK;
|
||||
|
||||
coeffs = _mm_load_ps(&Coeffs[i+1][0]);
|
||||
vals = _mm_load_ps(&Values[o2][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[o2][0], vals);
|
||||
imp0 = imp1;
|
||||
}
|
||||
vals = _mm_loadl_pi(vals, (__m64*)&Values[o1][0]);
|
||||
imp0 = _mm_movehl_ps(imp0, imp0);
|
||||
vals = _mm_add_ps(imp0, vals);
|
||||
_mm_storel_pi((__m64*)&Values[o1][0], vals);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(i = 0;i < IrSize;i += 2)
|
||||
{
|
||||
const ALsizei o = (Offset + i)&HRIR_MASK;
|
||||
|
||||
coeffs = _mm_load_ps(&Coeffs[i][0]);
|
||||
vals = _mm_load_ps(&Values[o][0]);
|
||||
vals = _mm_add_ps(vals, _mm_mul_ps(lrlr, coeffs));
|
||||
_mm_store_ps(&Values[o][0], vals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define MixHrtf MixHrtf_SSE
|
||||
#define MixHrtfBlend MixHrtfBlend_SSE
|
||||
#define MixDirectHrtf MixDirectHrtf_SSE
|
||||
#include "hrtf_inc.c"
|
||||
|
||||
|
||||
void Mix_SSE(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
|
||||
ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
|
||||
ALsizei BufferSize)
|
||||
{
|
||||
const ALfloat delta = (Counter > 0) ? 1.0f/(ALfloat)Counter : 0.0f;
|
||||
ALsizei c;
|
||||
|
||||
ASSUME(OutChans > 0);
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
for(c = 0;c < OutChans;c++)
|
||||
{
|
||||
ALsizei pos = 0;
|
||||
ALfloat gain = CurrentGains[c];
|
||||
const ALfloat diff = TargetGains[c] - gain;
|
||||
|
||||
if(fabsf(diff) > FLT_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(&OutBuffer[c][OutPos+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(&OutBuffer[c][OutPos+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++)
|
||||
{
|
||||
OutBuffer[c][OutPos+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++)
|
||||
OutBuffer[c][OutPos+pos] += data[pos]*gain;
|
||||
}
|
||||
|
||||
if(!(fabsf(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(&OutBuffer[c][OutPos+pos]);
|
||||
dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
|
||||
_mm_store_ps(&OutBuffer[c][OutPos+pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
}
|
||||
for(;pos < BufferSize;pos++)
|
||||
OutBuffer[c][OutPos+pos] += data[pos]*gain;
|
||||
}
|
||||
}
|
||||
|
||||
void MixRow_SSE(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans, ALsizei InPos, ALsizei BufferSize)
|
||||
{
|
||||
ALsizei c;
|
||||
|
||||
ASSUME(InChans > 0);
|
||||
ASSUME(BufferSize > 0);
|
||||
|
||||
for(c = 0;c < InChans;c++)
|
||||
{
|
||||
ALsizei pos = 0;
|
||||
const ALfloat gain = Gains[c];
|
||||
if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
|
||||
continue;
|
||||
|
||||
if(LIKELY(BufferSize > 3))
|
||||
{
|
||||
ALsizei todo = BufferSize >> 2;
|
||||
const __m128 gain4 = _mm_set1_ps(gain);
|
||||
do {
|
||||
const __m128 val4 = _mm_load_ps(&data[c][InPos+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] += data[c][InPos+pos]*gain;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
|
||||
const ALfloat *Resample_lerp_SSE2(const InterpState* UNUSED(state),
|
||||
const ALfloat *restrict src, ALsizei frac, ALint increment,
|
||||
ALfloat *restrict dst, ALsizei numsamples)
|
||||
{
|
||||
const __m128i increment4 = _mm_set1_epi32(increment*4);
|
||||
const __m128 fracOne4 = _mm_set1_ps(1.0f/FRACTIONONE);
|
||||
const __m128i fracMask4 = _mm_set1_epi32(FRACTIONMASK);
|
||||
alignas(16) ALsizei pos_[4], frac_[4];
|
||||
__m128i frac4, pos4;
|
||||
ALsizei todo, pos, i;
|
||||
|
||||
ASSUME(numsamples > 0);
|
||||
|
||||
InitiatePositionArrays(frac, increment, frac_, pos_, 4);
|
||||
frac4 = _mm_setr_epi32(frac_[0], frac_[1], frac_[2], frac_[3]);
|
||||
pos4 = _mm_setr_epi32(pos_[0], pos_[1], pos_[2], pos_[3]);
|
||||
|
||||
todo = numsamples & ~3;
|
||||
for(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.
|
||||
*/
|
||||
pos = _mm_cvtsi128_si32(pos4);
|
||||
frac = _mm_cvtsi128_si32(frac4);
|
||||
|
||||
for(;i < numsamples;++i)
|
||||
{
|
||||
dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE));
|
||||
|
||||
frac += increment;
|
||||
pos += frac>>FRACTIONBITS;
|
||||
frac &= FRACTIONMASK;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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 <smmintrin.h>
|
||||
|
||||
#include "alu.h"
|
||||
#include "defs.h"
|
||||
|
||||
|
||||
const ALfloat *Resample_lerp_SSE41(const InterpState* UNUSED(state),
|
||||
const ALfloat *restrict src, ALsizei frac, ALint increment,
|
||||
ALfloat *restrict dst, ALsizei numsamples)
|
||||
{
|
||||
const __m128i increment4 = _mm_set1_epi32(increment*4);
|
||||
const __m128 fracOne4 = _mm_set1_ps(1.0f/FRACTIONONE);
|
||||
const __m128i fracMask4 = _mm_set1_epi32(FRACTIONMASK);
|
||||
alignas(16) ALsizei pos_[4], frac_[4];
|
||||
__m128i frac4, pos4;
|
||||
ALsizei todo, pos, i;
|
||||
|
||||
ASSUME(numsamples > 0);
|
||||
|
||||
InitiatePositionArrays(frac, increment, frac_, pos_, 4);
|
||||
frac4 = _mm_setr_epi32(frac_[0], frac_[1], frac_[2], frac_[3]);
|
||||
pos4 = _mm_setr_epi32(pos_[0], pos_[1], pos_[2], pos_[3]);
|
||||
|
||||
todo = numsamples & ~3;
|
||||
for(i = 0;i < todo;i += 4)
|
||||
{
|
||||
const int pos0 = _mm_extract_epi32(pos4, 0);
|
||||
const int pos1 = _mm_extract_epi32(pos4, 1);
|
||||
const int pos2 = _mm_extract_epi32(pos4, 2);
|
||||
const int pos3 = _mm_extract_epi32(pos4, 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.
|
||||
*/
|
||||
pos = _mm_cvtsi128_si32(pos4);
|
||||
frac = _mm_cvtsi128_si32(frac4);
|
||||
|
||||
for(;i < numsamples;++i)
|
||||
{
|
||||
dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE));
|
||||
|
||||
frac += increment;
|
||||
pos += frac>>FRACTIONBITS;
|
||||
frac &= FRACTIONMASK;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
+762
@@ -0,0 +1,762 @@
|
||||
/**
|
||||
* 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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.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!");
|
||||
|
||||
extern inline void InitiatePositionArrays(ALsizei frac, ALint increment, ALsizei *restrict frac_arr, ALsizei *restrict pos_arr, ALsizei size);
|
||||
|
||||
|
||||
/* 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!");
|
||||
|
||||
|
||||
enum Resampler ResamplerDefault = LinearResampler;
|
||||
|
||||
MixerFunc MixSamples = Mix_C;
|
||||
RowMixerFunc MixRowSamples = MixRow_C;
|
||||
static HrtfMixerFunc MixHrtfSamples = MixHrtf_C;
|
||||
static HrtfMixerBlendFunc MixHrtfBlendSamples = MixHrtfBlend_C;
|
||||
|
||||
static MixerFunc SelectMixer(void)
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
if((CPUCapFlags&CPU_CAP_NEON))
|
||||
return Mix_Neon;
|
||||
#endif
|
||||
#ifdef HAVE_SSE
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
return Mix_SSE;
|
||||
#endif
|
||||
return Mix_C;
|
||||
}
|
||||
|
||||
static RowMixerFunc SelectRowMixer(void)
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
if((CPUCapFlags&CPU_CAP_NEON))
|
||||
return MixRow_Neon;
|
||||
#endif
|
||||
#ifdef HAVE_SSE
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
return MixRow_SSE;
|
||||
#endif
|
||||
return MixRow_C;
|
||||
}
|
||||
|
||||
static inline HrtfMixerFunc SelectHrtfMixer(void)
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
if((CPUCapFlags&CPU_CAP_NEON))
|
||||
return MixHrtf_Neon;
|
||||
#endif
|
||||
#ifdef HAVE_SSE
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
return MixHrtf_SSE;
|
||||
#endif
|
||||
return MixHrtf_C;
|
||||
}
|
||||
|
||||
static inline HrtfMixerBlendFunc SelectHrtfBlendMixer(void)
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
if((CPUCapFlags&CPU_CAP_NEON))
|
||||
return MixHrtfBlend_Neon;
|
||||
#endif
|
||||
#ifdef HAVE_SSE
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
return MixHrtfBlend_SSE;
|
||||
#endif
|
||||
return MixHrtfBlend_C;
|
||||
}
|
||||
|
||||
ResamplerFunc SelectResampler(enum Resampler resampler)
|
||||
{
|
||||
switch(resampler)
|
||||
{
|
||||
case PointResampler:
|
||||
return Resample_point_C;
|
||||
case LinearResampler:
|
||||
#ifdef HAVE_NEON
|
||||
if((CPUCapFlags&CPU_CAP_NEON))
|
||||
return Resample_lerp_Neon;
|
||||
#endif
|
||||
#ifdef HAVE_SSE4_1
|
||||
if((CPUCapFlags&CPU_CAP_SSE4_1))
|
||||
return Resample_lerp_SSE41;
|
||||
#endif
|
||||
#ifdef HAVE_SSE2
|
||||
if((CPUCapFlags&CPU_CAP_SSE2))
|
||||
return Resample_lerp_SSE2;
|
||||
#endif
|
||||
return Resample_lerp_C;
|
||||
case FIR4Resampler:
|
||||
return Resample_cubic_C;
|
||||
case BSinc12Resampler:
|
||||
case BSinc24Resampler:
|
||||
#ifdef HAVE_NEON
|
||||
if((CPUCapFlags&CPU_CAP_NEON))
|
||||
return Resample_bsinc_Neon;
|
||||
#endif
|
||||
#ifdef HAVE_SSE
|
||||
if((CPUCapFlags&CPU_CAP_SSE))
|
||||
return Resample_bsinc_SSE;
|
||||
#endif
|
||||
return Resample_bsinc_C;
|
||||
}
|
||||
|
||||
return Resample_point_C;
|
||||
}
|
||||
|
||||
|
||||
void aluInitMixer(void)
|
||||
{
|
||||
const char *str;
|
||||
|
||||
if(ConfigValueStr(NULL, NULL, "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 = n;
|
||||
else
|
||||
WARN("Invalid resampler: %s\n", str);
|
||||
}
|
||||
}
|
||||
|
||||
MixHrtfBlendSamples = SelectHrtfBlendMixer();
|
||||
MixHrtfSamples = SelectHrtfMixer();
|
||||
MixSamples = SelectMixer();
|
||||
MixRowSamples = SelectRowMixer();
|
||||
}
|
||||
|
||||
|
||||
static void SendAsyncEvent(ALCcontext *context, ALuint enumtype, ALenum type,
|
||||
ALuint objid, ALuint param, const char *msg)
|
||||
{
|
||||
AsyncEvent evt = ASYNC_EVENT(enumtype);
|
||||
evt.u.user.type = type;
|
||||
evt.u.user.id = objid;
|
||||
evt.u.user.param = param;
|
||||
strcpy(evt.u.user.msg, msg);
|
||||
if(ll_ringbuffer_write(context->AsyncEvents, (const char*)&evt, 1) == 1)
|
||||
alsem_post(&context->EventSem);
|
||||
}
|
||||
|
||||
|
||||
static inline ALfloat Sample_ALubyte(ALubyte val)
|
||||
{ return (val-128) * (1.0f/128.0f); }
|
||||
|
||||
static inline ALfloat Sample_ALshort(ALshort val)
|
||||
{ return val * (1.0f/32768.0f); }
|
||||
|
||||
static inline ALfloat Sample_ALfloat(ALfloat val)
|
||||
{ return val; }
|
||||
|
||||
static inline ALfloat Sample_ALdouble(ALdouble val)
|
||||
{ return (ALfloat)val; }
|
||||
|
||||
typedef ALubyte ALmulaw;
|
||||
static inline ALfloat Sample_ALmulaw(ALmulaw val)
|
||||
{ return muLawDecompressionTable[val] * (1.0f/32768.0f); }
|
||||
|
||||
typedef ALubyte ALalaw;
|
||||
static inline ALfloat Sample_ALalaw(ALalaw val)
|
||||
{ return aLawDecompressionTable[val] * (1.0f/32768.0f); }
|
||||
|
||||
#define DECL_TEMPLATE(T) \
|
||||
static inline void Load_##T(ALfloat *restrict dst, const T *restrict src, \
|
||||
ALint srcstep, ALsizei samples) \
|
||||
{ \
|
||||
ALsizei i; \
|
||||
for(i = 0;i < samples;i++) \
|
||||
dst[i] += Sample_##T(src[i*srcstep]); \
|
||||
}
|
||||
|
||||
DECL_TEMPLATE(ALubyte)
|
||||
DECL_TEMPLATE(ALshort)
|
||||
DECL_TEMPLATE(ALfloat)
|
||||
DECL_TEMPLATE(ALdouble)
|
||||
DECL_TEMPLATE(ALmulaw)
|
||||
DECL_TEMPLATE(ALalaw)
|
||||
|
||||
#undef DECL_TEMPLATE
|
||||
|
||||
static void LoadSamples(ALfloat *restrict dst, const ALvoid *restrict src, ALint srcstep,
|
||||
enum FmtType srctype, ALsizei samples)
|
||||
{
|
||||
#define HANDLE_FMT(ET, ST) case ET: Load_##ST(dst, src, srcstep, samples); break
|
||||
switch(srctype)
|
||||
{
|
||||
HANDLE_FMT(FmtUByte, ALubyte);
|
||||
HANDLE_FMT(FmtShort, ALshort);
|
||||
HANDLE_FMT(FmtFloat, ALfloat);
|
||||
HANDLE_FMT(FmtDouble, ALdouble);
|
||||
HANDLE_FMT(FmtMulaw, ALmulaw);
|
||||
HANDLE_FMT(FmtAlaw, ALalaw);
|
||||
}
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
|
||||
|
||||
static const ALfloat *DoFilters(BiquadFilter *lpfilter, BiquadFilter *hpfilter,
|
||||
ALfloat *restrict dst, const ALfloat *restrict src,
|
||||
ALsizei numsamples, enum ActiveFilters type)
|
||||
{
|
||||
ALsizei i;
|
||||
switch(type)
|
||||
{
|
||||
case AF_None:
|
||||
BiquadFilter_passthru(lpfilter, numsamples);
|
||||
BiquadFilter_passthru(hpfilter, numsamples);
|
||||
break;
|
||||
|
||||
case AF_LowPass:
|
||||
BiquadFilter_process(lpfilter, dst, src, numsamples);
|
||||
BiquadFilter_passthru(hpfilter, numsamples);
|
||||
return dst;
|
||||
case AF_HighPass:
|
||||
BiquadFilter_passthru(lpfilter, numsamples);
|
||||
BiquadFilter_process(hpfilter, dst, src, numsamples);
|
||||
return dst;
|
||||
|
||||
case AF_BandPass:
|
||||
for(i = 0;i < numsamples;)
|
||||
{
|
||||
ALfloat temp[256];
|
||||
ALsizei todo = mini(256, numsamples-i);
|
||||
|
||||
BiquadFilter_process(lpfilter, temp, src+i, todo);
|
||||
BiquadFilter_process(hpfilter, dst+i, temp, todo);
|
||||
i += todo;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
|
||||
/* This function uses these device temp buffers. */
|
||||
#define SOURCE_DATA_BUF 0
|
||||
#define RESAMPLED_BUF 1
|
||||
#define FILTERED_BUF 2
|
||||
#define NFC_DATA_BUF 3
|
||||
ALboolean MixSource(ALvoice *voice, ALuint SourceID, ALCcontext *Context, ALsizei SamplesToDo)
|
||||
{
|
||||
ALCdevice *Device = Context->Device;
|
||||
ALbufferlistitem *BufferListItem;
|
||||
ALbufferlistitem *BufferLoopItem;
|
||||
ALsizei NumChannels, SampleSize;
|
||||
ALbitfieldSOFT enabledevt;
|
||||
ALsizei buffers_done = 0;
|
||||
ResamplerFunc Resample;
|
||||
ALsizei DataPosInt;
|
||||
ALsizei DataPosFrac;
|
||||
ALint64 DataSize64;
|
||||
ALint increment;
|
||||
ALsizei Counter;
|
||||
ALsizei OutPos;
|
||||
ALsizei IrSize;
|
||||
bool isplaying;
|
||||
bool firstpass;
|
||||
bool isstatic;
|
||||
ALsizei chan;
|
||||
ALsizei send;
|
||||
|
||||
/* Get source info */
|
||||
isplaying = true; /* Will only be called while playing. */
|
||||
isstatic = !!(voice->Flags&VOICE_IS_STATIC);
|
||||
DataPosInt = ATOMIC_LOAD(&voice->position, almemory_order_acquire);
|
||||
DataPosFrac = ATOMIC_LOAD(&voice->position_fraction, almemory_order_relaxed);
|
||||
BufferListItem = ATOMIC_LOAD(&voice->current_buffer, almemory_order_relaxed);
|
||||
BufferLoopItem = ATOMIC_LOAD(&voice->loop_buffer, almemory_order_relaxed);
|
||||
NumChannels = voice->NumChannels;
|
||||
SampleSize = voice->SampleSize;
|
||||
increment = voice->Step;
|
||||
|
||||
IrSize = (Device->HrtfHandle ? Device->HrtfHandle->irSize : 0);
|
||||
|
||||
Resample = ((increment == FRACTIONONE && DataPosFrac == 0) ?
|
||||
Resample_copy_C : voice->Resampler);
|
||||
|
||||
Counter = (voice->Flags&VOICE_IS_FADING) ? SamplesToDo : 0;
|
||||
firstpass = true;
|
||||
OutPos = 0;
|
||||
|
||||
do {
|
||||
ALsizei SrcBufferSize, DstBufferSize;
|
||||
|
||||
/* Figure out how many buffer samples will be needed */
|
||||
DataSize64 = SamplesToDo-OutPos;
|
||||
DataSize64 *= increment;
|
||||
DataSize64 += DataPosFrac+FRACTIONMASK;
|
||||
DataSize64 >>= FRACTIONBITS;
|
||||
DataSize64 += MAX_RESAMPLE_PADDING*2;
|
||||
SrcBufferSize = (ALsizei)mini64(DataSize64, BUFFERSIZE);
|
||||
|
||||
/* Figure out how many samples we can actually mix from this. */
|
||||
DataSize64 = SrcBufferSize;
|
||||
DataSize64 -= MAX_RESAMPLE_PADDING*2;
|
||||
DataSize64 <<= FRACTIONBITS;
|
||||
DataSize64 -= DataPosFrac;
|
||||
DstBufferSize = (ALsizei)mini64((DataSize64+(increment-1)) / increment,
|
||||
SamplesToDo - OutPos);
|
||||
|
||||
/* 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;
|
||||
|
||||
/* It's impossible to have a buffer list item with no entries. */
|
||||
assert(BufferListItem->num_buffers > 0);
|
||||
|
||||
for(chan = 0;chan < NumChannels;chan++)
|
||||
{
|
||||
const ALfloat *ResampledData;
|
||||
ALfloat *SrcData = Device->TempBuffer[SOURCE_DATA_BUF];
|
||||
ALsizei FilledAmt;
|
||||
|
||||
/* Load the previous samples into the source data first, and clear the rest. */
|
||||
memcpy(SrcData, voice->PrevSamples[chan], MAX_RESAMPLE_PADDING*sizeof(ALfloat));
|
||||
memset(SrcData+MAX_RESAMPLE_PADDING, 0, (BUFFERSIZE-MAX_RESAMPLE_PADDING)*
|
||||
sizeof(ALfloat));
|
||||
FilledAmt = MAX_RESAMPLE_PADDING;
|
||||
|
||||
if(isstatic)
|
||||
{
|
||||
/* 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;
|
||||
const ALsizei LoopSize = LoopEnd - LoopStart;
|
||||
|
||||
/* If current pos is beyond the loop range, do not loop */
|
||||
if(!BufferLoopItem || DataPosInt >= LoopEnd)
|
||||
{
|
||||
ALsizei SizeToDo = SrcBufferSize - FilledAmt;
|
||||
ALsizei CompLen = 0;
|
||||
ALsizei i;
|
||||
|
||||
BufferLoopItem = NULL;
|
||||
|
||||
for(i = 0;i < BufferListItem->num_buffers;i++)
|
||||
{
|
||||
const ALbuffer *buffer = BufferListItem->buffers[i];
|
||||
const ALubyte *Data = buffer->data;
|
||||
ALsizei DataSize;
|
||||
|
||||
if(DataPosInt >= buffer->SampleLen)
|
||||
continue;
|
||||
|
||||
/* Load what's left to play from the buffer */
|
||||
DataSize = mini(SizeToDo, buffer->SampleLen - DataPosInt);
|
||||
CompLen = maxi(CompLen, DataSize);
|
||||
|
||||
LoadSamples(&SrcData[FilledAmt],
|
||||
&Data[(DataPosInt*NumChannels + chan)*SampleSize],
|
||||
NumChannels, buffer->FmtType, DataSize
|
||||
);
|
||||
}
|
||||
FilledAmt += CompLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
ALsizei SizeToDo = mini(SrcBufferSize - FilledAmt, LoopEnd - DataPosInt);
|
||||
ALsizei CompLen = 0;
|
||||
ALsizei i;
|
||||
|
||||
for(i = 0;i < BufferListItem->num_buffers;i++)
|
||||
{
|
||||
const ALbuffer *buffer = BufferListItem->buffers[i];
|
||||
const ALubyte *Data = buffer->data;
|
||||
ALsizei DataSize;
|
||||
|
||||
if(DataPosInt >= buffer->SampleLen)
|
||||
continue;
|
||||
|
||||
/* Load what's left of this loop iteration */
|
||||
DataSize = mini(SizeToDo, buffer->SampleLen - DataPosInt);
|
||||
CompLen = maxi(CompLen, DataSize);
|
||||
|
||||
LoadSamples(&SrcData[FilledAmt],
|
||||
&Data[(DataPosInt*NumChannels + chan)*SampleSize],
|
||||
NumChannels, buffer->FmtType, DataSize
|
||||
);
|
||||
}
|
||||
FilledAmt += CompLen;
|
||||
|
||||
while(SrcBufferSize > FilledAmt)
|
||||
{
|
||||
const ALsizei SizeToDo = mini(SrcBufferSize - FilledAmt, LoopSize);
|
||||
|
||||
CompLen = 0;
|
||||
for(i = 0;i < BufferListItem->num_buffers;i++)
|
||||
{
|
||||
const ALbuffer *buffer = BufferListItem->buffers[i];
|
||||
const ALubyte *Data = buffer->data;
|
||||
ALsizei DataSize;
|
||||
|
||||
if(LoopStart >= buffer->SampleLen)
|
||||
continue;
|
||||
|
||||
DataSize = mini(SizeToDo, buffer->SampleLen - LoopStart);
|
||||
CompLen = maxi(CompLen, DataSize);
|
||||
|
||||
LoadSamples(&SrcData[FilledAmt],
|
||||
&Data[(LoopStart*NumChannels + chan)*SampleSize],
|
||||
NumChannels, buffer->FmtType, DataSize
|
||||
);
|
||||
}
|
||||
FilledAmt += CompLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Crawl the buffer queue to fill in the temp buffer */
|
||||
ALbufferlistitem *tmpiter = BufferListItem;
|
||||
ALsizei pos = DataPosInt;
|
||||
|
||||
while(tmpiter && SrcBufferSize > FilledAmt)
|
||||
{
|
||||
ALsizei SizeToDo = SrcBufferSize - FilledAmt;
|
||||
ALsizei CompLen = 0;
|
||||
ALsizei i;
|
||||
|
||||
for(i = 0;i < tmpiter->num_buffers;i++)
|
||||
{
|
||||
const ALbuffer *ALBuffer = tmpiter->buffers[i];
|
||||
ALsizei DataSize = ALBuffer ? ALBuffer->SampleLen : 0;
|
||||
|
||||
if(DataSize > pos)
|
||||
{
|
||||
const ALubyte *Data = ALBuffer->data;
|
||||
Data += (pos*NumChannels + chan)*SampleSize;
|
||||
|
||||
DataSize = mini(SizeToDo, DataSize - pos);
|
||||
CompLen = maxi(CompLen, DataSize);
|
||||
|
||||
LoadSamples(&SrcData[FilledAmt], Data, NumChannels,
|
||||
ALBuffer->FmtType, DataSize);
|
||||
}
|
||||
}
|
||||
if(UNLIKELY(!CompLen))
|
||||
pos -= tmpiter->max_samples;
|
||||
else
|
||||
{
|
||||
FilledAmt += CompLen;
|
||||
if(SrcBufferSize <= FilledAmt)
|
||||
break;
|
||||
pos = 0;
|
||||
}
|
||||
tmpiter = ATOMIC_LOAD(&tmpiter->next, almemory_order_acquire);
|
||||
if(!tmpiter) tmpiter = BufferLoopItem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Store the last source samples used for next time. */
|
||||
memcpy(voice->PrevSamples[chan],
|
||||
&SrcData[(increment*DstBufferSize + DataPosFrac)>>FRACTIONBITS],
|
||||
MAX_RESAMPLE_PADDING*sizeof(ALfloat)
|
||||
);
|
||||
|
||||
/* Now resample, then filter and mix to the appropriate outputs. */
|
||||
ResampledData = Resample(&voice->ResampleState,
|
||||
&SrcData[MAX_RESAMPLE_PADDING], DataPosFrac, increment,
|
||||
Device->TempBuffer[RESAMPLED_BUF], DstBufferSize
|
||||
);
|
||||
{
|
||||
DirectParams *parms = &voice->Direct.Params[chan];
|
||||
const ALfloat *samples;
|
||||
|
||||
samples = DoFilters(
|
||||
&parms->LowPass, &parms->HighPass, Device->TempBuffer[FILTERED_BUF],
|
||||
ResampledData, DstBufferSize, voice->Direct.FilterType
|
||||
);
|
||||
if(!(voice->Flags&VOICE_HAS_HRTF))
|
||||
{
|
||||
if(!Counter)
|
||||
memcpy(parms->Gains.Current, parms->Gains.Target,
|
||||
sizeof(parms->Gains.Current));
|
||||
if(!(voice->Flags&VOICE_HAS_NFC))
|
||||
MixSamples(samples, voice->Direct.Channels, voice->Direct.Buffer,
|
||||
parms->Gains.Current, parms->Gains.Target, Counter, OutPos,
|
||||
DstBufferSize
|
||||
);
|
||||
else
|
||||
{
|
||||
ALfloat *nfcsamples = Device->TempBuffer[NFC_DATA_BUF];
|
||||
ALsizei chanoffset = 0;
|
||||
|
||||
MixSamples(samples,
|
||||
voice->Direct.ChannelsPerOrder[0], voice->Direct.Buffer,
|
||||
parms->Gains.Current, parms->Gains.Target, Counter, OutPos,
|
||||
DstBufferSize
|
||||
);
|
||||
chanoffset += voice->Direct.ChannelsPerOrder[0];
|
||||
#define APPLY_NFC_MIX(order) \
|
||||
if(voice->Direct.ChannelsPerOrder[order] > 0) \
|
||||
{ \
|
||||
NfcFilterProcess##order(&parms->NFCtrlFilter, nfcsamples, samples, \
|
||||
DstBufferSize); \
|
||||
MixSamples(nfcsamples, voice->Direct.ChannelsPerOrder[order], \
|
||||
voice->Direct.Buffer+chanoffset, parms->Gains.Current+chanoffset, \
|
||||
parms->Gains.Target+chanoffset, Counter, OutPos, DstBufferSize \
|
||||
); \
|
||||
chanoffset += voice->Direct.ChannelsPerOrder[order]; \
|
||||
}
|
||||
APPLY_NFC_MIX(1)
|
||||
APPLY_NFC_MIX(2)
|
||||
APPLY_NFC_MIX(3)
|
||||
#undef APPLY_NFC_MIX
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MixHrtfParams hrtfparams;
|
||||
ALsizei fademix = 0;
|
||||
int lidx, ridx;
|
||||
|
||||
lidx = GetChannelIdxByName(&Device->RealOut, FrontLeft);
|
||||
ridx = GetChannelIdxByName(&Device->RealOut, FrontRight);
|
||||
assert(lidx != -1 && ridx != -1);
|
||||
|
||||
if(!Counter)
|
||||
{
|
||||
/* No fading, just overwrite the old HRTF params. */
|
||||
parms->Hrtf.Old = parms->Hrtf.Target;
|
||||
}
|
||||
else 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;
|
||||
}
|
||||
else if(firstpass)
|
||||
{
|
||||
ALfloat gain;
|
||||
|
||||
/* Fade between the coefficients over 128 samples. */
|
||||
fademix = mini(DstBufferSize, 128);
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
gain = lerp(parms->Hrtf.Old.Gain, parms->Hrtf.Target.Gain,
|
||||
minf(1.0f, (ALfloat)fademix/Counter));
|
||||
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 / (ALfloat)fademix;
|
||||
|
||||
MixHrtfBlendSamples(
|
||||
voice->Direct.Buffer[lidx], voice->Direct.Buffer[ridx],
|
||||
samples, voice->Offset, OutPos, IrSize, &parms->Hrtf.Old,
|
||||
&hrtfparams, &parms->Hrtf.State, fademix
|
||||
);
|
||||
/* Update the old parameters with the result. */
|
||||
parms->Hrtf.Old = parms->Hrtf.Target;
|
||||
if(fademix < Counter)
|
||||
parms->Hrtf.Old.Gain = hrtfparams.Gain;
|
||||
}
|
||||
|
||||
if(fademix < DstBufferSize)
|
||||
{
|
||||
ALsizei todo = DstBufferSize - fademix;
|
||||
ALfloat gain = parms->Hrtf.Target.Gain;
|
||||
|
||||
/* Interpolate the target gain if the gain fading lasts
|
||||
* longer than this mix.
|
||||
*/
|
||||
if(Counter > DstBufferSize)
|
||||
gain = lerp(parms->Hrtf.Old.Gain, gain,
|
||||
(ALfloat)todo/(Counter-fademix));
|
||||
|
||||
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) / (ALfloat)todo;
|
||||
MixHrtfSamples(
|
||||
voice->Direct.Buffer[lidx], voice->Direct.Buffer[ridx],
|
||||
samples+fademix, voice->Offset+fademix, OutPos+fademix, IrSize,
|
||||
&hrtfparams, &parms->Hrtf.State, 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 = parms->Hrtf.Target.Gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(send = 0;send < Device->NumAuxSends;send++)
|
||||
{
|
||||
SendParams *parms = &voice->Send[send].Params[chan];
|
||||
const ALfloat *samples;
|
||||
|
||||
if(!voice->Send[send].Buffer)
|
||||
continue;
|
||||
|
||||
samples = DoFilters(
|
||||
&parms->LowPass, &parms->HighPass, Device->TempBuffer[FILTERED_BUF],
|
||||
ResampledData, DstBufferSize, voice->Send[send].FilterType
|
||||
);
|
||||
|
||||
if(!Counter)
|
||||
memcpy(parms->Gains.Current, parms->Gains.Target,
|
||||
sizeof(parms->Gains.Current));
|
||||
MixSamples(samples, voice->Send[send].Channels, voice->Send[send].Buffer,
|
||||
parms->Gains.Current, parms->Gains.Target, Counter, OutPos, DstBufferSize
|
||||
);
|
||||
}
|
||||
}
|
||||
/* Update positions */
|
||||
DataPosFrac += increment*DstBufferSize;
|
||||
DataPosInt += DataPosFrac>>FRACTIONBITS;
|
||||
DataPosFrac &= FRACTIONMASK;
|
||||
|
||||
OutPos += DstBufferSize;
|
||||
voice->Offset += DstBufferSize;
|
||||
Counter = maxi(DstBufferSize, Counter) - DstBufferSize;
|
||||
firstpass = false;
|
||||
|
||||
if(isstatic)
|
||||
{
|
||||
if(BufferLoopItem)
|
||||
{
|
||||
/* Handle looping static source */
|
||||
const ALbuffer *Buffer = BufferListItem->buffers[0];
|
||||
ALsizei LoopStart = Buffer->LoopStart;
|
||||
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)
|
||||
{
|
||||
isplaying = false;
|
||||
BufferListItem = NULL;
|
||||
DataPosInt = 0;
|
||||
DataPosFrac = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else while(1)
|
||||
{
|
||||
/* Handle streaming source */
|
||||
if(BufferListItem->max_samples > DataPosInt)
|
||||
break;
|
||||
|
||||
DataPosInt -= BufferListItem->max_samples;
|
||||
|
||||
buffers_done += BufferListItem->num_buffers;
|
||||
BufferListItem = ATOMIC_LOAD(&BufferListItem->next, almemory_order_relaxed);
|
||||
if(!BufferListItem && !(BufferListItem=BufferLoopItem))
|
||||
{
|
||||
isplaying = false;
|
||||
DataPosInt = 0;
|
||||
DataPosFrac = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(isplaying && OutPos < SamplesToDo);
|
||||
|
||||
voice->Flags |= VOICE_IS_FADING;
|
||||
|
||||
/* Update source info */
|
||||
ATOMIC_STORE(&voice->position, DataPosInt, almemory_order_relaxed);
|
||||
ATOMIC_STORE(&voice->position_fraction, DataPosFrac, almemory_order_relaxed);
|
||||
ATOMIC_STORE(&voice->current_buffer, BufferListItem, almemory_order_release);
|
||||
|
||||
/* Send any events now, after the position/buffer info was updated. */
|
||||
enabledevt = ATOMIC_LOAD(&Context->EnabledEvts, almemory_order_acquire);
|
||||
if(buffers_done > 0 && (enabledevt&EventType_BufferCompleted))
|
||||
SendAsyncEvent(Context, EventType_BufferCompleted,
|
||||
AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, SourceID, buffers_done, "Buffer completed"
|
||||
);
|
||||
|
||||
return isplaying;
|
||||
}
|
||||
+1239
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
||||
#ifndef POLYMORPHISM_H
|
||||
#define POLYMORPHISM_H
|
||||
|
||||
/* Macros to declare inheriting types, and to (down-)cast and up-cast. */
|
||||
#define DERIVE_FROM_TYPE(t) t t##_parent
|
||||
#define STATIC_CAST(to, obj) (&(obj)->to##_parent)
|
||||
#ifdef __GNUC__
|
||||
#define STATIC_UPCAST(to, from, obj) __extension__({ \
|
||||
static_assert(__builtin_types_compatible_p(from, __typeof(*(obj))), \
|
||||
"Invalid upcast object from type"); \
|
||||
(to*)((char*)(obj) - offsetof(to, from##_parent)); \
|
||||
})
|
||||
#else
|
||||
#define STATIC_UPCAST(to, from, obj) ((to*)((char*)(obj) - offsetof(to, from##_parent)))
|
||||
#endif
|
||||
|
||||
/* Defines method forwards, which call the given parent's (T2's) implementation. */
|
||||
#define DECLARE_FORWARD(T1, T2, rettype, func) \
|
||||
rettype T1##_##func(T1 *obj) \
|
||||
{ return T2##_##func(STATIC_CAST(T2, obj)); }
|
||||
|
||||
#define DECLARE_FORWARD1(T1, T2, rettype, func, argtype1) \
|
||||
rettype T1##_##func(T1 *obj, argtype1 a) \
|
||||
{ return T2##_##func(STATIC_CAST(T2, obj), a); }
|
||||
|
||||
#define DECLARE_FORWARD2(T1, T2, rettype, func, argtype1, argtype2) \
|
||||
rettype T1##_##func(T1 *obj, argtype1 a, argtype2 b) \
|
||||
{ return T2##_##func(STATIC_CAST(T2, obj), a, b); }
|
||||
|
||||
#define DECLARE_FORWARD3(T1, T2, rettype, func, argtype1, argtype2, argtype3) \
|
||||
rettype T1##_##func(T1 *obj, argtype1 a, argtype2 b, argtype3 c) \
|
||||
{ return T2##_##func(STATIC_CAST(T2, obj), a, b, c); }
|
||||
|
||||
/* Defines method thunks, functions that call to the child's method. */
|
||||
#define DECLARE_THUNK(T1, T2, rettype, func) \
|
||||
static rettype T1##_##T2##_##func(T2 *obj) \
|
||||
{ return T1##_##func(STATIC_UPCAST(T1, T2, obj)); }
|
||||
|
||||
#define DECLARE_THUNK1(T1, T2, rettype, func, argtype1) \
|
||||
static rettype T1##_##T2##_##func(T2 *obj, argtype1 a) \
|
||||
{ return T1##_##func(STATIC_UPCAST(T1, T2, obj), a); }
|
||||
|
||||
#define DECLARE_THUNK2(T1, T2, rettype, func, argtype1, argtype2) \
|
||||
static rettype T1##_##T2##_##func(T2 *obj, argtype1 a, argtype2 b) \
|
||||
{ return T1##_##func(STATIC_UPCAST(T1, T2, obj), a, b); }
|
||||
|
||||
#define DECLARE_THUNK3(T1, T2, rettype, func, argtype1, argtype2, argtype3) \
|
||||
static rettype T1##_##T2##_##func(T2 *obj, argtype1 a, argtype2 b, argtype3 c) \
|
||||
{ return T1##_##func(STATIC_UPCAST(T1, T2, obj), a, b, c); }
|
||||
|
||||
#define DECLARE_THUNK4(T1, T2, rettype, func, argtype1, argtype2, argtype3, argtype4) \
|
||||
static rettype T1##_##T2##_##func(T2 *obj, argtype1 a, argtype2 b, argtype3 c, argtype4 d) \
|
||||
{ return T1##_##func(STATIC_UPCAST(T1, T2, obj), a, b, c, d); }
|
||||
|
||||
/* Defines the default functions used to (de)allocate a polymorphic object. */
|
||||
#define DECLARE_DEFAULT_ALLOCATORS(T) \
|
||||
static void* T##_New(size_t size) { return al_malloc(16, size); } \
|
||||
static void T##_Delete(void *ptr) { al_free(ptr); }
|
||||
|
||||
|
||||
/* Helper to extract an argument list for virtual method calls. */
|
||||
#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
|
||||
|
||||
/* Call a "virtual" method on an object, with arguments. */
|
||||
#define V(obj, func) ((obj)->vtbl->func((obj), EXTRACT_VCALL_ARGS
|
||||
/* Call a "virtual" method on an object, with no arguments. */
|
||||
#define V0(obj, func) ((obj)->vtbl->func((obj) EXTRACT_VCALL_ARGS
|
||||
|
||||
|
||||
/* Helper to extract an argument list for NEW_OBJ calls. */
|
||||
#define EXTRACT_NEW_ARGS(...) __VA_ARGS__); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* Allocate and construct an object, with arguments. */
|
||||
#define NEW_OBJ(_res, T) do { \
|
||||
_res = T##_New(sizeof(T)); \
|
||||
if(_res) \
|
||||
{ \
|
||||
memset(_res, 0, sizeof(T)); \
|
||||
T##_Construct(_res, EXTRACT_NEW_ARGS
|
||||
/* Allocate and construct an object, with no arguments. */
|
||||
#define NEW_OBJ0(_res, T) do { \
|
||||
_res = T##_New(sizeof(T)); \
|
||||
if(_res) \
|
||||
{ \
|
||||
memset(_res, 0, sizeof(T)); \
|
||||
T##_Construct(_res EXTRACT_NEW_ARGS
|
||||
|
||||
/* Destructs and deallocate an object. */
|
||||
#define DELETE_OBJ(obj) do { \
|
||||
if((obj) != NULL) \
|
||||
{ \
|
||||
V0((obj),Destruct)(); \
|
||||
V0((obj),Delete)(); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
|
||||
/* Helper to get a type's vtable thunk for a child type. */
|
||||
#define GET_VTABLE2(T1, T2) (&(T1##_##T2##_vtable))
|
||||
/* Helper to set an object's vtable thunk for a child type. Used when constructing an object. */
|
||||
#define SET_VTABLE2(T1, T2, obj) (STATIC_CAST(T2, obj)->vtbl = GET_VTABLE2(T1, T2))
|
||||
|
||||
#endif /* POLYMORPHISM_H */
|
||||
@@ -0,0 +1,295 @@
|
||||
/**
|
||||
* 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 <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "ringbuffer.h"
|
||||
#include "align.h"
|
||||
#include "atomic.h"
|
||||
#include "threads.h"
|
||||
#include "almalloc.h"
|
||||
#include "compat.h"
|
||||
|
||||
|
||||
/* NOTE: This lockless ringbuffer implementation is copied from JACK, extended
|
||||
* to include an element size. Consequently, parameters and return values for a
|
||||
* size or count is in 'elements', not bytes. Additionally, it only supports
|
||||
* single-consumer/single-provider operation. */
|
||||
struct ll_ringbuffer {
|
||||
ATOMIC(size_t) write_ptr;
|
||||
ATOMIC(size_t) read_ptr;
|
||||
size_t size;
|
||||
size_t size_mask;
|
||||
size_t elem_size;
|
||||
|
||||
alignas(16) char buf[];
|
||||
};
|
||||
|
||||
ll_ringbuffer_t *ll_ringbuffer_create(size_t sz, size_t elem_sz, int limit_writes)
|
||||
{
|
||||
ll_ringbuffer_t *rb;
|
||||
size_t power_of_two = 0;
|
||||
|
||||
if(sz > 0)
|
||||
{
|
||||
power_of_two = sz;
|
||||
power_of_two |= power_of_two>>1;
|
||||
power_of_two |= power_of_two>>2;
|
||||
power_of_two |= power_of_two>>4;
|
||||
power_of_two |= power_of_two>>8;
|
||||
power_of_two |= power_of_two>>16;
|
||||
#if SIZE_MAX > UINT_MAX
|
||||
power_of_two |= power_of_two>>32;
|
||||
#endif
|
||||
}
|
||||
power_of_two++;
|
||||
if(power_of_two < sz) return NULL;
|
||||
|
||||
rb = al_malloc(16, sizeof(*rb) + power_of_two*elem_sz);
|
||||
if(!rb) return NULL;
|
||||
|
||||
ATOMIC_INIT(&rb->write_ptr, 0);
|
||||
ATOMIC_INIT(&rb->read_ptr, 0);
|
||||
rb->size = limit_writes ? sz : power_of_two;
|
||||
rb->size_mask = power_of_two - 1;
|
||||
rb->elem_size = elem_sz;
|
||||
return rb;
|
||||
}
|
||||
|
||||
void ll_ringbuffer_free(ll_ringbuffer_t *rb)
|
||||
{
|
||||
al_free(rb);
|
||||
}
|
||||
|
||||
void ll_ringbuffer_reset(ll_ringbuffer_t *rb)
|
||||
{
|
||||
ATOMIC_STORE(&rb->write_ptr, 0, almemory_order_release);
|
||||
ATOMIC_STORE(&rb->read_ptr, 0, almemory_order_release);
|
||||
memset(rb->buf, 0, (rb->size_mask+1)*rb->elem_size);
|
||||
}
|
||||
|
||||
|
||||
size_t ll_ringbuffer_read_space(const ll_ringbuffer_t *rb)
|
||||
{
|
||||
size_t w = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->write_ptr, almemory_order_acquire);
|
||||
size_t r = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->read_ptr, almemory_order_acquire);
|
||||
return (w-r) & rb->size_mask;
|
||||
}
|
||||
|
||||
size_t ll_ringbuffer_write_space(const ll_ringbuffer_t *rb)
|
||||
{
|
||||
size_t w = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->write_ptr, almemory_order_acquire);
|
||||
size_t r = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->read_ptr, almemory_order_acquire);
|
||||
w = (r-w-1) & rb->size_mask;
|
||||
return (w > rb->size) ? rb->size : w;
|
||||
}
|
||||
|
||||
|
||||
size_t ll_ringbuffer_read(ll_ringbuffer_t *rb, char *dest, size_t cnt)
|
||||
{
|
||||
size_t read_ptr;
|
||||
size_t free_cnt;
|
||||
size_t cnt2;
|
||||
size_t to_read;
|
||||
size_t n1, n2;
|
||||
|
||||
free_cnt = ll_ringbuffer_read_space(rb);
|
||||
if(free_cnt == 0) return 0;
|
||||
|
||||
to_read = (cnt > free_cnt) ? free_cnt : cnt;
|
||||
read_ptr = ATOMIC_LOAD(&rb->read_ptr, almemory_order_relaxed) & rb->size_mask;
|
||||
|
||||
cnt2 = read_ptr + to_read;
|
||||
if(cnt2 > rb->size_mask+1)
|
||||
{
|
||||
n1 = rb->size_mask+1 - read_ptr;
|
||||
n2 = cnt2 & rb->size_mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
n1 = to_read;
|
||||
n2 = 0;
|
||||
}
|
||||
|
||||
memcpy(dest, &rb->buf[read_ptr*rb->elem_size], n1*rb->elem_size);
|
||||
read_ptr += n1;
|
||||
if(n2)
|
||||
{
|
||||
memcpy(dest + n1*rb->elem_size, &rb->buf[(read_ptr&rb->size_mask)*rb->elem_size],
|
||||
n2*rb->elem_size);
|
||||
read_ptr += n2;
|
||||
}
|
||||
ATOMIC_STORE(&rb->read_ptr, read_ptr, almemory_order_release);
|
||||
return to_read;
|
||||
}
|
||||
|
||||
size_t ll_ringbuffer_peek(ll_ringbuffer_t *rb, char *dest, size_t cnt)
|
||||
{
|
||||
size_t free_cnt;
|
||||
size_t cnt2;
|
||||
size_t to_read;
|
||||
size_t n1, n2;
|
||||
size_t read_ptr;
|
||||
|
||||
free_cnt = ll_ringbuffer_read_space(rb);
|
||||
if(free_cnt == 0) return 0;
|
||||
|
||||
to_read = (cnt > free_cnt) ? free_cnt : cnt;
|
||||
read_ptr = ATOMIC_LOAD(&rb->read_ptr, almemory_order_relaxed) & rb->size_mask;
|
||||
|
||||
cnt2 = read_ptr + to_read;
|
||||
if(cnt2 > rb->size_mask+1)
|
||||
{
|
||||
n1 = rb->size_mask+1 - read_ptr;
|
||||
n2 = cnt2 & rb->size_mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
n1 = to_read;
|
||||
n2 = 0;
|
||||
}
|
||||
|
||||
memcpy(dest, &rb->buf[read_ptr*rb->elem_size], n1*rb->elem_size);
|
||||
if(n2)
|
||||
{
|
||||
read_ptr += n1;
|
||||
memcpy(dest + n1*rb->elem_size, &rb->buf[(read_ptr&rb->size_mask)*rb->elem_size],
|
||||
n2*rb->elem_size);
|
||||
}
|
||||
return to_read;
|
||||
}
|
||||
|
||||
size_t ll_ringbuffer_write(ll_ringbuffer_t *rb, const char *src, size_t cnt)
|
||||
{
|
||||
size_t write_ptr;
|
||||
size_t free_cnt;
|
||||
size_t cnt2;
|
||||
size_t to_write;
|
||||
size_t n1, n2;
|
||||
|
||||
free_cnt = ll_ringbuffer_write_space(rb);
|
||||
if(free_cnt == 0) return 0;
|
||||
|
||||
to_write = (cnt > free_cnt) ? free_cnt : cnt;
|
||||
write_ptr = ATOMIC_LOAD(&rb->write_ptr, almemory_order_relaxed) & rb->size_mask;
|
||||
|
||||
cnt2 = write_ptr + to_write;
|
||||
if(cnt2 > rb->size_mask+1)
|
||||
{
|
||||
n1 = rb->size_mask+1 - write_ptr;
|
||||
n2 = cnt2 & rb->size_mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
n1 = to_write;
|
||||
n2 = 0;
|
||||
}
|
||||
|
||||
memcpy(&rb->buf[write_ptr*rb->elem_size], src, n1*rb->elem_size);
|
||||
write_ptr += n1;
|
||||
if(n2)
|
||||
{
|
||||
memcpy(&rb->buf[(write_ptr&rb->size_mask)*rb->elem_size], src + n1*rb->elem_size,
|
||||
n2*rb->elem_size);
|
||||
write_ptr += n2;
|
||||
}
|
||||
ATOMIC_STORE(&rb->write_ptr, write_ptr, almemory_order_release);
|
||||
return to_write;
|
||||
}
|
||||
|
||||
|
||||
void ll_ringbuffer_read_advance(ll_ringbuffer_t *rb, size_t cnt)
|
||||
{
|
||||
ATOMIC_ADD(&rb->read_ptr, cnt, almemory_order_acq_rel);
|
||||
}
|
||||
|
||||
void ll_ringbuffer_write_advance(ll_ringbuffer_t *rb, size_t cnt)
|
||||
{
|
||||
ATOMIC_ADD(&rb->write_ptr, cnt, almemory_order_acq_rel);
|
||||
}
|
||||
|
||||
|
||||
void ll_ringbuffer_get_read_vector(const ll_ringbuffer_t *rb, ll_ringbuffer_data_t vec[2])
|
||||
{
|
||||
size_t free_cnt;
|
||||
size_t cnt2;
|
||||
size_t w, r;
|
||||
|
||||
w = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->write_ptr, almemory_order_acquire);
|
||||
r = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->read_ptr, almemory_order_acquire);
|
||||
w &= rb->size_mask;
|
||||
r &= rb->size_mask;
|
||||
free_cnt = (w-r) & rb->size_mask;
|
||||
|
||||
cnt2 = r + free_cnt;
|
||||
if(cnt2 > rb->size_mask+1)
|
||||
{
|
||||
/* Two part vector: the rest of the buffer after the current write ptr,
|
||||
* plus some from the start of the buffer. */
|
||||
vec[0].buf = (char*)&rb->buf[r*rb->elem_size];
|
||||
vec[0].len = rb->size_mask+1 - r;
|
||||
vec[1].buf = (char*)rb->buf;
|
||||
vec[1].len = cnt2 & rb->size_mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Single part vector: just the rest of the buffer */
|
||||
vec[0].buf = (char*)&rb->buf[r*rb->elem_size];
|
||||
vec[0].len = free_cnt;
|
||||
vec[1].buf = NULL;
|
||||
vec[1].len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ll_ringbuffer_get_write_vector(const ll_ringbuffer_t *rb, ll_ringbuffer_data_t vec[2])
|
||||
{
|
||||
size_t free_cnt;
|
||||
size_t cnt2;
|
||||
size_t w, r;
|
||||
|
||||
w = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->write_ptr, almemory_order_acquire);
|
||||
r = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->read_ptr, almemory_order_acquire);
|
||||
w &= rb->size_mask;
|
||||
r &= rb->size_mask;
|
||||
free_cnt = (r-w-1) & rb->size_mask;
|
||||
if(free_cnt > rb->size) free_cnt = rb->size;
|
||||
|
||||
cnt2 = w + free_cnt;
|
||||
if(cnt2 > rb->size_mask+1)
|
||||
{
|
||||
/* Two part vector: the rest of the buffer after the current write ptr,
|
||||
* plus some from the start of the buffer. */
|
||||
vec[0].buf = (char*)&rb->buf[w*rb->elem_size];
|
||||
vec[0].len = rb->size_mask+1 - w;
|
||||
vec[1].buf = (char*)rb->buf;
|
||||
vec[1].len = cnt2 & rb->size_mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
vec[0].buf = (char*)&rb->buf[w*rb->elem_size];
|
||||
vec[0].len = free_cnt;
|
||||
vec[1].buf = NULL;
|
||||
vec[1].len = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef RINGBUFFER_H
|
||||
#define RINGBUFFER_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct ll_ringbuffer ll_ringbuffer_t;
|
||||
typedef struct ll_ringbuffer_data {
|
||||
char *buf;
|
||||
size_t len;
|
||||
} ll_ringbuffer_data_t;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new ringbuffer to hold at least `sz' elements of `elem_sz' bytes.
|
||||
* The number of elements is rounded up to the next power of two (even if it is
|
||||
* already a power of two, to ensure the requested amount can be written).
|
||||
*/
|
||||
ll_ringbuffer_t *ll_ringbuffer_create(size_t sz, size_t elem_sz, int limit_writes);
|
||||
/** Free all data associated with the ringbuffer `rb'. */
|
||||
void ll_ringbuffer_free(ll_ringbuffer_t *rb);
|
||||
/** Reset the read and write pointers to zero. This is not thread safe. */
|
||||
void ll_ringbuffer_reset(ll_ringbuffer_t *rb);
|
||||
|
||||
/**
|
||||
* The non-copying data reader. `vec' is an array of two places. Set the values
|
||||
* at `vec' to hold the current readable data at `rb'. If the readable data is
|
||||
* in one segment the second segment has zero length.
|
||||
*/
|
||||
void ll_ringbuffer_get_read_vector(const ll_ringbuffer_t *rb, ll_ringbuffer_data_t vec[2]);
|
||||
/**
|
||||
* The non-copying data writer. `vec' is an array of two places. Set the values
|
||||
* at `vec' to hold the current writeable data at `rb'. If the writeable data
|
||||
* is in one segment the second segment has zero length.
|
||||
*/
|
||||
void ll_ringbuffer_get_write_vector(const ll_ringbuffer_t *rb, ll_ringbuffer_data_t vec[2]);
|
||||
|
||||
/**
|
||||
* Return the number of elements available for reading. This is the number of
|
||||
* elements in front of the read pointer and behind the write pointer.
|
||||
*/
|
||||
size_t ll_ringbuffer_read_space(const ll_ringbuffer_t *rb);
|
||||
/**
|
||||
* The copying data reader. Copy at most `cnt' elements from `rb' to `dest'.
|
||||
* Returns the actual number of elements copied.
|
||||
*/
|
||||
size_t ll_ringbuffer_read(ll_ringbuffer_t *rb, char *dest, size_t cnt);
|
||||
/**
|
||||
* The copying data reader w/o read pointer advance. Copy at most `cnt'
|
||||
* elements from `rb' to `dest'. Returns the actual number of elements copied.
|
||||
*/
|
||||
size_t ll_ringbuffer_peek(ll_ringbuffer_t *rb, char *dest, size_t cnt);
|
||||
/** Advance the read pointer `cnt' places. */
|
||||
void ll_ringbuffer_read_advance(ll_ringbuffer_t *rb, size_t cnt);
|
||||
|
||||
/**
|
||||
* Return the number of elements available for writing. This is the number of
|
||||
* elements in front of the write pointer and behind the read pointer.
|
||||
*/
|
||||
size_t ll_ringbuffer_write_space(const ll_ringbuffer_t *rb);
|
||||
/**
|
||||
* The copying data writer. Copy at most `cnt' elements to `rb' from `src'.
|
||||
* Returns the actual number of elements copied.
|
||||
*/
|
||||
size_t ll_ringbuffer_write(ll_ringbuffer_t *rb, const char *src, size_t cnt);
|
||||
/** Advance the write pointer `cnt' places. */
|
||||
void ll_ringbuffer_write_advance(ll_ringbuffer_t *rb, size_t cnt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* RINGBUFFER_H */
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "alu.h"
|
||||
#include "uhjfilter.h"
|
||||
|
||||
/* This is the maximum number of samples processed for each inner loop
|
||||
* iteration. */
|
||||
#define MAX_UPDATE_SAMPLES 128
|
||||
|
||||
|
||||
static const ALfloat Filter1CoeffSqr[4] = {
|
||||
0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f
|
||||
};
|
||||
static const ALfloat Filter2CoeffSqr[4] = {
|
||||
0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156685f
|
||||
};
|
||||
|
||||
static void allpass_process(AllPassState *state, ALfloat *restrict dst, const ALfloat *restrict src, const ALfloat aa, ALsizei todo)
|
||||
{
|
||||
ALfloat z1 = state->z[0];
|
||||
ALfloat z2 = state->z[1];
|
||||
ALsizei i;
|
||||
|
||||
for(i = 0;i < todo;i++)
|
||||
{
|
||||
ALfloat input = src[i];
|
||||
ALfloat output = input*aa + z1;
|
||||
z1 = z2; z2 = output*aa - input;
|
||||
dst[i] = output;
|
||||
}
|
||||
|
||||
state->z[0] = z1;
|
||||
state->z[1] = z2;
|
||||
}
|
||||
|
||||
|
||||
/* 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 EncodeUhj2(Uhj2Encoder *enc, ALfloat *restrict LeftOut, ALfloat *restrict RightOut, ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo)
|
||||
{
|
||||
ALfloat D[MAX_UPDATE_SAMPLES], S[MAX_UPDATE_SAMPLES];
|
||||
ALfloat temp[2][MAX_UPDATE_SAMPLES];
|
||||
ALsizei base, i;
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
for(base = 0;base < SamplesToDo;)
|
||||
{
|
||||
ALsizei todo = mini(SamplesToDo - base, MAX_UPDATE_SAMPLES);
|
||||
ASSUME(todo > 0);
|
||||
|
||||
/* D = 0.6554516*Y */
|
||||
for(i = 0;i < todo;i++)
|
||||
temp[0][i] = 0.6554516f*InSamples[2][base+i];
|
||||
allpass_process(&enc->Filter1_Y[0], temp[1], temp[0], Filter1CoeffSqr[0], todo);
|
||||
allpass_process(&enc->Filter1_Y[1], temp[0], temp[1], Filter1CoeffSqr[1], todo);
|
||||
allpass_process(&enc->Filter1_Y[2], temp[1], temp[0], Filter1CoeffSqr[2], todo);
|
||||
allpass_process(&enc->Filter1_Y[3], temp[0], temp[1], 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] = enc->LastY;
|
||||
for(i = 1;i < todo;i++)
|
||||
D[i] = temp[0][i-1];
|
||||
enc->LastY = temp[0][i-1];
|
||||
|
||||
/* D += j(-0.3420201*W + 0.5098604*X) */
|
||||
for(i = 0;i < todo;i++)
|
||||
temp[0][i] = -0.3420201f*InSamples[0][base+i] +
|
||||
0.5098604f*InSamples[1][base+i];
|
||||
allpass_process(&enc->Filter2_WX[0], temp[1], temp[0], Filter2CoeffSqr[0], todo);
|
||||
allpass_process(&enc->Filter2_WX[1], temp[0], temp[1], Filter2CoeffSqr[1], todo);
|
||||
allpass_process(&enc->Filter2_WX[2], temp[1], temp[0], Filter2CoeffSqr[2], todo);
|
||||
allpass_process(&enc->Filter2_WX[3], temp[0], temp[1], Filter2CoeffSqr[3], todo);
|
||||
for(i = 0;i < todo;i++)
|
||||
D[i] += temp[0][i];
|
||||
|
||||
/* S = 0.9396926*W + 0.1855740*X */
|
||||
for(i = 0;i < todo;i++)
|
||||
temp[0][i] = 0.9396926f*InSamples[0][base+i] +
|
||||
0.1855740f*InSamples[1][base+i];
|
||||
allpass_process(&enc->Filter1_WX[0], temp[1], temp[0], Filter1CoeffSqr[0], todo);
|
||||
allpass_process(&enc->Filter1_WX[1], temp[0], temp[1], Filter1CoeffSqr[1], todo);
|
||||
allpass_process(&enc->Filter1_WX[2], temp[1], temp[0], Filter1CoeffSqr[2], todo);
|
||||
allpass_process(&enc->Filter1_WX[3], temp[0], temp[1], Filter1CoeffSqr[3], todo);
|
||||
S[0] = enc->LastWX;
|
||||
for(i = 1;i < todo;i++)
|
||||
S[i] = temp[0][i-1];
|
||||
enc->LastWX = temp[0][i-1];
|
||||
|
||||
/* Left = (S + D)/2.0 */
|
||||
for(i = 0;i < todo;i++)
|
||||
*(LeftOut++) += (S[i] + D[i]) * 0.5f;
|
||||
/* Right = (S - D)/2.0 */
|
||||
for(i = 0;i < todo;i++)
|
||||
*(RightOut++) += (S[i] - D[i]) * 0.5f;
|
||||
|
||||
base += todo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef UHJFILTER_H
|
||||
#define UHJFILTER_H
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "alMain.h"
|
||||
|
||||
typedef struct AllPassState {
|
||||
ALfloat z[2];
|
||||
} AllPassState;
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
typedef struct Uhj2Encoder {
|
||||
AllPassState Filter1_Y[4];
|
||||
AllPassState Filter2_WX[4];
|
||||
AllPassState Filter1_WX[4];
|
||||
ALfloat LastY, LastWX;
|
||||
} Uhj2Encoder;
|
||||
|
||||
/* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
* signal. The input must use FuMa channel ordering and scaling.
|
||||
*/
|
||||
void EncodeUhj2(Uhj2Encoder *enc, ALfloat *restrict LeftOut, ALfloat *restrict RightOut, ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo);
|
||||
|
||||
#endif /* UHJFILTER_H */
|
||||
@@ -0,0 +1,94 @@
|
||||
#ifndef AL_VECTOR_H
|
||||
#define AL_VECTOR_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <AL/al.h>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
|
||||
#define TYPEDEF_VECTOR(T, N) typedef struct { \
|
||||
size_t Capacity; \
|
||||
size_t Size; \
|
||||
T Data[]; \
|
||||
} _##N; \
|
||||
typedef _##N* N; \
|
||||
typedef const _##N* const_##N;
|
||||
|
||||
#define VECTOR(T) struct { \
|
||||
size_t Capacity; \
|
||||
size_t Size; \
|
||||
T Data[]; \
|
||||
}*
|
||||
|
||||
#define VECTOR_INIT(_x) do { (_x) = NULL; } while(0)
|
||||
#define VECTOR_INIT_STATIC() NULL
|
||||
#define VECTOR_DEINIT(_x) do { al_free((_x)); (_x) = NULL; } while(0)
|
||||
|
||||
#define VECTOR_RESIZE(_x, _s, _c) do { \
|
||||
size_t _size = (_s); \
|
||||
size_t _cap = (_c); \
|
||||
if(_size > _cap) \
|
||||
_cap = _size; \
|
||||
\
|
||||
if(!(_x) && _cap == 0) \
|
||||
break; \
|
||||
\
|
||||
if(((_x) ? (_x)->Capacity : 0) < _cap) \
|
||||
{ \
|
||||
ptrdiff_t data_offset = (_x) ? (char*)((_x)->Data) - (char*)(_x) : \
|
||||
sizeof(*(_x)); \
|
||||
size_t old_size = ((_x) ? (_x)->Size : 0); \
|
||||
void *temp; \
|
||||
\
|
||||
temp = al_calloc(16, data_offset + sizeof((_x)->Data[0])*_cap); \
|
||||
assert(temp != NULL); \
|
||||
if((_x)) \
|
||||
memcpy(((char*)temp)+data_offset, (_x)->Data, \
|
||||
sizeof((_x)->Data[0])*old_size); \
|
||||
\
|
||||
al_free((_x)); \
|
||||
(_x) = temp; \
|
||||
(_x)->Capacity = _cap; \
|
||||
} \
|
||||
(_x)->Size = _size; \
|
||||
} while(0) \
|
||||
|
||||
#define VECTOR_CAPACITY(_x) ((_x) ? (_x)->Capacity : 0)
|
||||
#define VECTOR_SIZE(_x) ((_x) ? (_x)->Size : 0)
|
||||
|
||||
#define VECTOR_BEGIN(_x) ((_x) ? (_x)->Data + 0 : NULL)
|
||||
#define VECTOR_END(_x) ((_x) ? (_x)->Data + (_x)->Size : NULL)
|
||||
|
||||
#define VECTOR_PUSH_BACK(_x, _obj) do { \
|
||||
size_t _pbsize = VECTOR_SIZE(_x)+1; \
|
||||
VECTOR_RESIZE(_x, _pbsize, _pbsize); \
|
||||
(_x)->Data[(_x)->Size-1] = (_obj); \
|
||||
} while(0)
|
||||
#define VECTOR_POP_BACK(_x) ((void)((_x)->Size--))
|
||||
|
||||
#define VECTOR_BACK(_x) ((_x)->Data[(_x)->Size-1])
|
||||
#define VECTOR_FRONT(_x) ((_x)->Data[0])
|
||||
|
||||
#define VECTOR_ELEM(_x, _o) ((_x)->Data[(_o)])
|
||||
|
||||
#define VECTOR_FOR_EACH(_t, _x, _f) do { \
|
||||
_t *_iter = VECTOR_BEGIN((_x)); \
|
||||
_t *_end = VECTOR_END((_x)); \
|
||||
for(;_iter != _end;++_iter) \
|
||||
_f(_iter); \
|
||||
} while(0)
|
||||
|
||||
#define VECTOR_FIND_IF(_i, _t, _x, _f) do { \
|
||||
_t *_iter = VECTOR_BEGIN((_x)); \
|
||||
_t *_end = VECTOR_END((_x)); \
|
||||
for(;_iter != _end;++_iter) \
|
||||
{ \
|
||||
if(_f(_iter)) \
|
||||
break; \
|
||||
} \
|
||||
(_i) = _iter; \
|
||||
} while(0)
|
||||
|
||||
#endif /* AL_VECTOR_H */
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
Portions of this software are licensed under the BSD 3-Clause license.
|
||||
|
||||
Copyright (c) 2015, Archontis Politis
|
||||
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.
|
||||
+1495
-1328
File diff suppressed because it is too large
Load Diff
@@ -1,177 +1,3 @@
|
||||
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,0 +1,185 @@
|
||||
#ifndef _AL_AUXEFFECTSLOT_H_
|
||||
#define _AL_AUXEFFECTSLOT_H_
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alEffect.h"
|
||||
|
||||
#include "atomic.h"
|
||||
#include "align.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ALeffectStateVtable;
|
||||
struct ALeffectslot;
|
||||
|
||||
typedef struct ALeffectState {
|
||||
RefCount Ref;
|
||||
const struct ALeffectStateVtable *vtbl;
|
||||
|
||||
ALfloat (*OutBuffer)[BUFFERSIZE];
|
||||
ALsizei OutChannels;
|
||||
} ALeffectState;
|
||||
|
||||
void ALeffectState_Construct(ALeffectState *state);
|
||||
void ALeffectState_Destruct(ALeffectState *state);
|
||||
|
||||
struct ALeffectStateVtable {
|
||||
void (*const Destruct)(ALeffectState *state);
|
||||
|
||||
ALboolean (*const deviceUpdate)(ALeffectState *state, ALCdevice *device);
|
||||
void (*const update)(ALeffectState *state, const ALCcontext *context, const struct ALeffectslot *slot, const union ALeffectProps *props);
|
||||
void (*const process)(ALeffectState *state, ALsizei samplesToDo, const ALfloat (*restrict samplesIn)[BUFFERSIZE], ALfloat (*restrict samplesOut)[BUFFERSIZE], ALsizei numChannels);
|
||||
|
||||
void (*const Delete)(void *ptr);
|
||||
};
|
||||
|
||||
/* Small hack to use a pointer-to-array types as a normal argument type.
|
||||
* Shouldn't be used directly.
|
||||
*/
|
||||
typedef ALfloat ALfloatBUFFERSIZE[BUFFERSIZE];
|
||||
|
||||
#define DEFINE_ALEFFECTSTATE_VTABLE(T) \
|
||||
DECLARE_THUNK(T, ALeffectState, void, Destruct) \
|
||||
DECLARE_THUNK1(T, ALeffectState, ALboolean, deviceUpdate, ALCdevice*) \
|
||||
DECLARE_THUNK3(T, ALeffectState, void, update, const ALCcontext*, const ALeffectslot*, const ALeffectProps*) \
|
||||
DECLARE_THUNK4(T, ALeffectState, void, process, ALsizei, const ALfloatBUFFERSIZE*restrict, ALfloatBUFFERSIZE*restrict, ALsizei) \
|
||||
static void T##_ALeffectState_Delete(void *ptr) \
|
||||
{ return T##_Delete(STATIC_UPCAST(T, ALeffectState, (ALeffectState*)ptr)); } \
|
||||
\
|
||||
static const struct ALeffectStateVtable T##_ALeffectState_vtable = { \
|
||||
T##_ALeffectState_Destruct, \
|
||||
\
|
||||
T##_ALeffectState_deviceUpdate, \
|
||||
T##_ALeffectState_update, \
|
||||
T##_ALeffectState_process, \
|
||||
\
|
||||
T##_ALeffectState_Delete, \
|
||||
}
|
||||
|
||||
|
||||
struct EffectStateFactoryVtable;
|
||||
|
||||
typedef struct EffectStateFactory {
|
||||
const struct EffectStateFactoryVtable *vtab;
|
||||
} EffectStateFactory;
|
||||
|
||||
struct EffectStateFactoryVtable {
|
||||
ALeffectState *(*const create)(EffectStateFactory *factory);
|
||||
};
|
||||
#define EffectStateFactory_create(x) ((x)->vtab->create((x)))
|
||||
|
||||
#define DEFINE_EFFECTSTATEFACTORY_VTABLE(T) \
|
||||
DECLARE_THUNK(T, EffectStateFactory, ALeffectState*, create) \
|
||||
\
|
||||
static const struct EffectStateFactoryVtable T##_EffectStateFactory_vtable = { \
|
||||
T##_EffectStateFactory_create, \
|
||||
}
|
||||
|
||||
|
||||
#define MAX_EFFECT_CHANNELS (4)
|
||||
|
||||
|
||||
struct ALeffectslotArray {
|
||||
ALsizei count;
|
||||
struct ALeffectslot *slot[];
|
||||
};
|
||||
|
||||
|
||||
struct ALeffectslotProps {
|
||||
ALfloat Gain;
|
||||
ALboolean AuxSendAuto;
|
||||
|
||||
ALenum Type;
|
||||
ALeffectProps Props;
|
||||
|
||||
ALeffectState *State;
|
||||
|
||||
ATOMIC(struct ALeffectslotProps*) next;
|
||||
};
|
||||
|
||||
|
||||
typedef struct ALeffectslot {
|
||||
ALfloat Gain;
|
||||
ALboolean AuxSendAuto;
|
||||
|
||||
struct {
|
||||
ALenum Type;
|
||||
ALeffectProps Props;
|
||||
|
||||
ALeffectState *State;
|
||||
} Effect;
|
||||
|
||||
ATOMIC_FLAG PropsClean;
|
||||
|
||||
RefCount ref;
|
||||
|
||||
ATOMIC(struct ALeffectslotProps*) Update;
|
||||
|
||||
struct {
|
||||
ALfloat Gain;
|
||||
ALboolean AuxSendAuto;
|
||||
|
||||
ALenum EffectType;
|
||||
ALeffectProps EffectProps;
|
||||
ALeffectState *EffectState;
|
||||
|
||||
ALfloat RoomRolloff; /* Added to the source's room rolloff, not multiplied. */
|
||||
ALfloat DecayTime;
|
||||
ALfloat DecayLFRatio;
|
||||
ALfloat DecayHFRatio;
|
||||
ALboolean DecayHFLimit;
|
||||
ALfloat AirAbsorptionGainHF;
|
||||
} Params;
|
||||
|
||||
/* Self ID */
|
||||
ALuint id;
|
||||
|
||||
ALsizei NumChannels;
|
||||
BFChannelConfig ChanMap[MAX_EFFECT_CHANNELS];
|
||||
/* Wet buffer configuration is ACN channel order with N3D scaling:
|
||||
* * Channel 0 is the unattenuated mono signal.
|
||||
* * Channel 1 is OpenAL -X * sqrt(3)
|
||||
* * Channel 2 is OpenAL Y * sqrt(3)
|
||||
* * Channel 3 is OpenAL -Z * sqrt(3)
|
||||
* 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 for first-order device
|
||||
* output (FOAOut).
|
||||
*/
|
||||
alignas(16) ALfloat WetBuffer[MAX_EFFECT_CHANNELS][BUFFERSIZE];
|
||||
} ALeffectslot;
|
||||
|
||||
ALenum InitEffectSlot(ALeffectslot *slot);
|
||||
void DeinitEffectSlot(ALeffectslot *slot);
|
||||
void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context);
|
||||
void UpdateAllEffectSlotProps(ALCcontext *context);
|
||||
ALvoid ReleaseALAuxiliaryEffectSlots(ALCcontext *Context);
|
||||
|
||||
|
||||
EffectStateFactory *NullStateFactory_getFactory(void);
|
||||
EffectStateFactory *ReverbStateFactory_getFactory(void);
|
||||
EffectStateFactory *AutowahStateFactory_getFactory(void);
|
||||
EffectStateFactory *ChorusStateFactory_getFactory(void);
|
||||
EffectStateFactory *CompressorStateFactory_getFactory(void);
|
||||
EffectStateFactory *DistortionStateFactory_getFactory(void);
|
||||
EffectStateFactory *EchoStateFactory_getFactory(void);
|
||||
EffectStateFactory *EqualizerStateFactory_getFactory(void);
|
||||
EffectStateFactory *FlangerStateFactory_getFactory(void);
|
||||
EffectStateFactory *FshifterStateFactory_getFactory(void);
|
||||
EffectStateFactory *ModulatorStateFactory_getFactory(void);
|
||||
EffectStateFactory *PshifterStateFactory_getFactory(void);
|
||||
|
||||
EffectStateFactory *DedicatedStateFactory_getFactory(void);
|
||||
|
||||
|
||||
ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect);
|
||||
|
||||
void ALeffectState_DecRef(ALeffectState *state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
#ifndef _AL_BUFFER_H_
|
||||
#define _AL_BUFFER_H_
|
||||
|
||||
#include "AL/alc.h"
|
||||
#include "AL/al.h"
|
||||
#include "AL/alext.h"
|
||||
|
||||
#include "inprogext.h"
|
||||
#include "atomic.h"
|
||||
#include "rwlock.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* User formats */
|
||||
enum UserFmtType {
|
||||
UserFmtUByte,
|
||||
UserFmtShort,
|
||||
UserFmtFloat,
|
||||
UserFmtDouble,
|
||||
UserFmtMulaw,
|
||||
UserFmtAlaw,
|
||||
UserFmtIMA4,
|
||||
UserFmtMSADPCM,
|
||||
};
|
||||
enum UserFmtChannels {
|
||||
UserFmtMono,
|
||||
UserFmtStereo,
|
||||
UserFmtRear,
|
||||
UserFmtQuad,
|
||||
UserFmtX51, /* (WFX order) */
|
||||
UserFmtX61, /* (WFX order) */
|
||||
UserFmtX71, /* (WFX order) */
|
||||
UserFmtBFormat2D, /* WXY */
|
||||
UserFmtBFormat3D, /* WXYZ */
|
||||
};
|
||||
|
||||
ALsizei BytesFromUserFmt(enum UserFmtType type);
|
||||
ALsizei ChannelsFromUserFmt(enum UserFmtChannels chans);
|
||||
inline ALsizei FrameSizeFromUserFmt(enum UserFmtChannels chans, enum UserFmtType type)
|
||||
{
|
||||
return ChannelsFromUserFmt(chans) * BytesFromUserFmt(type);
|
||||
}
|
||||
|
||||
|
||||
/* Storable formats */
|
||||
enum FmtType {
|
||||
FmtUByte = UserFmtUByte,
|
||||
FmtShort = UserFmtShort,
|
||||
FmtFloat = UserFmtFloat,
|
||||
FmtDouble = UserFmtDouble,
|
||||
FmtMulaw = UserFmtMulaw,
|
||||
FmtAlaw = UserFmtAlaw,
|
||||
};
|
||||
enum FmtChannels {
|
||||
FmtMono = UserFmtMono,
|
||||
FmtStereo = UserFmtStereo,
|
||||
FmtRear = UserFmtRear,
|
||||
FmtQuad = UserFmtQuad,
|
||||
FmtX51 = UserFmtX51,
|
||||
FmtX61 = UserFmtX61,
|
||||
FmtX71 = UserFmtX71,
|
||||
FmtBFormat2D = UserFmtBFormat2D,
|
||||
FmtBFormat3D = UserFmtBFormat3D,
|
||||
};
|
||||
#define MAX_INPUT_CHANNELS (8)
|
||||
|
||||
ALsizei BytesFromFmt(enum FmtType type);
|
||||
ALsizei ChannelsFromFmt(enum FmtChannels chans);
|
||||
inline ALsizei FrameSizeFromFmt(enum FmtChannels chans, enum FmtType type)
|
||||
{
|
||||
return ChannelsFromFmt(chans) * BytesFromFmt(type);
|
||||
}
|
||||
|
||||
|
||||
typedef struct ALbuffer {
|
||||
ALvoid *data;
|
||||
|
||||
ALsizei Frequency;
|
||||
ALbitfieldSOFT Access;
|
||||
ALsizei SampleLen;
|
||||
|
||||
enum FmtChannels FmtChannels;
|
||||
enum FmtType FmtType;
|
||||
ALsizei BytesAlloc;
|
||||
|
||||
enum UserFmtType OriginalType;
|
||||
ALsizei OriginalSize;
|
||||
ALsizei OriginalAlign;
|
||||
|
||||
ALsizei LoopStart;
|
||||
ALsizei LoopEnd;
|
||||
|
||||
ATOMIC(ALsizei) UnpackAlign;
|
||||
ATOMIC(ALsizei) PackAlign;
|
||||
|
||||
ALbitfieldSOFT MappedAccess;
|
||||
ALsizei MappedOffset;
|
||||
ALsizei MappedSize;
|
||||
|
||||
/* Number of times buffer was attached to a source (deletion can only occur when 0) */
|
||||
RefCount ref;
|
||||
|
||||
/* Self ID */
|
||||
ALuint id;
|
||||
} ALbuffer;
|
||||
|
||||
ALvoid ReleaseALBuffers(ALCdevice *device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,213 @@
|
||||
#ifndef _AL_EFFECT_H_
|
||||
#define _AL_EFFECT_H_
|
||||
|
||||
#include "alMain.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ALeffect;
|
||||
|
||||
enum {
|
||||
EAXREVERB_EFFECT = 0,
|
||||
REVERB_EFFECT,
|
||||
AUTOWAH_EFFECT,
|
||||
CHORUS_EFFECT,
|
||||
COMPRESSOR_EFFECT,
|
||||
DISTORTION_EFFECT,
|
||||
ECHO_EFFECT,
|
||||
EQUALIZER_EFFECT,
|
||||
FLANGER_EFFECT,
|
||||
FSHIFTER_EFFECT,
|
||||
MODULATOR_EFFECT,
|
||||
PSHIFTER_EFFECT,
|
||||
DEDICATED_EFFECT,
|
||||
|
||||
MAX_EFFECTS
|
||||
};
|
||||
extern ALboolean DisabledEffects[MAX_EFFECTS];
|
||||
|
||||
extern ALfloat ReverbBoost;
|
||||
|
||||
struct EffectList {
|
||||
const char name[16];
|
||||
int type;
|
||||
ALenum val;
|
||||
};
|
||||
#define EFFECTLIST_SIZE 14
|
||||
extern const struct EffectList EffectList[EFFECTLIST_SIZE];
|
||||
|
||||
|
||||
struct ALeffectVtable {
|
||||
void (*const setParami)(struct ALeffect *effect, ALCcontext *context, ALenum param, ALint val);
|
||||
void (*const setParamiv)(struct ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals);
|
||||
void (*const setParamf)(struct ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val);
|
||||
void (*const setParamfv)(struct ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals);
|
||||
|
||||
void (*const getParami)(const struct ALeffect *effect, ALCcontext *context, ALenum param, ALint *val);
|
||||
void (*const getParamiv)(const struct ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals);
|
||||
void (*const getParamf)(const struct ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val);
|
||||
void (*const getParamfv)(const struct ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals);
|
||||
};
|
||||
|
||||
#define DEFINE_ALEFFECT_VTABLE(T) \
|
||||
const struct ALeffectVtable T##_vtable = { \
|
||||
T##_setParami, T##_setParamiv, \
|
||||
T##_setParamf, T##_setParamfv, \
|
||||
T##_getParami, T##_getParamiv, \
|
||||
T##_getParamf, T##_getParamfv, \
|
||||
}
|
||||
|
||||
extern const struct ALeffectVtable ALeaxreverb_vtable;
|
||||
extern const struct ALeffectVtable ALreverb_vtable;
|
||||
extern const struct ALeffectVtable ALautowah_vtable;
|
||||
extern const struct ALeffectVtable ALchorus_vtable;
|
||||
extern const struct ALeffectVtable ALcompressor_vtable;
|
||||
extern const struct ALeffectVtable ALdistortion_vtable;
|
||||
extern const struct ALeffectVtable ALecho_vtable;
|
||||
extern const struct ALeffectVtable ALequalizer_vtable;
|
||||
extern const struct ALeffectVtable ALflanger_vtable;
|
||||
extern const struct ALeffectVtable ALfshifter_vtable;
|
||||
extern const struct ALeffectVtable ALmodulator_vtable;
|
||||
extern const struct ALeffectVtable ALnull_vtable;
|
||||
extern const struct ALeffectVtable ALpshifter_vtable;
|
||||
extern const struct ALeffectVtable ALdedicated_vtable;
|
||||
|
||||
|
||||
typedef union ALeffectProps {
|
||||
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;
|
||||
} ALeffectProps;
|
||||
|
||||
typedef struct ALeffect {
|
||||
// Effect type (AL_EFFECT_NULL, ...)
|
||||
ALenum type;
|
||||
|
||||
ALeffectProps Props;
|
||||
|
||||
const struct ALeffectVtable *vtab;
|
||||
|
||||
/* Self ID */
|
||||
ALuint id;
|
||||
} ALeffect;
|
||||
#define ALeffect_setParami(o, c, p, v) ((o)->vtab->setParami(o, c, p, v))
|
||||
#define ALeffect_setParamf(o, c, p, v) ((o)->vtab->setParamf(o, c, p, v))
|
||||
#define ALeffect_setParamiv(o, c, p, v) ((o)->vtab->setParamiv(o, c, p, v))
|
||||
#define ALeffect_setParamfv(o, c, p, v) ((o)->vtab->setParamfv(o, c, p, v))
|
||||
#define ALeffect_getParami(o, c, p, v) ((o)->vtab->getParami(o, c, p, v))
|
||||
#define ALeffect_getParamf(o, c, p, v) ((o)->vtab->getParamf(o, c, p, v))
|
||||
#define ALeffect_getParamiv(o, c, p, v) ((o)->vtab->getParamiv(o, c, p, v))
|
||||
#define ALeffect_getParamfv(o, c, p, v) ((o)->vtab->getParamfv(o, c, p, v))
|
||||
|
||||
inline ALboolean IsReverbEffect(ALenum type)
|
||||
{ return type == AL_EFFECT_REVERB || type == AL_EFFECT_EAXREVERB; }
|
||||
|
||||
void InitEffect(ALeffect *effect);
|
||||
void ReleaseALEffects(ALCdevice *device);
|
||||
|
||||
void LoadReverbPreset(const char *name, ALeffect *effect);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef _AL_ERROR_H_
|
||||
#define _AL_ERROR_H_
|
||||
|
||||
#include "alMain.h"
|
||||
#include "logging.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern ALboolean TrapALError;
|
||||
|
||||
void alSetError(ALCcontext *context, ALenum errorCode, const char *msg, ...) DECL_FORMAT(printf, 3, 4);
|
||||
|
||||
#define SETERR_GOTO(ctx, err, lbl, ...) do { \
|
||||
alSetError((ctx), (err), __VA_ARGS__); \
|
||||
goto lbl; \
|
||||
} while(0)
|
||||
|
||||
#define SETERR_RETURN(ctx, err, retval, ...) do { \
|
||||
alSetError((ctx), (err), __VA_ARGS__); \
|
||||
return retval; \
|
||||
} while(0)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,67 @@
|
||||
#ifndef _AL_FILTER_H_
|
||||
#define _AL_FILTER_H_
|
||||
|
||||
#include "AL/alc.h"
|
||||
#include "AL/al.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define LOWPASSFREQREF (5000.0f)
|
||||
#define HIGHPASSFREQREF (250.0f)
|
||||
|
||||
|
||||
struct ALfilter;
|
||||
|
||||
typedef struct ALfilterVtable {
|
||||
void (*const setParami)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALint val);
|
||||
void (*const setParamiv)(struct ALfilter *filter, ALCcontext *context, ALenum param, const ALint *vals);
|
||||
void (*const setParamf)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val);
|
||||
void (*const setParamfv)(struct ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals);
|
||||
|
||||
void (*const getParami)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALint *val);
|
||||
void (*const getParamiv)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALint *vals);
|
||||
void (*const getParamf)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val);
|
||||
void (*const getParamfv)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals);
|
||||
} ALfilterVtable;
|
||||
|
||||
#define DEFINE_ALFILTER_VTABLE(T) \
|
||||
const struct ALfilterVtable T##_vtable = { \
|
||||
T##_setParami, T##_setParamiv, \
|
||||
T##_setParamf, T##_setParamfv, \
|
||||
T##_getParami, T##_getParamiv, \
|
||||
T##_getParamf, T##_getParamfv, \
|
||||
}
|
||||
|
||||
typedef struct ALfilter {
|
||||
// Filter type (AL_FILTER_NULL, ...)
|
||||
ALenum type;
|
||||
|
||||
ALfloat Gain;
|
||||
ALfloat GainHF;
|
||||
ALfloat HFReference;
|
||||
ALfloat GainLF;
|
||||
ALfloat LFReference;
|
||||
|
||||
const struct ALfilterVtable *vtab;
|
||||
|
||||
/* Self ID */
|
||||
ALuint id;
|
||||
} ALfilter;
|
||||
#define ALfilter_setParami(o, c, p, v) ((o)->vtab->setParami(o, c, p, v))
|
||||
#define ALfilter_setParamf(o, c, p, v) ((o)->vtab->setParamf(o, c, p, v))
|
||||
#define ALfilter_setParamiv(o, c, p, v) ((o)->vtab->setParamiv(o, c, p, v))
|
||||
#define ALfilter_setParamfv(o, c, p, v) ((o)->vtab->setParamfv(o, c, p, v))
|
||||
#define ALfilter_getParami(o, c, p, v) ((o)->vtab->getParami(o, c, p, v))
|
||||
#define ALfilter_getParamf(o, c, p, v) ((o)->vtab->getParamf(o, c, p, v))
|
||||
#define ALfilter_getParamiv(o, c, p, v) ((o)->vtab->getParamiv(o, c, p, v))
|
||||
#define ALfilter_getParamfv(o, c, p, v) ((o)->vtab->getParamfv(o, c, p, v))
|
||||
|
||||
void ReleaseALFilters(ALCdevice *device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,67 @@
|
||||
#ifndef _AL_LISTENER_H_
|
||||
#define _AL_LISTENER_H_
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ALcontextProps {
|
||||
ALfloat DopplerFactor;
|
||||
ALfloat DopplerVelocity;
|
||||
ALfloat SpeedOfSound;
|
||||
ALboolean SourceDistanceModel;
|
||||
enum DistanceModel DistanceModel;
|
||||
ALfloat MetersPerUnit;
|
||||
|
||||
ATOMIC(struct ALcontextProps*) next;
|
||||
};
|
||||
|
||||
struct ALlistenerProps {
|
||||
ALfloat Position[3];
|
||||
ALfloat Velocity[3];
|
||||
ALfloat Forward[3];
|
||||
ALfloat Up[3];
|
||||
ALfloat Gain;
|
||||
|
||||
ATOMIC(struct ALlistenerProps*) next;
|
||||
};
|
||||
|
||||
typedef struct ALlistener {
|
||||
alignas(16) ALfloat Position[3];
|
||||
ALfloat Velocity[3];
|
||||
ALfloat Forward[3];
|
||||
ALfloat Up[3];
|
||||
ALfloat Gain;
|
||||
|
||||
ATOMIC_FLAG PropsClean;
|
||||
|
||||
/* Pointer to the most recent property values that are awaiting an update.
|
||||
*/
|
||||
ATOMIC(struct ALlistenerProps*) Update;
|
||||
|
||||
struct {
|
||||
aluMatrixf Matrix;
|
||||
aluVector Velocity;
|
||||
|
||||
ALfloat Gain;
|
||||
ALfloat MetersPerUnit;
|
||||
|
||||
ALfloat DopplerFactor;
|
||||
ALfloat SpeedOfSound; /* in units per sec! */
|
||||
ALfloat ReverbSpeedOfSound; /* in meters per sec! */
|
||||
|
||||
ALboolean SourceDistanceModel;
|
||||
enum DistanceModel DistanceModel;
|
||||
} Params;
|
||||
} ALlistener;
|
||||
|
||||
void UpdateListenerProps(ALCcontext *context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,920 @@
|
||||
#ifndef AL_MAIN_H
|
||||
#define AL_MAIN_H
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#ifdef HAVE_STRINGS_H
|
||||
#include <strings.h>
|
||||
#endif
|
||||
#ifdef HAVE_INTRIN_H
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
|
||||
#include "inprogext.h"
|
||||
#include "logging.h"
|
||||
#include "polymorphism.h"
|
||||
#include "static_assert.h"
|
||||
#include "align.h"
|
||||
#include "atomic.h"
|
||||
#include "vector.h"
|
||||
#include "alstring.h"
|
||||
#include "almalloc.h"
|
||||
#include "threads.h"
|
||||
|
||||
|
||||
#if defined(_WIN64)
|
||||
#define SZFMT "%I64u"
|
||||
#elif defined(_WIN32)
|
||||
#define SZFMT "%u"
|
||||
#else
|
||||
#define SZFMT "%zu"
|
||||
#endif
|
||||
|
||||
#ifdef __has_builtin
|
||||
#define HAS_BUILTIN __has_builtin
|
||||
#else
|
||||
#define HAS_BUILTIN(x) (0)
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
/* LIKELY optimizes the case where the condition is true. The condition is not
|
||||
* required to be true, but it can result in more optimal code for the true
|
||||
* path at the expense of a less optimal false path.
|
||||
*/
|
||||
#define LIKELY(x) __builtin_expect(!!(x), !0)
|
||||
/* The opposite of LIKELY, optimizing the case where the condition is false. */
|
||||
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
|
||||
/* Unlike LIKELY, ASSUME requires the condition to be true or else it invokes
|
||||
* undefined behavior. It's essentially an assert without actually checking the
|
||||
* condition at run-time, allowing for stronger optimizations than LIKELY.
|
||||
*/
|
||||
#if HAS_BUILTIN(__builtin_assume)
|
||||
#define ASSUME __builtin_assume
|
||||
#else
|
||||
#define ASSUME(x) do { if(!(x)) __builtin_unreachable(); } while(0)
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#define LIKELY(x) (!!(x))
|
||||
#define UNLIKELY(x) (!!(x))
|
||||
#ifdef _MSC_VER
|
||||
#define ASSUME __assume
|
||||
#else
|
||||
#define ASSUME(x) ((void)0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef UINT64_MAX
|
||||
#define UINT64_MAX U64(18446744073709551615)
|
||||
#endif
|
||||
|
||||
#ifndef UNUSED
|
||||
#if defined(__cplusplus)
|
||||
#define UNUSED(x)
|
||||
#elif defined(__GNUC__)
|
||||
#define UNUSED(x) UNUSED_##x __attribute__((unused))
|
||||
#elif defined(__LCLINT__)
|
||||
#define UNUSED(x) /*@unused@*/ x
|
||||
#else
|
||||
#define UNUSED(x) x
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Calculates the size of a struct with N elements of a flexible array member.
|
||||
* GCC and Clang allow offsetof(Type, fam[N]) for this, but MSVC seems to have
|
||||
* trouble, so a bit more verbose workaround is needed.
|
||||
*/
|
||||
#define FAM_SIZE(T, M, N) (offsetof(T, M) + sizeof(((T*)NULL)->M[0])*(N))
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef ALint64SOFT ALint64;
|
||||
typedef ALuint64SOFT ALuint64;
|
||||
|
||||
#ifndef U64
|
||||
#if defined(_MSC_VER)
|
||||
#define U64(x) ((ALuint64)(x##ui64))
|
||||
#elif SIZEOF_LONG == 8
|
||||
#define U64(x) ((ALuint64)(x##ul))
|
||||
#elif SIZEOF_LONG_LONG == 8
|
||||
#define U64(x) ((ALuint64)(x##ull))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef I64
|
||||
#if defined(_MSC_VER)
|
||||
#define I64(x) ((ALint64)(x##i64))
|
||||
#elif SIZEOF_LONG == 8
|
||||
#define I64(x) ((ALint64)(x##l))
|
||||
#elif SIZEOF_LONG_LONG == 8
|
||||
#define I64(x) ((ALint64)(x##ll))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Define a CTZ64 macro (count trailing zeros, for 64-bit integers). The result
|
||||
* is *UNDEFINED* if the value is 0.
|
||||
*/
|
||||
#ifdef __GNUC__
|
||||
|
||||
#if SIZEOF_LONG == 8
|
||||
#define CTZ64 __builtin_ctzl
|
||||
#else
|
||||
#define CTZ64 __builtin_ctzll
|
||||
#endif
|
||||
|
||||
#elif defined(HAVE_BITSCANFORWARD64_INTRINSIC)
|
||||
|
||||
inline int msvc64_ctz64(ALuint64 v)
|
||||
{
|
||||
unsigned long idx = 64;
|
||||
_BitScanForward64(&idx, v);
|
||||
return (int)idx;
|
||||
}
|
||||
#define CTZ64 msvc64_ctz64
|
||||
|
||||
#elif defined(HAVE_BITSCANFORWARD_INTRINSIC)
|
||||
|
||||
inline int msvc_ctz64(ALuint64 v)
|
||||
{
|
||||
unsigned long idx = 64;
|
||||
if(!_BitScanForward(&idx, v&0xffffffff))
|
||||
{
|
||||
if(_BitScanForward(&idx, v>>32))
|
||||
idx += 32;
|
||||
}
|
||||
return (int)idx;
|
||||
}
|
||||
#define CTZ64 msvc_ctz64
|
||||
|
||||
#else
|
||||
|
||||
/* There be black magics here. The popcnt64 method is derived from
|
||||
* https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
|
||||
* while the ctz-utilizing-popcnt algorithm is shown here
|
||||
* http://www.hackersdelight.org/hdcodetxt/ntz.c.txt
|
||||
* as the ntz2 variant. These likely aren't the most efficient methods, but
|
||||
* they're good enough if the GCC or MSVC intrinsics aren't available.
|
||||
*/
|
||||
inline int fallback_popcnt64(ALuint64 v)
|
||||
{
|
||||
v = v - ((v >> 1) & U64(0x5555555555555555));
|
||||
v = (v & U64(0x3333333333333333)) + ((v >> 2) & U64(0x3333333333333333));
|
||||
v = (v + (v >> 4)) & U64(0x0f0f0f0f0f0f0f0f);
|
||||
return (int)((v * U64(0x0101010101010101)) >> 56);
|
||||
}
|
||||
|
||||
inline int fallback_ctz64(ALuint64 value)
|
||||
{
|
||||
return fallback_popcnt64(~value & (value - 1));
|
||||
}
|
||||
#define CTZ64 fallback_ctz64
|
||||
#endif
|
||||
|
||||
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__)
|
||||
#define IS_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
#else
|
||||
static const union {
|
||||
ALuint u;
|
||||
ALubyte b[sizeof(ALuint)];
|
||||
} EndianTest = { 1 };
|
||||
#define IS_LITTLE_ENDIAN (EndianTest.b[0] == 1)
|
||||
#endif
|
||||
|
||||
#define COUNTOF(x) (sizeof(x) / sizeof(0[x]))
|
||||
|
||||
|
||||
struct ll_ringbuffer;
|
||||
struct Hrtf;
|
||||
struct HrtfEntry;
|
||||
struct DirectHrtfState;
|
||||
struct FrontStablizer;
|
||||
struct Compressor;
|
||||
struct ALCbackend;
|
||||
struct ALbuffer;
|
||||
struct ALeffect;
|
||||
struct ALfilter;
|
||||
struct ALsource;
|
||||
struct ALcontextProps;
|
||||
struct ALlistenerProps;
|
||||
struct ALvoiceProps;
|
||||
struct ALeffectslotProps;
|
||||
|
||||
|
||||
#define DEFAULT_OUTPUT_RATE (44100)
|
||||
#define MIN_OUTPUT_RATE (8000)
|
||||
|
||||
|
||||
/* Find the next power-of-2 for non-power-of-2 numbers. */
|
||||
inline ALuint NextPowerOf2(ALuint value)
|
||||
{
|
||||
if(value > 0)
|
||||
{
|
||||
value--;
|
||||
value |= value>>1;
|
||||
value |= value>>2;
|
||||
value |= value>>4;
|
||||
value |= value>>8;
|
||||
value |= value>>16;
|
||||
}
|
||||
return value+1;
|
||||
}
|
||||
|
||||
/** Round up a value to the next multiple. */
|
||||
inline size_t RoundUp(size_t value, size_t r)
|
||||
{
|
||||
value += r-1;
|
||||
return value - (value%r);
|
||||
}
|
||||
|
||||
/* Fast float-to-int conversion. No particular rounding mode is assumed; the
|
||||
* IEEE-754 default is round-to-nearest with ties-to-even, though an app could
|
||||
* change it on its own threads. On some systems, a truncating conversion may
|
||||
* always be the fastest method.
|
||||
*/
|
||||
inline ALint fastf2i(ALfloat f)
|
||||
{
|
||||
#if defined(HAVE_INTRIN_H) && ((defined(_M_IX86_FP) && (_M_IX86_FP > 0)) || defined(_M_X64))
|
||||
return _mm_cvt_ss2si(_mm_set1_ps(f));
|
||||
|
||||
#elif defined(_MSC_VER) && defined(_M_IX86_FP)
|
||||
|
||||
ALint i;
|
||||
__asm fld f
|
||||
__asm fistp i
|
||||
return i;
|
||||
|
||||
#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
|
||||
|
||||
ALint i;
|
||||
#ifdef __SSE_MATH__
|
||||
__asm__("cvtss2si %1, %0" : "=r"(i) : "x"(f));
|
||||
#else
|
||||
__asm__ __volatile__("fistpl %0" : "=m"(i) : "t"(f) : "st");
|
||||
#endif
|
||||
return i;
|
||||
|
||||
/* On GCC when compiling with -fno-math-errno, lrintf can be inlined to
|
||||
* some simple instructions. Clang does not inline it, always generating a
|
||||
* libc call, while MSVC's implementation is horribly slow, so always fall
|
||||
* back to a normal integer conversion for them.
|
||||
*/
|
||||
#elif defined(HAVE_LRINTF) && !defined(_MSC_VER) && !defined(__clang__)
|
||||
|
||||
return lrintf(f);
|
||||
|
||||
#else
|
||||
|
||||
return (ALint)f;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Converts float-to-int using standard behavior (truncation). */
|
||||
inline int float2int(float f)
|
||||
{
|
||||
#if ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
|
||||
!defined(__SSE_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0)
|
||||
ALint sign, shift, mant;
|
||||
union {
|
||||
ALfloat f;
|
||||
ALint i;
|
||||
} conv;
|
||||
|
||||
conv.f = f;
|
||||
sign = (conv.i>>31) | 1;
|
||||
shift = ((conv.i>>23)&0xff) - (127+23);
|
||||
|
||||
/* Over/underflow */
|
||||
if(UNLIKELY(shift >= 31 || shift < -23))
|
||||
return 0;
|
||||
|
||||
mant = (conv.i&0x7fffff) | 0x800000;
|
||||
if(LIKELY(shift < 0))
|
||||
return (mant >> -shift) * sign;
|
||||
return (mant << shift) * sign;
|
||||
|
||||
#else
|
||||
|
||||
return (ALint)f;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Rounds a float to the nearest integral value, according to the current
|
||||
* rounding mode. This is essentially an inlined version of rintf, although
|
||||
* makes fewer promises (e.g. -0 or -0.25 rounded to 0 may result in +0).
|
||||
*/
|
||||
inline float fast_roundf(float f)
|
||||
{
|
||||
#if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
|
||||
!defined(__SSE_MATH__)
|
||||
|
||||
float out;
|
||||
__asm__ __volatile__("frndint" : "=t"(out) : "0"(f));
|
||||
return out;
|
||||
|
||||
#else
|
||||
|
||||
/* Integral limit, where sub-integral precision is not available for
|
||||
* floats.
|
||||
*/
|
||||
static const float ilim[2] = {
|
||||
8388608.0f /* 0x1.0p+23 */,
|
||||
-8388608.0f /* -0x1.0p+23 */
|
||||
};
|
||||
ALuint sign, expo;
|
||||
union {
|
||||
ALfloat f;
|
||||
ALuint i;
|
||||
} conv;
|
||||
|
||||
conv.f = f;
|
||||
sign = (conv.i>>31)&0x01;
|
||||
expo = (conv.i>>23)&0xff;
|
||||
|
||||
if(UNLIKELY(expo >= 150/*+23*/))
|
||||
{
|
||||
/* An exponent (base-2) of 23 or higher is incapable of sub-integral
|
||||
* precision, so it's already an integral value. We don't need to worry
|
||||
* about infinity or NaN here.
|
||||
*/
|
||||
return f;
|
||||
}
|
||||
/* Adding the integral limit to the value (with a matching sign) forces a
|
||||
* result that has no sub-integral precision, and is consequently forced to
|
||||
* round to an integral value. Removing the integral limit then restores
|
||||
* the initial value rounded to the integral. The compiler should not
|
||||
* optimize this out because of non-associative rules on floating-point
|
||||
* math (as long as you don't use -fassociative-math,
|
||||
* -funsafe-math-optimizations, -ffast-math, or -Ofast, in which case this
|
||||
* may break).
|
||||
*/
|
||||
f += ilim[sign];
|
||||
return f - ilim[sign];
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
enum DevProbe {
|
||||
ALL_DEVICE_PROBE,
|
||||
CAPTURE_DEVICE_PROBE
|
||||
};
|
||||
|
||||
|
||||
enum DistanceModel {
|
||||
InverseDistanceClamped = AL_INVERSE_DISTANCE_CLAMPED,
|
||||
LinearDistanceClamped = AL_LINEAR_DISTANCE_CLAMPED,
|
||||
ExponentDistanceClamped = AL_EXPONENT_DISTANCE_CLAMPED,
|
||||
InverseDistance = AL_INVERSE_DISTANCE,
|
||||
LinearDistance = AL_LINEAR_DISTANCE,
|
||||
ExponentDistance = AL_EXPONENT_DISTANCE,
|
||||
DisableDistance = AL_NONE,
|
||||
|
||||
DefaultDistanceModel = InverseDistanceClamped
|
||||
};
|
||||
|
||||
enum Channel {
|
||||
FrontLeft = 0,
|
||||
FrontRight,
|
||||
FrontCenter,
|
||||
LFE,
|
||||
BackLeft,
|
||||
BackRight,
|
||||
BackCenter,
|
||||
SideLeft,
|
||||
SideRight,
|
||||
|
||||
UpperFrontLeft,
|
||||
UpperFrontRight,
|
||||
UpperBackLeft,
|
||||
UpperBackRight,
|
||||
LowerFrontLeft,
|
||||
LowerFrontRight,
|
||||
LowerBackLeft,
|
||||
LowerBackRight,
|
||||
|
||||
Aux0,
|
||||
Aux1,
|
||||
Aux2,
|
||||
Aux3,
|
||||
Aux4,
|
||||
Aux5,
|
||||
Aux6,
|
||||
Aux7,
|
||||
Aux8,
|
||||
Aux9,
|
||||
Aux10,
|
||||
Aux11,
|
||||
Aux12,
|
||||
Aux13,
|
||||
Aux14,
|
||||
Aux15,
|
||||
|
||||
InvalidChannel
|
||||
};
|
||||
|
||||
|
||||
/* Device formats */
|
||||
enum DevFmtType {
|
||||
DevFmtByte = ALC_BYTE_SOFT,
|
||||
DevFmtUByte = ALC_UNSIGNED_BYTE_SOFT,
|
||||
DevFmtShort = ALC_SHORT_SOFT,
|
||||
DevFmtUShort = ALC_UNSIGNED_SHORT_SOFT,
|
||||
DevFmtInt = ALC_INT_SOFT,
|
||||
DevFmtUInt = ALC_UNSIGNED_INT_SOFT,
|
||||
DevFmtFloat = ALC_FLOAT_SOFT,
|
||||
|
||||
DevFmtTypeDefault = DevFmtFloat
|
||||
};
|
||||
enum DevFmtChannels {
|
||||
DevFmtMono = ALC_MONO_SOFT,
|
||||
DevFmtStereo = ALC_STEREO_SOFT,
|
||||
DevFmtQuad = ALC_QUAD_SOFT,
|
||||
DevFmtX51 = ALC_5POINT1_SOFT,
|
||||
DevFmtX61 = ALC_6POINT1_SOFT,
|
||||
DevFmtX71 = ALC_7POINT1_SOFT,
|
||||
DevFmtAmbi3D = ALC_BFORMAT3D_SOFT,
|
||||
|
||||
/* Similar to 5.1, except using rear channels instead of sides */
|
||||
DevFmtX51Rear = 0x80000000,
|
||||
|
||||
DevFmtChannelsDefault = DevFmtStereo
|
||||
};
|
||||
#define MAX_OUTPUT_CHANNELS (16)
|
||||
|
||||
ALsizei BytesFromDevFmt(enum DevFmtType type);
|
||||
ALsizei ChannelsFromDevFmt(enum DevFmtChannels chans, ALsizei ambiorder);
|
||||
inline ALsizei FrameSizeFromDevFmt(enum DevFmtChannels chans, enum DevFmtType type, ALsizei ambiorder)
|
||||
{
|
||||
return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type);
|
||||
}
|
||||
|
||||
enum AmbiLayout {
|
||||
AmbiLayout_FuMa = ALC_FUMA_SOFT, /* FuMa channel order */
|
||||
AmbiLayout_ACN = ALC_ACN_SOFT, /* ACN channel order */
|
||||
|
||||
AmbiLayout_Default = AmbiLayout_ACN
|
||||
};
|
||||
|
||||
enum AmbiNorm {
|
||||
AmbiNorm_FuMa = ALC_FUMA_SOFT, /* FuMa normalization */
|
||||
AmbiNorm_SN3D = ALC_SN3D_SOFT, /* SN3D normalization */
|
||||
AmbiNorm_N3D = ALC_N3D_SOFT, /* N3D normalization */
|
||||
|
||||
AmbiNorm_Default = AmbiNorm_SN3D
|
||||
};
|
||||
|
||||
|
||||
enum DeviceType {
|
||||
Playback,
|
||||
Capture,
|
||||
Loopback
|
||||
};
|
||||
|
||||
|
||||
enum RenderMode {
|
||||
NormalRender,
|
||||
StereoPair,
|
||||
HrtfRender
|
||||
};
|
||||
|
||||
|
||||
/* The maximum number of Ambisonics coefficients. 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
|
||||
#define MAX_AMBI_COEFFS ((MAX_AMBI_ORDER+1) * (MAX_AMBI_ORDER+1))
|
||||
|
||||
/* 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 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). This is ACN ordering, with bit 0 being ACN 0, etc.
|
||||
*/
|
||||
#define AMBI_PERIPHONIC_MASK (0xfe7ce4)
|
||||
|
||||
/* The maximum number of Ambisonic coefficients for 2D (non-periphonic)
|
||||
* representation. This is 2 per each order above zero-order, plus 1 for zero-
|
||||
* order. Or simply, o*2 + 1.
|
||||
*/
|
||||
#define MAX_AMBI2D_COEFFS (MAX_AMBI_ORDER*2 + 1)
|
||||
|
||||
|
||||
typedef ALfloat ChannelConfig[MAX_AMBI_COEFFS];
|
||||
typedef struct BFChannelConfig {
|
||||
ALfloat Scale;
|
||||
ALsizei Index;
|
||||
} BFChannelConfig;
|
||||
|
||||
typedef union AmbiConfig {
|
||||
/* Ambisonic coefficients for mixing to the dry buffer. */
|
||||
ChannelConfig Coeffs[MAX_OUTPUT_CHANNELS];
|
||||
/* Coefficient channel mapping for mixing to the dry buffer. */
|
||||
BFChannelConfig Map[MAX_OUTPUT_CHANNELS];
|
||||
} AmbiConfig;
|
||||
|
||||
|
||||
typedef struct BufferSubList {
|
||||
ALuint64 FreeMask;
|
||||
struct ALbuffer *Buffers; /* 64 */
|
||||
} BufferSubList;
|
||||
TYPEDEF_VECTOR(BufferSubList, vector_BufferSubList)
|
||||
|
||||
typedef struct EffectSubList {
|
||||
ALuint64 FreeMask;
|
||||
struct ALeffect *Effects; /* 64 */
|
||||
} EffectSubList;
|
||||
TYPEDEF_VECTOR(EffectSubList, vector_EffectSubList)
|
||||
|
||||
typedef struct FilterSubList {
|
||||
ALuint64 FreeMask;
|
||||
struct ALfilter *Filters; /* 64 */
|
||||
} FilterSubList;
|
||||
TYPEDEF_VECTOR(FilterSubList, vector_FilterSubList)
|
||||
|
||||
typedef struct SourceSubList {
|
||||
ALuint64 FreeMask;
|
||||
struct ALsource *Sources; /* 64 */
|
||||
} SourceSubList;
|
||||
TYPEDEF_VECTOR(SourceSubList, vector_SourceSubList)
|
||||
|
||||
/* Effect slots are rather large, and apps aren't likely to have more than one
|
||||
* or two (let alone 64), so hold them individually.
|
||||
*/
|
||||
typedef struct ALeffectslot *ALeffectslotPtr;
|
||||
TYPEDEF_VECTOR(ALeffectslotPtr, vector_ALeffectslotPtr)
|
||||
|
||||
|
||||
typedef struct EnumeratedHrtf {
|
||||
al_string name;
|
||||
|
||||
struct HrtfEntry *hrtf;
|
||||
} EnumeratedHrtf;
|
||||
TYPEDEF_VECTOR(EnumeratedHrtf, vector_EnumeratedHrtf)
|
||||
|
||||
|
||||
/* Maximum delay in samples for speaker distance compensation. */
|
||||
#define MAX_DELAY_LENGTH 1024
|
||||
|
||||
typedef struct DistanceComp {
|
||||
ALfloat Gain;
|
||||
ALsizei Length; /* Valid range is [0...MAX_DELAY_LENGTH). */
|
||||
ALfloat *Buffer;
|
||||
} DistanceComp;
|
||||
|
||||
/* Size for temporary storage of buffer data, in ALfloats. Larger values need
|
||||
* more memory, while smaller values may need more iterations. The value needs
|
||||
* to be a sensible size, however, as it constrains the max stepping value used
|
||||
* for mixing, as well as the maximum number of samples per mixing iteration.
|
||||
*/
|
||||
#define BUFFERSIZE 2048
|
||||
|
||||
typedef struct MixParams {
|
||||
AmbiConfig Ambi;
|
||||
/* Number of coefficients in each Ambi.Coeffs to mix together (4 for first-
|
||||
* order, 9 for second-order, etc). If the count is 0, Ambi.Map is used
|
||||
* instead to map each output to a coefficient index.
|
||||
*/
|
||||
ALsizei CoeffCount;
|
||||
|
||||
ALfloat (*Buffer)[BUFFERSIZE];
|
||||
ALsizei NumChannels;
|
||||
} MixParams;
|
||||
|
||||
typedef struct RealMixParams {
|
||||
enum Channel ChannelName[MAX_OUTPUT_CHANNELS];
|
||||
|
||||
ALfloat (*Buffer)[BUFFERSIZE];
|
||||
ALsizei NumChannels;
|
||||
} RealMixParams;
|
||||
|
||||
typedef void (*POSTPROCESS)(ALCdevice *device, ALsizei SamplesToDo);
|
||||
|
||||
struct ALCdevice_struct {
|
||||
RefCount ref;
|
||||
|
||||
ATOMIC(ALenum) Connected;
|
||||
enum DeviceType Type;
|
||||
|
||||
ALuint Frequency;
|
||||
ALuint UpdateSize;
|
||||
ALuint NumUpdates;
|
||||
enum DevFmtChannels FmtChans;
|
||||
enum DevFmtType FmtType;
|
||||
ALboolean IsHeadphones;
|
||||
ALsizei AmbiOrder;
|
||||
/* For DevFmtAmbi* output only, specifies the channel order and
|
||||
* normalization.
|
||||
*/
|
||||
enum AmbiLayout AmbiLayout;
|
||||
enum AmbiNorm AmbiScale;
|
||||
|
||||
ALCenum LimiterState;
|
||||
|
||||
al_string DeviceName;
|
||||
|
||||
ATOMIC(ALCenum) LastError;
|
||||
|
||||
// Maximum number of sources that can be created
|
||||
ALuint SourcesMax;
|
||||
// Maximum number of slots that can be created
|
||||
ALuint AuxiliaryEffectSlotMax;
|
||||
|
||||
ALCuint NumMonoSources;
|
||||
ALCuint NumStereoSources;
|
||||
ALsizei NumAuxSends;
|
||||
|
||||
// Map of Buffers for this device
|
||||
vector_BufferSubList BufferList;
|
||||
almtx_t BufferLock;
|
||||
|
||||
// Map of Effects for this device
|
||||
vector_EffectSubList EffectList;
|
||||
almtx_t EffectLock;
|
||||
|
||||
// Map of Filters for this device
|
||||
vector_FilterSubList FilterList;
|
||||
almtx_t FilterLock;
|
||||
|
||||
POSTPROCESS PostProcess;
|
||||
|
||||
/* HRTF state and info */
|
||||
struct DirectHrtfState *Hrtf;
|
||||
al_string HrtfName;
|
||||
struct Hrtf *HrtfHandle;
|
||||
vector_EnumeratedHrtf HrtfList;
|
||||
ALCenum HrtfStatus;
|
||||
|
||||
/* UHJ encoder state */
|
||||
struct Uhj2Encoder *Uhj_Encoder;
|
||||
|
||||
/* High quality Ambisonic decoder */
|
||||
struct BFormatDec *AmbiDecoder;
|
||||
|
||||
/* Stereo-to-binaural filter */
|
||||
struct bs2b *Bs2b;
|
||||
|
||||
/* First-order ambisonic upsampler for higher-order output */
|
||||
struct AmbiUpsampler *AmbiUp;
|
||||
|
||||
/* Rendering mode. */
|
||||
enum RenderMode Render_Mode;
|
||||
|
||||
// Device flags
|
||||
ALuint Flags;
|
||||
|
||||
ALuint64 ClockBase;
|
||||
ALuint SamplesDone;
|
||||
ALuint FixedLatency;
|
||||
|
||||
/* Temp storage used for mixer processing. */
|
||||
alignas(16) ALfloat TempBuffer[4][BUFFERSIZE];
|
||||
|
||||
/* The "dry" path corresponds to the main output. */
|
||||
MixParams Dry;
|
||||
ALsizei NumChannelsPerOrder[MAX_AMBI_ORDER+1];
|
||||
|
||||
/* First-order ambisonics output, to be upsampled to the dry buffer if different. */
|
||||
MixParams FOAOut;
|
||||
|
||||
/* "Real" output, which will be written to the device buffer. May alias the
|
||||
* dry buffer.
|
||||
*/
|
||||
RealMixParams RealOut;
|
||||
|
||||
struct FrontStablizer *Stablizer;
|
||||
|
||||
struct Compressor *Limiter;
|
||||
|
||||
/* The average speaker distance as determined by the ambdec configuration
|
||||
* (or alternatively, by the NFC-HOA reference delay). Only used for NFC.
|
||||
*/
|
||||
ALfloat AvgSpeakerDist;
|
||||
|
||||
/* Delay buffers used to compensate for speaker distances. */
|
||||
DistanceComp ChannelDelay[MAX_OUTPUT_CHANNELS];
|
||||
|
||||
/* Dithering control. */
|
||||
ALfloat DitherDepth;
|
||||
ALuint DitherSeed;
|
||||
|
||||
/* Running count of the mixer invocations, in 31.1 fixed point. This
|
||||
* actually increments *twice* when mixing, first at the start and then at
|
||||
* the end, so the bottom bit indicates if the device is currently mixing
|
||||
* and the upper bits indicates how many mixes have been done.
|
||||
*/
|
||||
RefCount MixCount;
|
||||
|
||||
// Contexts created on this device
|
||||
ATOMIC(ALCcontext*) ContextList;
|
||||
|
||||
almtx_t BackendLock;
|
||||
struct ALCbackend *Backend;
|
||||
|
||||
ATOMIC(ALCdevice*) next;
|
||||
};
|
||||
|
||||
// Frequency was requested by the app or config file
|
||||
#define DEVICE_FREQUENCY_REQUEST (1u<<1)
|
||||
// Channel configuration was requested by the config file
|
||||
#define DEVICE_CHANNELS_REQUEST (1u<<2)
|
||||
// Sample type was requested by the config file
|
||||
#define DEVICE_SAMPLE_TYPE_REQUEST (1u<<3)
|
||||
|
||||
// Specifies if the DSP is paused at user request
|
||||
#define DEVICE_PAUSED (1u<<30)
|
||||
|
||||
// Specifies if the device is currently running
|
||||
#define DEVICE_RUNNING (1u<<31)
|
||||
|
||||
|
||||
/* Nanosecond resolution for the device clock time. */
|
||||
#define DEVICE_CLOCK_RES U64(1000000000)
|
||||
|
||||
|
||||
/* Must be less than 15 characters (16 including terminating null) for
|
||||
* compatibility with pthread_setname_np limitations. */
|
||||
#define MIXER_THREAD_NAME "alsoft-mixer"
|
||||
|
||||
#define RECORD_THREAD_NAME "alsoft-record"
|
||||
|
||||
|
||||
enum {
|
||||
/* End event thread processing. */
|
||||
EventType_KillThread = 0,
|
||||
|
||||
/* User event types. */
|
||||
EventType_SourceStateChange = 1<<0,
|
||||
EventType_BufferCompleted = 1<<1,
|
||||
EventType_Error = 1<<2,
|
||||
EventType_Performance = 1<<3,
|
||||
EventType_Deprecated = 1<<4,
|
||||
EventType_Disconnected = 1<<5,
|
||||
|
||||
/* Internal events. */
|
||||
EventType_ReleaseEffectState = 65536,
|
||||
};
|
||||
|
||||
typedef struct AsyncEvent {
|
||||
unsigned int EnumType;
|
||||
union {
|
||||
char dummy;
|
||||
struct {
|
||||
ALenum type;
|
||||
ALuint id;
|
||||
ALuint param;
|
||||
ALchar msg[1008];
|
||||
} user;
|
||||
struct ALeffectState *EffectState;
|
||||
} u;
|
||||
} AsyncEvent;
|
||||
#define ASYNC_EVENT(t) { t, { 0 } }
|
||||
|
||||
struct ALCcontext_struct {
|
||||
RefCount ref;
|
||||
|
||||
struct ALlistener *Listener;
|
||||
|
||||
vector_SourceSubList SourceList;
|
||||
ALuint NumSources;
|
||||
almtx_t SourceLock;
|
||||
|
||||
vector_ALeffectslotPtr EffectSlotList;
|
||||
almtx_t EffectSlotLock;
|
||||
|
||||
ATOMIC(ALenum) LastError;
|
||||
|
||||
enum DistanceModel DistanceModel;
|
||||
ALboolean SourceDistanceModel;
|
||||
|
||||
ALfloat DopplerFactor;
|
||||
ALfloat DopplerVelocity;
|
||||
ALfloat SpeedOfSound;
|
||||
ALfloat MetersPerUnit;
|
||||
|
||||
ATOMIC_FLAG PropsClean;
|
||||
ATOMIC(ALenum) DeferUpdates;
|
||||
|
||||
almtx_t PropLock;
|
||||
|
||||
/* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
|
||||
* indicates if updates are currently happening).
|
||||
*/
|
||||
RefCount UpdateCount;
|
||||
ATOMIC(ALenum) HoldUpdates;
|
||||
|
||||
ALfloat GainBoost;
|
||||
|
||||
ATOMIC(struct ALcontextProps*) Update;
|
||||
|
||||
/* Linked lists of unused property containers, free to use for future
|
||||
* updates.
|
||||
*/
|
||||
ATOMIC(struct ALcontextProps*) FreeContextProps;
|
||||
ATOMIC(struct ALlistenerProps*) FreeListenerProps;
|
||||
ATOMIC(struct ALvoiceProps*) FreeVoiceProps;
|
||||
ATOMIC(struct ALeffectslotProps*) FreeEffectslotProps;
|
||||
|
||||
struct ALvoice **Voices;
|
||||
ALsizei VoiceCount;
|
||||
ALsizei MaxVoices;
|
||||
|
||||
ATOMIC(struct ALeffectslotArray*) ActiveAuxSlots;
|
||||
|
||||
althrd_t EventThread;
|
||||
alsem_t EventSem;
|
||||
struct ll_ringbuffer *AsyncEvents;
|
||||
ATOMIC(ALbitfieldSOFT) EnabledEvts;
|
||||
almtx_t EventCbLock;
|
||||
ALEVENTPROCSOFT EventCb;
|
||||
void *EventParam;
|
||||
|
||||
/* Default effect slot */
|
||||
struct ALeffectslot *DefaultSlot;
|
||||
|
||||
ALCdevice *Device;
|
||||
const ALCchar *ExtensionList;
|
||||
|
||||
ATOMIC(ALCcontext*) next;
|
||||
|
||||
/* Memory space used by the listener (and possibly default effect slot) */
|
||||
alignas(16) ALCbyte _listener_mem[];
|
||||
};
|
||||
|
||||
ALCcontext *GetContextRef(void);
|
||||
|
||||
void ALCcontext_DecRef(ALCcontext *context);
|
||||
|
||||
void ALCcontext_DeferUpdates(ALCcontext *context);
|
||||
void ALCcontext_ProcessUpdates(ALCcontext *context);
|
||||
|
||||
void AllocateVoices(ALCcontext *context, ALsizei num_voices, ALsizei old_sends);
|
||||
|
||||
|
||||
extern ALint RTPrioLevel;
|
||||
void SetRTPriority(void);
|
||||
|
||||
void SetDefaultChannelOrder(ALCdevice *device);
|
||||
void SetDefaultWFXChannelOrder(ALCdevice *device);
|
||||
|
||||
const ALCchar *DevFmtTypeString(enum DevFmtType type);
|
||||
const ALCchar *DevFmtChannelsString(enum DevFmtChannels chans);
|
||||
|
||||
inline ALint GetChannelIndex(const enum Channel names[MAX_OUTPUT_CHANNELS], enum Channel chan)
|
||||
{
|
||||
ALint i;
|
||||
for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
|
||||
{
|
||||
if(names[i] == chan)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/**
|
||||
* GetChannelIdxByName
|
||||
*
|
||||
* Returns the index for the given channel name (e.g. FrontCenter), or -1 if it
|
||||
* doesn't exist.
|
||||
*/
|
||||
inline ALint GetChannelIdxByName(const RealMixParams *real, enum Channel chan)
|
||||
{ return GetChannelIndex(real->ChannelName, chan); }
|
||||
|
||||
|
||||
inline void LockBufferList(ALCdevice *device) { almtx_lock(&device->BufferLock); }
|
||||
inline void UnlockBufferList(ALCdevice *device) { almtx_unlock(&device->BufferLock); }
|
||||
|
||||
inline void LockEffectList(ALCdevice *device) { almtx_lock(&device->EffectLock); }
|
||||
inline void UnlockEffectList(ALCdevice *device) { almtx_unlock(&device->EffectLock); }
|
||||
|
||||
inline void LockFilterList(ALCdevice *device) { almtx_lock(&device->FilterLock); }
|
||||
inline void UnlockFilterList(ALCdevice *device) { almtx_unlock(&device->FilterLock); }
|
||||
|
||||
inline void LockEffectSlotList(ALCcontext *context)
|
||||
{ almtx_lock(&context->EffectSlotLock); }
|
||||
inline void UnlockEffectSlotList(ALCcontext *context)
|
||||
{ almtx_unlock(&context->EffectSlotLock); }
|
||||
|
||||
|
||||
int EventThread(void *arg);
|
||||
|
||||
|
||||
vector_al_string SearchDataFiles(const char *match, const char *subdir);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,120 @@
|
||||
#ifndef _AL_SOURCE_H_
|
||||
#define _AL_SOURCE_H_
|
||||
|
||||
#include "bool.h"
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "hrtf.h"
|
||||
#include "atomic.h"
|
||||
|
||||
#define MAX_SENDS 16
|
||||
#define DEFAULT_SENDS 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ALbuffer;
|
||||
struct ALsource;
|
||||
|
||||
|
||||
typedef struct ALbufferlistitem {
|
||||
ATOMIC(struct ALbufferlistitem*) next;
|
||||
ALsizei max_samples;
|
||||
ALsizei num_buffers;
|
||||
struct ALbuffer *buffers[];
|
||||
} ALbufferlistitem;
|
||||
|
||||
|
||||
typedef struct ALsource {
|
||||
/** Source properties. */
|
||||
ALfloat Pitch;
|
||||
ALfloat Gain;
|
||||
ALfloat OuterGain;
|
||||
ALfloat MinGain;
|
||||
ALfloat MaxGain;
|
||||
ALfloat InnerAngle;
|
||||
ALfloat OuterAngle;
|
||||
ALfloat RefDistance;
|
||||
ALfloat MaxDistance;
|
||||
ALfloat RolloffFactor;
|
||||
ALfloat Position[3];
|
||||
ALfloat Velocity[3];
|
||||
ALfloat Direction[3];
|
||||
ALfloat Orientation[2][3];
|
||||
ALboolean HeadRelative;
|
||||
ALboolean Looping;
|
||||
enum DistanceModel DistanceModel;
|
||||
enum Resampler Resampler;
|
||||
ALboolean DirectChannels;
|
||||
enum SpatializeMode Spatialize;
|
||||
|
||||
ALboolean DryGainHFAuto;
|
||||
ALboolean WetGainAuto;
|
||||
ALboolean WetGainHFAuto;
|
||||
ALfloat OuterGainHF;
|
||||
|
||||
ALfloat AirAbsorptionFactor;
|
||||
ALfloat RoomRolloffFactor;
|
||||
ALfloat DopplerFactor;
|
||||
|
||||
/* NOTE: Stereo pan angles are specified in radians, counter-clockwise
|
||||
* rather than clockwise.
|
||||
*/
|
||||
ALfloat StereoPan[2];
|
||||
|
||||
ALfloat Radius;
|
||||
|
||||
/** Direct filter and auxiliary send info. */
|
||||
struct {
|
||||
ALfloat Gain;
|
||||
ALfloat GainHF;
|
||||
ALfloat HFReference;
|
||||
ALfloat GainLF;
|
||||
ALfloat LFReference;
|
||||
} Direct;
|
||||
struct {
|
||||
struct ALeffectslot *Slot;
|
||||
ALfloat Gain;
|
||||
ALfloat GainHF;
|
||||
ALfloat HFReference;
|
||||
ALfloat GainLF;
|
||||
ALfloat LFReference;
|
||||
} *Send;
|
||||
|
||||
/**
|
||||
* Last user-specified offset, and the offset type (bytes, samples, or
|
||||
* seconds).
|
||||
*/
|
||||
ALdouble Offset;
|
||||
ALenum OffsetType;
|
||||
|
||||
/** Source type (static, streaming, or undetermined) */
|
||||
ALint SourceType;
|
||||
|
||||
/** Source state (initial, playing, paused, or stopped) */
|
||||
ALenum state;
|
||||
|
||||
/** Source Buffer Queue head. */
|
||||
ALbufferlistitem *queue;
|
||||
|
||||
ATOMIC_FLAG PropsClean;
|
||||
|
||||
/* Index into the context's Voices array. Lazily updated, only checked and
|
||||
* reset when looking up the voice.
|
||||
*/
|
||||
ALint VoiceIdx;
|
||||
|
||||
/** Self ID */
|
||||
ALuint id;
|
||||
} ALsource;
|
||||
|
||||
void UpdateAllSourceProps(ALCcontext *context);
|
||||
|
||||
ALvoid ReleaseALSources(ALCcontext *Context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,534 @@
|
||||
#ifndef _ALU_H_
|
||||
#define _ALU_H_
|
||||
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#ifdef HAVE_FLOAT_H
|
||||
#include <float.h>
|
||||
#endif
|
||||
#ifdef HAVE_IEEEFP_H
|
||||
#include <ieeefp.h>
|
||||
#endif
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alBuffer.h"
|
||||
|
||||
#include "hrtf.h"
|
||||
#include "align.h"
|
||||
#include "math_defs.h"
|
||||
#include "filters/defs.h"
|
||||
#include "filters/nfc.h"
|
||||
|
||||
|
||||
#define MAX_PITCH (255)
|
||||
|
||||
/* Maximum number of samples to pad on either end of a buffer for resampling.
|
||||
* Note that both the beginning and end need padding!
|
||||
*/
|
||||
#define MAX_RESAMPLE_PADDING 24
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct BSincTable;
|
||||
struct ALsource;
|
||||
struct ALbufferlistitem;
|
||||
struct ALvoice;
|
||||
struct ALeffectslot;
|
||||
|
||||
|
||||
#define DITHER_RNG_SEED 22222
|
||||
|
||||
|
||||
enum SpatializeMode {
|
||||
SpatializeOff = AL_FALSE,
|
||||
SpatializeOn = AL_TRUE,
|
||||
SpatializeAuto = AL_AUTO_SOFT
|
||||
};
|
||||
|
||||
enum Resampler {
|
||||
PointResampler,
|
||||
LinearResampler,
|
||||
FIR4Resampler,
|
||||
BSinc12Resampler,
|
||||
BSinc24Resampler,
|
||||
|
||||
ResamplerMax = BSinc24Resampler
|
||||
};
|
||||
extern enum Resampler ResamplerDefault;
|
||||
|
||||
/* The number of distinct scale and phase intervals within the bsinc filter
|
||||
* table.
|
||||
*/
|
||||
#define BSINC_SCALE_BITS 4
|
||||
#define BSINC_SCALE_COUNT (1<<BSINC_SCALE_BITS)
|
||||
#define BSINC_PHASE_BITS 4
|
||||
#define BSINC_PHASE_COUNT (1<<BSINC_PHASE_BITS)
|
||||
|
||||
/* Interpolator state. Kind of a misnomer since the interpolator itself is
|
||||
* stateless. This just keeps it from having to recompute scale-related
|
||||
* mappings for every sample.
|
||||
*/
|
||||
typedef struct BsincState {
|
||||
ALfloat sf; /* Scale interpolation factor. */
|
||||
ALsizei m; /* Coefficient count. */
|
||||
ALsizei l; /* Left coefficient offset. */
|
||||
/* Filter coefficients, followed by the scale, phase, and scale-phase
|
||||
* delta coefficients. Starting at phase index 0, each subsequent phase
|
||||
* index follows contiguously.
|
||||
*/
|
||||
const ALfloat *filter;
|
||||
} BsincState;
|
||||
|
||||
typedef union InterpState {
|
||||
BsincState bsinc;
|
||||
} InterpState;
|
||||
|
||||
typedef const ALfloat* (*ResamplerFunc)(const InterpState *state,
|
||||
const ALfloat *restrict src, ALsizei frac, ALint increment,
|
||||
ALfloat *restrict dst, ALsizei dstlen
|
||||
);
|
||||
|
||||
void BsincPrepare(const ALuint increment, BsincState *state, const struct BSincTable *table);
|
||||
|
||||
extern const struct BSincTable bsinc12;
|
||||
extern const struct BSincTable bsinc24;
|
||||
|
||||
|
||||
typedef union aluVector {
|
||||
alignas(16) ALfloat v[4];
|
||||
} aluVector;
|
||||
|
||||
inline void aluVectorSet(aluVector *vector, ALfloat x, ALfloat y, ALfloat z, ALfloat w)
|
||||
{
|
||||
vector->v[0] = x;
|
||||
vector->v[1] = y;
|
||||
vector->v[2] = z;
|
||||
vector->v[3] = w;
|
||||
}
|
||||
|
||||
|
||||
typedef union aluMatrixf {
|
||||
alignas(16) ALfloat m[4][4];
|
||||
} aluMatrixf;
|
||||
extern const aluMatrixf IdentityMatrixf;
|
||||
|
||||
inline void aluMatrixfSetRow(aluMatrixf *matrix, ALuint row,
|
||||
ALfloat m0, ALfloat m1, ALfloat m2, ALfloat m3)
|
||||
{
|
||||
matrix->m[row][0] = m0;
|
||||
matrix->m[row][1] = m1;
|
||||
matrix->m[row][2] = m2;
|
||||
matrix->m[row][3] = m3;
|
||||
}
|
||||
|
||||
inline void aluMatrixfSet(aluMatrixf *matrix, ALfloat m00, ALfloat m01, ALfloat m02, ALfloat m03,
|
||||
ALfloat m10, ALfloat m11, ALfloat m12, ALfloat m13,
|
||||
ALfloat m20, ALfloat m21, ALfloat m22, ALfloat m23,
|
||||
ALfloat m30, ALfloat m31, ALfloat m32, ALfloat m33)
|
||||
{
|
||||
aluMatrixfSetRow(matrix, 0, m00, m01, m02, m03);
|
||||
aluMatrixfSetRow(matrix, 1, m10, m11, m12, m13);
|
||||
aluMatrixfSetRow(matrix, 2, m20, m21, m22, m23);
|
||||
aluMatrixfSetRow(matrix, 3, m30, m31, m32, m33);
|
||||
}
|
||||
|
||||
|
||||
enum ActiveFilters {
|
||||
AF_None = 0,
|
||||
AF_LowPass = 1,
|
||||
AF_HighPass = 2,
|
||||
AF_BandPass = AF_LowPass | AF_HighPass
|
||||
};
|
||||
|
||||
|
||||
typedef struct MixHrtfParams {
|
||||
const ALfloat (*Coeffs)[2];
|
||||
ALsizei Delay[2];
|
||||
ALfloat Gain;
|
||||
ALfloat GainStep;
|
||||
} MixHrtfParams;
|
||||
|
||||
|
||||
typedef struct DirectParams {
|
||||
BiquadFilter LowPass;
|
||||
BiquadFilter HighPass;
|
||||
|
||||
NfcFilter NFCtrlFilter;
|
||||
|
||||
struct {
|
||||
HrtfParams Old;
|
||||
HrtfParams Target;
|
||||
HrtfState State;
|
||||
} Hrtf;
|
||||
|
||||
struct {
|
||||
ALfloat Current[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat Target[MAX_OUTPUT_CHANNELS];
|
||||
} Gains;
|
||||
} DirectParams;
|
||||
|
||||
typedef struct SendParams {
|
||||
BiquadFilter LowPass;
|
||||
BiquadFilter HighPass;
|
||||
|
||||
struct {
|
||||
ALfloat Current[MAX_OUTPUT_CHANNELS];
|
||||
ALfloat Target[MAX_OUTPUT_CHANNELS];
|
||||
} Gains;
|
||||
} SendParams;
|
||||
|
||||
|
||||
struct ALvoiceProps {
|
||||
ATOMIC(struct ALvoiceProps*) next;
|
||||
|
||||
ALfloat Pitch;
|
||||
ALfloat Gain;
|
||||
ALfloat OuterGain;
|
||||
ALfloat MinGain;
|
||||
ALfloat MaxGain;
|
||||
ALfloat InnerAngle;
|
||||
ALfloat OuterAngle;
|
||||
ALfloat RefDistance;
|
||||
ALfloat MaxDistance;
|
||||
ALfloat RolloffFactor;
|
||||
ALfloat Position[3];
|
||||
ALfloat Velocity[3];
|
||||
ALfloat Direction[3];
|
||||
ALfloat Orientation[2][3];
|
||||
ALboolean HeadRelative;
|
||||
enum DistanceModel DistanceModel;
|
||||
enum Resampler Resampler;
|
||||
ALboolean DirectChannels;
|
||||
enum SpatializeMode SpatializeMode;
|
||||
|
||||
ALboolean DryGainHFAuto;
|
||||
ALboolean WetGainAuto;
|
||||
ALboolean WetGainHFAuto;
|
||||
ALfloat OuterGainHF;
|
||||
|
||||
ALfloat AirAbsorptionFactor;
|
||||
ALfloat RoomRolloffFactor;
|
||||
ALfloat DopplerFactor;
|
||||
|
||||
ALfloat StereoPan[2];
|
||||
|
||||
ALfloat Radius;
|
||||
|
||||
/** Direct filter and auxiliary send info. */
|
||||
struct {
|
||||
ALfloat Gain;
|
||||
ALfloat GainHF;
|
||||
ALfloat HFReference;
|
||||
ALfloat GainLF;
|
||||
ALfloat LFReference;
|
||||
} Direct;
|
||||
struct {
|
||||
struct ALeffectslot *Slot;
|
||||
ALfloat Gain;
|
||||
ALfloat GainHF;
|
||||
ALfloat HFReference;
|
||||
ALfloat GainLF;
|
||||
ALfloat LFReference;
|
||||
} Send[];
|
||||
};
|
||||
|
||||
#define VOICE_IS_STATIC (1<<0)
|
||||
#define VOICE_IS_FADING (1<<1) /* Fading sources use gain stepping for smooth transitions. */
|
||||
#define VOICE_HAS_HRTF (1<<2)
|
||||
#define VOICE_HAS_NFC (1<<3)
|
||||
|
||||
typedef struct ALvoice {
|
||||
struct ALvoiceProps *Props;
|
||||
|
||||
ATOMIC(struct ALvoiceProps*) Update;
|
||||
|
||||
ATOMIC(struct ALsource*) Source;
|
||||
ATOMIC(bool) Playing;
|
||||
|
||||
/**
|
||||
* Source offset in samples, relative to the currently playing buffer, NOT
|
||||
* the whole queue, and the fractional (fixed-point) offset to the next
|
||||
* sample.
|
||||
*/
|
||||
ATOMIC(ALuint) position;
|
||||
ATOMIC(ALsizei) position_fraction;
|
||||
|
||||
/* Current buffer queue item being played. */
|
||||
ATOMIC(struct ALbufferlistitem*) current_buffer;
|
||||
|
||||
/* Buffer queue item to loop to at end of queue (will be NULL for non-
|
||||
* looping voices).
|
||||
*/
|
||||
ATOMIC(struct ALbufferlistitem*) loop_buffer;
|
||||
|
||||
/**
|
||||
* Number of channels and bytes-per-sample for the attached source's
|
||||
* buffer(s).
|
||||
*/
|
||||
ALsizei NumChannels;
|
||||
ALsizei SampleSize;
|
||||
|
||||
/** Current target parameters used for mixing. */
|
||||
ALint Step;
|
||||
|
||||
ResamplerFunc Resampler;
|
||||
|
||||
ALuint Flags;
|
||||
|
||||
ALuint Offset; /* Number of output samples mixed since starting. */
|
||||
|
||||
alignas(16) ALfloat PrevSamples[MAX_INPUT_CHANNELS][MAX_RESAMPLE_PADDING];
|
||||
|
||||
InterpState ResampleState;
|
||||
|
||||
struct {
|
||||
enum ActiveFilters FilterType;
|
||||
DirectParams Params[MAX_INPUT_CHANNELS];
|
||||
|
||||
ALfloat (*Buffer)[BUFFERSIZE];
|
||||
ALsizei Channels;
|
||||
ALsizei ChannelsPerOrder[MAX_AMBI_ORDER+1];
|
||||
} Direct;
|
||||
|
||||
struct {
|
||||
enum ActiveFilters FilterType;
|
||||
SendParams Params[MAX_INPUT_CHANNELS];
|
||||
|
||||
ALfloat (*Buffer)[BUFFERSIZE];
|
||||
ALsizei Channels;
|
||||
} Send[];
|
||||
} ALvoice;
|
||||
|
||||
void DeinitVoice(ALvoice *voice);
|
||||
|
||||
|
||||
typedef void (*MixerFunc)(const ALfloat *data, ALsizei OutChans,
|
||||
ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALfloat *CurrentGains,
|
||||
const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
|
||||
ALsizei BufferSize);
|
||||
typedef void (*RowMixerFunc)(ALfloat *OutBuffer, const ALfloat *gains,
|
||||
const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans,
|
||||
ALsizei InPos, ALsizei BufferSize);
|
||||
typedef void (*HrtfMixerFunc)(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, ALsizei OutPos,
|
||||
const ALsizei IrSize, MixHrtfParams *hrtfparams,
|
||||
HrtfState *hrtfstate, ALsizei BufferSize);
|
||||
typedef void (*HrtfMixerBlendFunc)(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, ALsizei OutPos,
|
||||
const ALsizei IrSize, const HrtfParams *oldparams,
|
||||
MixHrtfParams *newparams, HrtfState *hrtfstate,
|
||||
ALsizei BufferSize);
|
||||
typedef void (*HrtfDirectMixerFunc)(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
|
||||
const ALfloat *data, ALsizei Offset, const ALsizei IrSize,
|
||||
const ALfloat (*restrict Coeffs)[2],
|
||||
ALfloat (*restrict Values)[2], ALsizei BufferSize);
|
||||
|
||||
|
||||
#define GAIN_MIX_MAX (16.0f) /* +24dB */
|
||||
|
||||
#define GAIN_SILENCE_THRESHOLD (0.00001f) /* -100dB */
|
||||
|
||||
#define SPEEDOFSOUNDMETRESPERSEC (343.3f)
|
||||
#define AIRABSORBGAINHF (0.99426f) /* -0.05dB */
|
||||
|
||||
/* Target gain for the reverb decay feedback reaching the decay time. */
|
||||
#define REVERB_DECAY_GAIN (0.001f) /* -60 dB */
|
||||
|
||||
#define FRACTIONBITS (12)
|
||||
#define FRACTIONONE (1<<FRACTIONBITS)
|
||||
#define FRACTIONMASK (FRACTIONONE-1)
|
||||
|
||||
|
||||
inline ALfloat minf(ALfloat a, ALfloat b)
|
||||
{ return ((a > b) ? b : a); }
|
||||
inline ALfloat maxf(ALfloat a, ALfloat b)
|
||||
{ return ((a > b) ? a : b); }
|
||||
inline ALfloat clampf(ALfloat val, ALfloat min, ALfloat max)
|
||||
{ return minf(max, maxf(min, val)); }
|
||||
|
||||
inline ALdouble mind(ALdouble a, ALdouble b)
|
||||
{ return ((a > b) ? b : a); }
|
||||
inline ALdouble maxd(ALdouble a, ALdouble b)
|
||||
{ return ((a > b) ? a : b); }
|
||||
inline ALdouble clampd(ALdouble val, ALdouble min, ALdouble max)
|
||||
{ return mind(max, maxd(min, val)); }
|
||||
|
||||
inline ALuint minu(ALuint a, ALuint b)
|
||||
{ return ((a > b) ? b : a); }
|
||||
inline ALuint maxu(ALuint a, ALuint b)
|
||||
{ return ((a > b) ? a : b); }
|
||||
inline ALuint clampu(ALuint val, ALuint min, ALuint max)
|
||||
{ return minu(max, maxu(min, val)); }
|
||||
|
||||
inline ALint mini(ALint a, ALint b)
|
||||
{ return ((a > b) ? b : a); }
|
||||
inline ALint maxi(ALint a, ALint b)
|
||||
{ return ((a > b) ? a : b); }
|
||||
inline ALint clampi(ALint val, ALint min, ALint max)
|
||||
{ return mini(max, maxi(min, val)); }
|
||||
|
||||
inline ALint64 mini64(ALint64 a, ALint64 b)
|
||||
{ return ((a > b) ? b : a); }
|
||||
inline ALint64 maxi64(ALint64 a, ALint64 b)
|
||||
{ return ((a > b) ? a : b); }
|
||||
inline ALint64 clampi64(ALint64 val, ALint64 min, ALint64 max)
|
||||
{ return mini64(max, maxi64(min, val)); }
|
||||
|
||||
inline ALuint64 minu64(ALuint64 a, ALuint64 b)
|
||||
{ return ((a > b) ? b : a); }
|
||||
inline ALuint64 maxu64(ALuint64 a, ALuint64 b)
|
||||
{ return ((a > b) ? a : b); }
|
||||
inline ALuint64 clampu64(ALuint64 val, ALuint64 min, ALuint64 max)
|
||||
{ return minu64(max, maxu64(min, val)); }
|
||||
|
||||
inline size_t minz(size_t a, size_t b)
|
||||
{ return ((a > b) ? b : a); }
|
||||
inline size_t maxz(size_t a, size_t b)
|
||||
{ return ((a > b) ? a : b); }
|
||||
inline size_t clampz(size_t val, size_t min, size_t max)
|
||||
{ return minz(max, maxz(min, val)); }
|
||||
|
||||
|
||||
inline ALfloat lerp(ALfloat val1, ALfloat val2, ALfloat mu)
|
||||
{
|
||||
return val1 + (val2-val1)*mu;
|
||||
}
|
||||
inline ALfloat cubic(ALfloat val1, ALfloat val2, ALfloat val3, ALfloat val4, ALfloat mu)
|
||||
{
|
||||
ALfloat mu2 = mu*mu, mu3 = mu2*mu;
|
||||
ALfloat a0 = -0.5f*mu3 + mu2 + -0.5f*mu;
|
||||
ALfloat a1 = 1.5f*mu3 + -2.5f*mu2 + 1.0f;
|
||||
ALfloat a2 = -1.5f*mu3 + 2.0f*mu2 + 0.5f*mu;
|
||||
ALfloat a3 = 0.5f*mu3 + -0.5f*mu2;
|
||||
return val1*a0 + val2*a1 + val3*a2 + val4*a3;
|
||||
}
|
||||
|
||||
|
||||
enum HrtfRequestMode {
|
||||
Hrtf_Default = 0,
|
||||
Hrtf_Enable = 1,
|
||||
Hrtf_Disable = 2,
|
||||
};
|
||||
|
||||
void aluInit(void);
|
||||
|
||||
void aluInitMixer(void);
|
||||
|
||||
ResamplerFunc SelectResampler(enum Resampler resampler);
|
||||
|
||||
/* aluInitRenderer
|
||||
*
|
||||
* Set up the appropriate panning method and mixing method given the device
|
||||
* properties.
|
||||
*/
|
||||
void aluInitRenderer(ALCdevice *device, ALint hrtf_id, enum HrtfRequestMode hrtf_appreq, enum HrtfRequestMode hrtf_userreq);
|
||||
|
||||
void aluInitEffectPanning(struct ALeffectslot *slot);
|
||||
|
||||
void aluSelectPostProcess(ALCdevice *device);
|
||||
|
||||
/**
|
||||
* Calculates ambisonic encoder coefficients using the X, Y, and Z direction
|
||||
* components, which must represent a normalized (unit length) vector, and the
|
||||
* spread is the angular width of the sound (0...tau).
|
||||
*
|
||||
* NOTE: The components use ambisonic coordinates. As a result:
|
||||
*
|
||||
* Ambisonic Y = OpenAL -X
|
||||
* Ambisonic Z = OpenAL Y
|
||||
* Ambisonic X = OpenAL -Z
|
||||
*
|
||||
* The components are ordered such that OpenAL's X, Y, and Z are the first,
|
||||
* second, and third parameters respectively -- simply negate X and Z.
|
||||
*/
|
||||
void CalcAmbiCoeffs(const ALfloat y, const ALfloat z, const ALfloat x, const ALfloat spread,
|
||||
ALfloat coeffs[MAX_AMBI_COEFFS]);
|
||||
|
||||
/**
|
||||
* CalcDirectionCoeffs
|
||||
*
|
||||
* Calculates ambisonic coefficients based on an OpenAL direction vector. The
|
||||
* vector must be normalized (unit length), and the spread is the angular width
|
||||
* of the sound (0...tau).
|
||||
*/
|
||||
inline void CalcDirectionCoeffs(const ALfloat dir[3], ALfloat spread, ALfloat coeffs[MAX_AMBI_COEFFS])
|
||||
{
|
||||
/* Convert from OpenAL coords to Ambisonics. */
|
||||
CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread, coeffs);
|
||||
}
|
||||
|
||||
/**
|
||||
* CalcAngleCoeffs
|
||||
*
|
||||
* Calculates ambisonic coefficients based on azimuth and elevation. The
|
||||
* azimuth and elevation parameters are in radians, going right and up
|
||||
* respectively.
|
||||
*/
|
||||
inline void CalcAngleCoeffs(ALfloat azimuth, ALfloat elevation, ALfloat spread, ALfloat coeffs[MAX_AMBI_COEFFS])
|
||||
{
|
||||
ALfloat x = -sinf(azimuth) * cosf(elevation);
|
||||
ALfloat y = sinf(elevation);
|
||||
ALfloat z = cosf(azimuth) * cosf(elevation);
|
||||
|
||||
CalcAmbiCoeffs(x, y, z, spread, coeffs);
|
||||
}
|
||||
|
||||
/**
|
||||
* ScaleAzimuthFront
|
||||
*
|
||||
* Scales the given azimuth toward the side (+/- pi/2 radians) for positions in
|
||||
* front.
|
||||
*/
|
||||
inline float ScaleAzimuthFront(float azimuth, float scale)
|
||||
{
|
||||
ALfloat sign = copysignf(1.0f, azimuth);
|
||||
if(!(fabsf(azimuth) > F_PI_2))
|
||||
return minf(fabsf(azimuth) * scale, F_PI_2) * sign;
|
||||
return azimuth;
|
||||
}
|
||||
|
||||
|
||||
void ComputePanningGainsMC(const ChannelConfig *chancoeffs, ALsizei numchans, ALsizei numcoeffs, const ALfloat*restrict coeffs, ALfloat ingain, ALfloat gains[MAX_OUTPUT_CHANNELS]);
|
||||
void ComputePanningGainsBF(const BFChannelConfig *chanmap, ALsizei numchans, const ALfloat*restrict coeffs, ALfloat ingain, ALfloat gains[MAX_OUTPUT_CHANNELS]);
|
||||
|
||||
/**
|
||||
* ComputePanGains
|
||||
*
|
||||
* Computes panning gains using the given channel decoder coefficients and the
|
||||
* pre-calculated direction or angle coefficients. For B-Format sources, the
|
||||
* coeffs are a 'slice' of a transform matrix for the input channel, used to
|
||||
* scale and orient the sound samples.
|
||||
*/
|
||||
inline void ComputePanGains(const MixParams *dry, const ALfloat*restrict coeffs, ALfloat ingain, ALfloat gains[MAX_OUTPUT_CHANNELS])
|
||||
{
|
||||
if(dry->CoeffCount > 0)
|
||||
ComputePanningGainsMC(dry->Ambi.Coeffs, dry->NumChannels, dry->CoeffCount,
|
||||
coeffs, ingain, gains);
|
||||
else
|
||||
ComputePanningGainsBF(dry->Ambi.Map, dry->NumChannels, coeffs, ingain, gains);
|
||||
}
|
||||
|
||||
|
||||
ALboolean MixSource(struct ALvoice *voice, ALuint SourceID, ALCcontext *Context, ALsizei SamplesToDo);
|
||||
|
||||
void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples);
|
||||
/* Caller must lock the device, and the mixer must not be running. */
|
||||
void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) DECL_FORMAT(printf, 2, 3);
|
||||
|
||||
void UpdateContextProps(ALCcontext *context);
|
||||
|
||||
extern MixerFunc MixSamples;
|
||||
extern RowMixerFunc MixRowSamples;
|
||||
|
||||
extern ALfloat ConeScale;
|
||||
extern ALfloat ZScale;
|
||||
extern ALboolean OverrideReverbSpeedOfSound;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -24,8 +24,6 @@
|
||||
#ifndef BS2B_H
|
||||
#define BS2B_H
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
/* Number of crossfeed levels */
|
||||
#define BS2B_CLEVELS 3
|
||||
|
||||
@@ -44,6 +42,10 @@
|
||||
/* Default sample rate (Hz) */
|
||||
#define BS2B_DEFAULT_SRATE 44100
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
struct bs2b {
|
||||
int level; /* Crossfeed level */
|
||||
int srate; /* Sample rate (Hz) */
|
||||
@@ -57,15 +59,14 @@ struct bs2b {
|
||||
float a1_hi;
|
||||
float b1_hi;
|
||||
|
||||
/* Buffer of filter history
|
||||
/* Buffer of last filtered sample.
|
||||
* [0] - first channel, [1] - second channel
|
||||
*/
|
||||
struct t_last_sample {
|
||||
float asis;
|
||||
float lo;
|
||||
float hi;
|
||||
} history[2];
|
||||
|
||||
DEF_NEWDEL(bs2b)
|
||||
} last_sample[2];
|
||||
};
|
||||
|
||||
/* Clear buffers and set new coefficients with new crossfeed level and sample
|
||||
@@ -73,17 +74,21 @@ struct bs2b {
|
||||
* level - crossfeed level of *LEVEL values.
|
||||
* srate - sample rate by Hz.
|
||||
*/
|
||||
void bs2b_set_params(bs2b *bs2b, int level, int srate);
|
||||
void bs2b_set_params(struct bs2b *bs2b, int level, int srate);
|
||||
|
||||
/* Return current crossfeed level value */
|
||||
int bs2b_get_level(bs2b *bs2b);
|
||||
int bs2b_get_level(struct bs2b *bs2b);
|
||||
|
||||
/* Return current sample rate value */
|
||||
int bs2b_get_srate(bs2b *bs2b);
|
||||
int bs2b_get_srate(struct bs2b *bs2b);
|
||||
|
||||
/* Clear buffer */
|
||||
void bs2b_clear(bs2b *bs2b);
|
||||
void bs2b_clear(struct bs2b *bs2b);
|
||||
|
||||
void bs2b_cross_feed(bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo);
|
||||
void bs2b_cross_feed(struct bs2b *bs2b, float *restrict Left, float *restrict Right, int SamplesToDo);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* BS2B_H */
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef SAMPLE_CVT_H
|
||||
#define SAMPLE_CVT_H
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "alBuffer.h"
|
||||
|
||||
extern const ALshort muLawDecompressionTable[256];
|
||||
extern const ALshort aLawDecompressionTable[256];
|
||||
|
||||
void Convert_ALshort_ALima4(ALshort *dst, const ALubyte *src, ALsizei numchans, ALsizei len,
|
||||
ALsizei align);
|
||||
void Convert_ALshort_ALmsadpcm(ALshort *dst, const ALubyte *src, ALsizei numchans, ALsizei len,
|
||||
ALsizei align);
|
||||
|
||||
#endif /* SAMPLE_CVT_H */
|
||||
@@ -0,0 +1,802 @@
|
||||
/**
|
||||
* 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 <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "alMain.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alError.h"
|
||||
#include "alListener.h"
|
||||
#include "alSource.h"
|
||||
|
||||
#include "fpu_modes.h"
|
||||
#include "almalloc.h"
|
||||
|
||||
|
||||
extern inline void LockEffectSlotList(ALCcontext *context);
|
||||
extern inline void UnlockEffectSlotList(ALCcontext *context);
|
||||
|
||||
static void AddActiveEffectSlots(const ALuint *slotids, ALsizei count, ALCcontext *context);
|
||||
static void RemoveActiveEffectSlots(const ALuint *slotids, ALsizei count, ALCcontext *context);
|
||||
|
||||
static const struct {
|
||||
ALenum Type;
|
||||
EffectStateFactory* (*GetFactory)(void);
|
||||
} FactoryList[] = {
|
||||
{ AL_EFFECT_NULL, NullStateFactory_getFactory },
|
||||
{ AL_EFFECT_EAXREVERB, ReverbStateFactory_getFactory },
|
||||
{ AL_EFFECT_REVERB, ReverbStateFactory_getFactory },
|
||||
{ AL_EFFECT_AUTOWAH, AutowahStateFactory_getFactory },
|
||||
{ AL_EFFECT_CHORUS, ChorusStateFactory_getFactory },
|
||||
{ AL_EFFECT_COMPRESSOR, CompressorStateFactory_getFactory },
|
||||
{ AL_EFFECT_DISTORTION, DistortionStateFactory_getFactory },
|
||||
{ AL_EFFECT_ECHO, EchoStateFactory_getFactory },
|
||||
{ AL_EFFECT_EQUALIZER, EqualizerStateFactory_getFactory },
|
||||
{ AL_EFFECT_FLANGER, FlangerStateFactory_getFactory },
|
||||
{ AL_EFFECT_FREQUENCY_SHIFTER, FshifterStateFactory_getFactory },
|
||||
{ AL_EFFECT_RING_MODULATOR, ModulatorStateFactory_getFactory },
|
||||
{ AL_EFFECT_PITCH_SHIFTER, PshifterStateFactory_getFactory},
|
||||
{ AL_EFFECT_DEDICATED_DIALOGUE, DedicatedStateFactory_getFactory },
|
||||
{ AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedStateFactory_getFactory }
|
||||
};
|
||||
|
||||
static inline EffectStateFactory *getFactoryByType(ALenum type)
|
||||
{
|
||||
size_t i;
|
||||
for(i = 0;i < COUNTOF(FactoryList);i++)
|
||||
{
|
||||
if(FactoryList[i].Type == type)
|
||||
return FactoryList[i].GetFactory();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void ALeffectState_IncRef(ALeffectState *state);
|
||||
|
||||
|
||||
static inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id)
|
||||
{
|
||||
id--;
|
||||
if(UNLIKELY(id >= VECTOR_SIZE(context->EffectSlotList)))
|
||||
return NULL;
|
||||
return VECTOR_ELEM(context->EffectSlotList, id);
|
||||
}
|
||||
|
||||
static inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
|
||||
{
|
||||
EffectSubList *sublist;
|
||||
ALuint lidx = (id-1) >> 6;
|
||||
ALsizei slidx = (id-1) & 0x3f;
|
||||
|
||||
if(UNLIKELY(lidx >= VECTOR_SIZE(device->EffectList)))
|
||||
return NULL;
|
||||
sublist = &VECTOR_ELEM(device->EffectList, lidx);
|
||||
if(UNLIKELY(sublist->FreeMask & (U64(1)<<slidx)))
|
||||
return NULL;
|
||||
return sublist->Effects + slidx;
|
||||
}
|
||||
|
||||
|
||||
#define DO_UPDATEPROPS() do { \
|
||||
if(!ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire)) \
|
||||
UpdateEffectSlotProps(slot, context); \
|
||||
else \
|
||||
ATOMIC_FLAG_CLEAR(&slot->PropsClean, almemory_order_release); \
|
||||
} while(0)
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots)
|
||||
{
|
||||
ALCdevice *device;
|
||||
ALCcontext *context;
|
||||
ALsizei cur;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!(n >= 0))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Generating %d effect slots", n);
|
||||
if(n == 0) goto done;
|
||||
|
||||
LockEffectSlotList(context);
|
||||
device = context->Device;
|
||||
for(cur = 0;cur < n;cur++)
|
||||
{
|
||||
ALeffectslotPtr *iter = VECTOR_BEGIN(context->EffectSlotList);
|
||||
ALeffectslotPtr *end = VECTOR_END(context->EffectSlotList);
|
||||
ALeffectslot *slot = NULL;
|
||||
ALenum err = AL_OUT_OF_MEMORY;
|
||||
|
||||
for(;iter != end;iter++)
|
||||
{
|
||||
if(!*iter)
|
||||
break;
|
||||
}
|
||||
if(iter == end)
|
||||
{
|
||||
if(device->AuxiliaryEffectSlotMax == VECTOR_SIZE(context->EffectSlotList))
|
||||
{
|
||||
UnlockEffectSlotList(context);
|
||||
alDeleteAuxiliaryEffectSlots(cur, effectslots);
|
||||
SETERR_GOTO(context, AL_OUT_OF_MEMORY, done,
|
||||
"Exceeding %u auxiliary effect slot limit", device->AuxiliaryEffectSlotMax);
|
||||
}
|
||||
VECTOR_PUSH_BACK(context->EffectSlotList, NULL);
|
||||
iter = &VECTOR_BACK(context->EffectSlotList);
|
||||
}
|
||||
slot = al_calloc(16, sizeof(ALeffectslot));
|
||||
if(!slot || (err=InitEffectSlot(slot)) != AL_NO_ERROR)
|
||||
{
|
||||
al_free(slot);
|
||||
UnlockEffectSlotList(context);
|
||||
|
||||
alDeleteAuxiliaryEffectSlots(cur, effectslots);
|
||||
SETERR_GOTO(context, err, done, "Effect slot object allocation failed");
|
||||
}
|
||||
aluInitEffectPanning(slot);
|
||||
|
||||
slot->id = (iter - VECTOR_BEGIN(context->EffectSlotList)) + 1;
|
||||
*iter = slot;
|
||||
|
||||
effectslots[cur] = slot->id;
|
||||
}
|
||||
AddActiveEffectSlots(effectslots, n, context);
|
||||
UnlockEffectSlotList(context);
|
||||
|
||||
done:
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALeffectslot *slot;
|
||||
ALsizei i;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
LockEffectSlotList(context);
|
||||
if(!(n >= 0))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Deleting %d effect slots", n);
|
||||
if(n == 0) goto done;
|
||||
|
||||
for(i = 0;i < n;i++)
|
||||
{
|
||||
if((slot=LookupEffectSlot(context, effectslots[i])) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u",
|
||||
effectslots[i]);
|
||||
if(ReadRef(&slot->ref) != 0)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Deleting in-use effect slot %u",
|
||||
effectslots[i]);
|
||||
}
|
||||
|
||||
// All effectslots are valid
|
||||
RemoveActiveEffectSlots(effectslots, n, context);
|
||||
for(i = 0;i < n;i++)
|
||||
{
|
||||
if((slot=LookupEffectSlot(context, effectslots[i])) == NULL)
|
||||
continue;
|
||||
VECTOR_ELEM(context->EffectSlotList, effectslots[i]-1) = NULL;
|
||||
|
||||
DeinitEffectSlot(slot);
|
||||
|
||||
memset(slot, 0, sizeof(*slot));
|
||||
al_free(slot);
|
||||
}
|
||||
|
||||
done:
|
||||
UnlockEffectSlotList(context);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALboolean ret;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return AL_FALSE;
|
||||
|
||||
LockEffectSlotList(context);
|
||||
ret = (LookupEffectSlot(context, effectslot) ? AL_TRUE : AL_FALSE);
|
||||
UnlockEffectSlotList(context);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint value)
|
||||
{
|
||||
ALCdevice *device;
|
||||
ALCcontext *context;
|
||||
ALeffectslot *slot;
|
||||
ALeffect *effect = NULL;
|
||||
ALenum err;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
LockEffectSlotList(context);
|
||||
if((slot=LookupEffectSlot(context, effectslot)) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_EFFECT:
|
||||
device = context->Device;
|
||||
|
||||
LockEffectList(device);
|
||||
effect = (value ? LookupEffect(device, value) : NULL);
|
||||
if(!(value == 0 || effect != NULL))
|
||||
{
|
||||
UnlockEffectList(device);
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Invalid effect ID %u", value);
|
||||
}
|
||||
err = InitializeEffect(context, slot, effect);
|
||||
UnlockEffectList(device);
|
||||
|
||||
if(err != AL_NO_ERROR)
|
||||
SETERR_GOTO(context, err, done, "Effect initialization failed");
|
||||
break;
|
||||
|
||||
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
|
||||
if(!(value == AL_TRUE || value == AL_FALSE))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done,
|
||||
"Effect slot auxiliary send auto out of range");
|
||||
slot->AuxSendAuto = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
SETERR_GOTO(context, AL_INVALID_ENUM, done, "Invalid effect slot integer property 0x%04x",
|
||||
param);
|
||||
}
|
||||
DO_UPDATEPROPS();
|
||||
|
||||
done:
|
||||
UnlockEffectSlotList(context);
|
||||
almtx_unlock(&context->PropLock);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_EFFECT:
|
||||
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
|
||||
alAuxiliaryEffectSloti(effectslot, param, values[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
LockEffectSlotList(context);
|
||||
if(LookupEffectSlot(context, effectslot) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
|
||||
param);
|
||||
}
|
||||
|
||||
done:
|
||||
UnlockEffectSlotList(context);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat value)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALeffectslot *slot;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
LockEffectSlotList(context);
|
||||
if((slot=LookupEffectSlot(context, effectslot)) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_GAIN:
|
||||
if(!(value >= 0.0f && value <= 1.0f))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Effect slot gain out of range");
|
||||
slot->Gain = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
SETERR_GOTO(context, AL_INVALID_ENUM, done, "Invalid effect slot float property 0x%04x",
|
||||
param);
|
||||
}
|
||||
DO_UPDATEPROPS();
|
||||
|
||||
done:
|
||||
UnlockEffectSlotList(context);
|
||||
almtx_unlock(&context->PropLock);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_GAIN:
|
||||
alAuxiliaryEffectSlotf(effectslot, param, values[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
LockEffectSlotList(context);
|
||||
if(LookupEffectSlot(context, effectslot) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
|
||||
param);
|
||||
}
|
||||
|
||||
done:
|
||||
UnlockEffectSlotList(context);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *value)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALeffectslot *slot;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
LockEffectSlotList(context);
|
||||
if((slot=LookupEffectSlot(context, effectslot)) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
|
||||
*value = slot->AuxSendAuto;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param);
|
||||
}
|
||||
|
||||
done:
|
||||
UnlockEffectSlotList(context);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_EFFECT:
|
||||
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
|
||||
alGetAuxiliaryEffectSloti(effectslot, param, values);
|
||||
return;
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
LockEffectSlotList(context);
|
||||
if(LookupEffectSlot(context, effectslot) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
|
||||
param);
|
||||
}
|
||||
|
||||
done:
|
||||
UnlockEffectSlotList(context);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *value)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALeffectslot *slot;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
LockEffectSlotList(context);
|
||||
if((slot=LookupEffectSlot(context, effectslot)) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_GAIN:
|
||||
*value = slot->Gain;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param);
|
||||
}
|
||||
|
||||
done:
|
||||
UnlockEffectSlotList(context);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECTSLOT_GAIN:
|
||||
alGetAuxiliaryEffectSlotf(effectslot, param, values);
|
||||
return;
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
LockEffectSlotList(context);
|
||||
if(LookupEffectSlot(context, effectslot) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
|
||||
param);
|
||||
}
|
||||
|
||||
done:
|
||||
UnlockEffectSlotList(context);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect)
|
||||
{
|
||||
ALCdevice *Device = Context->Device;
|
||||
ALenum newtype = (effect ? effect->type : AL_EFFECT_NULL);
|
||||
struct ALeffectslotProps *props;
|
||||
ALeffectState *State;
|
||||
|
||||
if(newtype != EffectSlot->Effect.Type)
|
||||
{
|
||||
EffectStateFactory *factory;
|
||||
|
||||
factory = getFactoryByType(newtype);
|
||||
if(!factory)
|
||||
{
|
||||
ERR("Failed to find factory for effect type 0x%04x\n", newtype);
|
||||
return AL_INVALID_ENUM;
|
||||
}
|
||||
State = EffectStateFactory_create(factory);
|
||||
if(!State) return AL_OUT_OF_MEMORY;
|
||||
|
||||
START_MIXER_MODE();
|
||||
almtx_lock(&Device->BackendLock);
|
||||
State->OutBuffer = Device->Dry.Buffer;
|
||||
State->OutChannels = Device->Dry.NumChannels;
|
||||
if(V(State,deviceUpdate)(Device) == AL_FALSE)
|
||||
{
|
||||
almtx_unlock(&Device->BackendLock);
|
||||
LEAVE_MIXER_MODE();
|
||||
ALeffectState_DecRef(State);
|
||||
return AL_OUT_OF_MEMORY;
|
||||
}
|
||||
almtx_unlock(&Device->BackendLock);
|
||||
END_MIXER_MODE();
|
||||
|
||||
if(!effect)
|
||||
{
|
||||
EffectSlot->Effect.Type = AL_EFFECT_NULL;
|
||||
memset(&EffectSlot->Effect.Props, 0, sizeof(EffectSlot->Effect.Props));
|
||||
}
|
||||
else
|
||||
{
|
||||
EffectSlot->Effect.Type = effect->type;
|
||||
EffectSlot->Effect.Props = effect->Props;
|
||||
}
|
||||
|
||||
ALeffectState_DecRef(EffectSlot->Effect.State);
|
||||
EffectSlot->Effect.State = State;
|
||||
}
|
||||
else if(effect)
|
||||
EffectSlot->Effect.Props = effect->Props;
|
||||
|
||||
/* Remove state references from old effect slot property updates. */
|
||||
props = ATOMIC_LOAD_SEQ(&Context->FreeEffectslotProps);
|
||||
while(props)
|
||||
{
|
||||
if(props->State)
|
||||
ALeffectState_DecRef(props->State);
|
||||
props->State = NULL;
|
||||
props = ATOMIC_LOAD(&props->next, almemory_order_relaxed);
|
||||
}
|
||||
|
||||
return AL_NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static void ALeffectState_IncRef(ALeffectState *state)
|
||||
{
|
||||
uint ref;
|
||||
ref = IncrementRef(&state->Ref);
|
||||
TRACEREF("%p increasing refcount to %u\n", state, ref);
|
||||
}
|
||||
|
||||
void ALeffectState_DecRef(ALeffectState *state)
|
||||
{
|
||||
uint ref;
|
||||
ref = DecrementRef(&state->Ref);
|
||||
TRACEREF("%p decreasing refcount to %u\n", state, ref);
|
||||
if(ref == 0) DELETE_OBJ(state);
|
||||
}
|
||||
|
||||
|
||||
void ALeffectState_Construct(ALeffectState *state)
|
||||
{
|
||||
InitRef(&state->Ref, 1);
|
||||
|
||||
state->OutBuffer = NULL;
|
||||
state->OutChannels = 0;
|
||||
}
|
||||
|
||||
void ALeffectState_Destruct(ALeffectState *UNUSED(state))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void AddActiveEffectSlots(const ALuint *slotids, ALsizei count, ALCcontext *context)
|
||||
{
|
||||
struct ALeffectslotArray *curarray = ATOMIC_LOAD(&context->ActiveAuxSlots,
|
||||
almemory_order_acquire);
|
||||
struct ALeffectslotArray *newarray = NULL;
|
||||
ALsizei newcount = curarray->count + count;
|
||||
ALCdevice *device = context->Device;
|
||||
ALsizei i, j;
|
||||
|
||||
/* Insert the new effect slots into the head of the array, followed by the
|
||||
* existing ones.
|
||||
*/
|
||||
newarray = al_calloc(DEF_ALIGN, FAM_SIZE(struct ALeffectslotArray, slot, newcount));
|
||||
newarray->count = newcount;
|
||||
for(i = 0;i < count;i++)
|
||||
newarray->slot[i] = LookupEffectSlot(context, slotids[i]);
|
||||
for(j = 0;i < newcount;)
|
||||
newarray->slot[i++] = curarray->slot[j++];
|
||||
/* Remove any duplicates (first instance of each will be kept). */
|
||||
for(i = 1;i < newcount;i++)
|
||||
{
|
||||
for(j = i;j != 0;)
|
||||
{
|
||||
if(UNLIKELY(newarray->slot[i] == newarray->slot[--j]))
|
||||
{
|
||||
newcount--;
|
||||
for(j = i;j < newcount;j++)
|
||||
newarray->slot[j] = newarray->slot[j+1];
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Reallocate newarray if the new size ended up smaller from duplicate
|
||||
* removal.
|
||||
*/
|
||||
if(UNLIKELY(newcount < newarray->count))
|
||||
{
|
||||
struct ALeffectslotArray *tmpnewarray = al_calloc(DEF_ALIGN,
|
||||
FAM_SIZE(struct ALeffectslotArray, slot, newcount));
|
||||
memcpy(tmpnewarray, newarray, FAM_SIZE(struct ALeffectslotArray, slot, newcount));
|
||||
al_free(newarray);
|
||||
newarray = tmpnewarray;
|
||||
newarray->count = newcount;
|
||||
}
|
||||
|
||||
curarray = ATOMIC_EXCHANGE_PTR(&context->ActiveAuxSlots, newarray, almemory_order_acq_rel);
|
||||
while((ATOMIC_LOAD(&device->MixCount, almemory_order_acquire)&1))
|
||||
althrd_yield();
|
||||
al_free(curarray);
|
||||
}
|
||||
|
||||
static void RemoveActiveEffectSlots(const ALuint *slotids, ALsizei count, ALCcontext *context)
|
||||
{
|
||||
struct ALeffectslotArray *curarray = ATOMIC_LOAD(&context->ActiveAuxSlots,
|
||||
almemory_order_acquire);
|
||||
struct ALeffectslotArray *newarray = NULL;
|
||||
ALCdevice *device = context->Device;
|
||||
ALsizei i, j;
|
||||
|
||||
/* Don't shrink the allocated array size since we don't know how many (if
|
||||
* any) of the effect slots to remove are in the array.
|
||||
*/
|
||||
newarray = al_calloc(DEF_ALIGN, FAM_SIZE(struct ALeffectslotArray, slot, curarray->count));
|
||||
newarray->count = 0;
|
||||
for(i = 0;i < curarray->count;i++)
|
||||
{
|
||||
/* Insert this slot into the new array only if it's not one to remove. */
|
||||
ALeffectslot *slot = curarray->slot[i];
|
||||
for(j = count;j != 0;)
|
||||
{
|
||||
if(slot->id == slotids[--j])
|
||||
goto skip_ins;
|
||||
}
|
||||
newarray->slot[newarray->count++] = slot;
|
||||
skip_ins: ;
|
||||
}
|
||||
|
||||
/* TODO: Could reallocate newarray now that we know it's needed size. */
|
||||
|
||||
curarray = ATOMIC_EXCHANGE_PTR(&context->ActiveAuxSlots, newarray, almemory_order_acq_rel);
|
||||
while((ATOMIC_LOAD(&device->MixCount, almemory_order_acquire)&1))
|
||||
althrd_yield();
|
||||
al_free(curarray);
|
||||
}
|
||||
|
||||
|
||||
ALenum InitEffectSlot(ALeffectslot *slot)
|
||||
{
|
||||
EffectStateFactory *factory;
|
||||
|
||||
slot->Effect.Type = AL_EFFECT_NULL;
|
||||
|
||||
factory = getFactoryByType(AL_EFFECT_NULL);
|
||||
slot->Effect.State = EffectStateFactory_create(factory);
|
||||
if(!slot->Effect.State) return AL_OUT_OF_MEMORY;
|
||||
|
||||
slot->Gain = 1.0;
|
||||
slot->AuxSendAuto = AL_TRUE;
|
||||
ATOMIC_FLAG_TEST_AND_SET(&slot->PropsClean, almemory_order_relaxed);
|
||||
InitRef(&slot->ref, 0);
|
||||
|
||||
ATOMIC_INIT(&slot->Update, NULL);
|
||||
|
||||
slot->Params.Gain = 1.0f;
|
||||
slot->Params.AuxSendAuto = AL_TRUE;
|
||||
ALeffectState_IncRef(slot->Effect.State);
|
||||
slot->Params.EffectState = slot->Effect.State;
|
||||
slot->Params.RoomRolloff = 0.0f;
|
||||
slot->Params.DecayTime = 0.0f;
|
||||
slot->Params.DecayLFRatio = 0.0f;
|
||||
slot->Params.DecayHFRatio = 0.0f;
|
||||
slot->Params.DecayHFLimit = AL_FALSE;
|
||||
slot->Params.AirAbsorptionGainHF = 1.0f;
|
||||
|
||||
return AL_NO_ERROR;
|
||||
}
|
||||
|
||||
void DeinitEffectSlot(ALeffectslot *slot)
|
||||
{
|
||||
struct ALeffectslotProps *props;
|
||||
|
||||
props = ATOMIC_LOAD_SEQ(&slot->Update);
|
||||
if(props)
|
||||
{
|
||||
if(props->State) ALeffectState_DecRef(props->State);
|
||||
TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n", props);
|
||||
al_free(props);
|
||||
}
|
||||
|
||||
ALeffectState_DecRef(slot->Effect.State);
|
||||
if(slot->Params.EffectState)
|
||||
ALeffectState_DecRef(slot->Params.EffectState);
|
||||
}
|
||||
|
||||
void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context)
|
||||
{
|
||||
struct ALeffectslotProps *props;
|
||||
ALeffectState *oldstate;
|
||||
|
||||
/* Get an unused property container, or allocate a new one as needed. */
|
||||
props = ATOMIC_LOAD(&context->FreeEffectslotProps, almemory_order_relaxed);
|
||||
if(!props)
|
||||
props = al_calloc(16, sizeof(*props));
|
||||
else
|
||||
{
|
||||
struct ALeffectslotProps *next;
|
||||
do {
|
||||
next = ATOMIC_LOAD(&props->next, almemory_order_relaxed);
|
||||
} while(ATOMIC_COMPARE_EXCHANGE_PTR_WEAK(&context->FreeEffectslotProps, &props, next,
|
||||
almemory_order_seq_cst, almemory_order_acquire) == 0);
|
||||
}
|
||||
|
||||
/* Copy in current property values. */
|
||||
props->Gain = slot->Gain;
|
||||
props->AuxSendAuto = slot->AuxSendAuto;
|
||||
|
||||
props->Type = slot->Effect.Type;
|
||||
props->Props = slot->Effect.Props;
|
||||
/* Swap out any stale effect state object there may be in the container, to
|
||||
* delete it.
|
||||
*/
|
||||
ALeffectState_IncRef(slot->Effect.State);
|
||||
oldstate = props->State;
|
||||
props->State = slot->Effect.State;
|
||||
|
||||
/* Set the new container for updating internal parameters. */
|
||||
props = ATOMIC_EXCHANGE_PTR(&slot->Update, props, almemory_order_acq_rel);
|
||||
if(props)
|
||||
{
|
||||
/* If there was an unused update container, put it back in the
|
||||
* freelist.
|
||||
*/
|
||||
if(props->State)
|
||||
ALeffectState_DecRef(props->State);
|
||||
props->State = NULL;
|
||||
ATOMIC_REPLACE_HEAD(struct ALeffectslotProps*, &context->FreeEffectslotProps, props);
|
||||
}
|
||||
|
||||
if(oldstate)
|
||||
ALeffectState_DecRef(oldstate);
|
||||
}
|
||||
|
||||
void UpdateAllEffectSlotProps(ALCcontext *context)
|
||||
{
|
||||
struct ALeffectslotArray *auxslots;
|
||||
ALsizei i;
|
||||
|
||||
LockEffectSlotList(context);
|
||||
auxslots = ATOMIC_LOAD(&context->ActiveAuxSlots, almemory_order_acquire);
|
||||
for(i = 0;i < auxslots->count;i++)
|
||||
{
|
||||
ALeffectslot *slot = auxslots->slot[i];
|
||||
if(!ATOMIC_FLAG_TEST_AND_SET(&slot->PropsClean, almemory_order_acq_rel))
|
||||
UpdateEffectSlotProps(slot, context);
|
||||
}
|
||||
UnlockEffectSlotList(context);
|
||||
}
|
||||
|
||||
ALvoid ReleaseALAuxiliaryEffectSlots(ALCcontext *context)
|
||||
{
|
||||
ALeffectslotPtr *iter = VECTOR_BEGIN(context->EffectSlotList);
|
||||
ALeffectslotPtr *end = VECTOR_END(context->EffectSlotList);
|
||||
size_t leftover = 0;
|
||||
|
||||
for(;iter != end;iter++)
|
||||
{
|
||||
ALeffectslot *slot = *iter;
|
||||
if(!slot) continue;
|
||||
*iter = NULL;
|
||||
|
||||
DeinitEffectSlot(slot);
|
||||
|
||||
memset(slot, 0, sizeof(*slot));
|
||||
al_free(slot);
|
||||
++leftover;
|
||||
}
|
||||
if(leftover > 0)
|
||||
WARN("(%p) Deleted "SZFMT" AuxiliaryEffectSlot%s\n", context, leftover, (leftover==1)?"":"s");
|
||||
}
|
||||
+1305
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,819 @@
|
||||
/**
|
||||
* 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 <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "alMain.h"
|
||||
#include "alEffect.h"
|
||||
#include "alError.h"
|
||||
|
||||
|
||||
extern inline void LockEffectList(ALCdevice *device);
|
||||
extern inline void UnlockEffectList(ALCdevice *device);
|
||||
extern inline ALboolean IsReverbEffect(ALenum type);
|
||||
|
||||
const struct EffectList EffectList[EFFECTLIST_SIZE] = {
|
||||
{ "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB },
|
||||
{ "reverb", REVERB_EFFECT, AL_EFFECT_REVERB },
|
||||
{ "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH },
|
||||
{ "chorus", CHORUS_EFFECT, AL_EFFECT_CHORUS },
|
||||
{ "compressor", COMPRESSOR_EFFECT, AL_EFFECT_COMPRESSOR },
|
||||
{ "distortion", DISTORTION_EFFECT, AL_EFFECT_DISTORTION },
|
||||
{ "echo", ECHO_EFFECT, AL_EFFECT_ECHO },
|
||||
{ "equalizer", EQUALIZER_EFFECT, AL_EFFECT_EQUALIZER },
|
||||
{ "flanger", FLANGER_EFFECT, AL_EFFECT_FLANGER },
|
||||
{ "fshifter", FSHIFTER_EFFECT, AL_EFFECT_FREQUENCY_SHIFTER },
|
||||
{ "modulator", MODULATOR_EFFECT, AL_EFFECT_RING_MODULATOR },
|
||||
{ "pshifter", PSHIFTER_EFFECT, AL_EFFECT_PITCH_SHIFTER },
|
||||
{ "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT },
|
||||
{ "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE },
|
||||
};
|
||||
|
||||
ALboolean DisabledEffects[MAX_EFFECTS];
|
||||
|
||||
static ALeffect *AllocEffect(ALCcontext *context);
|
||||
static void FreeEffect(ALCdevice *device, ALeffect *effect);
|
||||
static void InitEffectParams(ALeffect *effect, ALenum type);
|
||||
|
||||
static inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
|
||||
{
|
||||
EffectSubList *sublist;
|
||||
ALuint lidx = (id-1) >> 6;
|
||||
ALsizei slidx = (id-1) & 0x3f;
|
||||
|
||||
if(UNLIKELY(lidx >= VECTOR_SIZE(device->EffectList)))
|
||||
return NULL;
|
||||
sublist = &VECTOR_ELEM(device->EffectList, lidx);
|
||||
if(UNLIKELY(sublist->FreeMask & (U64(1)<<slidx)))
|
||||
return NULL;
|
||||
return sublist->Effects + slidx;
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALsizei cur;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!(n >= 0))
|
||||
alSetError(context, AL_INVALID_VALUE, "Generating %d effects", n);
|
||||
else for(cur = 0;cur < n;cur++)
|
||||
{
|
||||
ALeffect *effect = AllocEffect(context);
|
||||
if(!effect)
|
||||
{
|
||||
alDeleteEffects(cur, effects);
|
||||
break;
|
||||
}
|
||||
effects[cur] = effect->id;
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects)
|
||||
{
|
||||
ALCdevice *device;
|
||||
ALCcontext *context;
|
||||
ALeffect *effect;
|
||||
ALsizei i;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
device = context->Device;
|
||||
LockEffectList(device);
|
||||
if(!(n >= 0))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Deleting %d effects", n);
|
||||
for(i = 0;i < n;i++)
|
||||
{
|
||||
if(effects[i] && LookupEffect(device, effects[i]) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect ID %u", effects[i]);
|
||||
}
|
||||
for(i = 0;i < n;i++)
|
||||
{
|
||||
if((effect=LookupEffect(device, effects[i])) != NULL)
|
||||
FreeEffect(device, effect);
|
||||
}
|
||||
|
||||
done:
|
||||
UnlockEffectList(device);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALboolean result;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return AL_FALSE;
|
||||
|
||||
LockEffectList(Context->Device);
|
||||
result = ((!effect || LookupEffect(Context->Device, effect)) ?
|
||||
AL_TRUE : AL_FALSE);
|
||||
UnlockEffectList(Context->Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALeffect *ALEffect;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockEffectList(Device);
|
||||
if((ALEffect=LookupEffect(Device, effect)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
if(param == AL_EFFECT_TYPE)
|
||||
{
|
||||
ALboolean isOk = (value == AL_EFFECT_NULL);
|
||||
ALint i;
|
||||
for(i = 0;!isOk && i < EFFECTLIST_SIZE;i++)
|
||||
{
|
||||
if(value == EffectList[i].val &&
|
||||
!DisabledEffects[EffectList[i].type])
|
||||
isOk = AL_TRUE;
|
||||
}
|
||||
|
||||
if(isOk)
|
||||
InitEffectParams(ALEffect, value);
|
||||
else
|
||||
alSetError(Context, AL_INVALID_VALUE, "Effect type 0x%04x not supported", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_setParami(ALEffect, Context, param, value);
|
||||
}
|
||||
}
|
||||
UnlockEffectList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALeffect *ALEffect;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECT_TYPE:
|
||||
alEffecti(effect, param, values[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockEffectList(Device);
|
||||
if((ALEffect=LookupEffect(Device, effect)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_setParamiv(ALEffect, Context, param, values);
|
||||
}
|
||||
UnlockEffectList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALeffect *ALEffect;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockEffectList(Device);
|
||||
if((ALEffect=LookupEffect(Device, effect)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_setParamf(ALEffect, Context, param, value);
|
||||
}
|
||||
UnlockEffectList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALeffect *ALEffect;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockEffectList(Device);
|
||||
if((ALEffect=LookupEffect(Device, effect)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_setParamfv(ALEffect, Context, param, values);
|
||||
}
|
||||
UnlockEffectList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALeffect *ALEffect;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockEffectList(Device);
|
||||
if((ALEffect=LookupEffect(Device, effect)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
if(param == AL_EFFECT_TYPE)
|
||||
*value = ALEffect->type;
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_getParami(ALEffect, Context, param, value);
|
||||
}
|
||||
}
|
||||
UnlockEffectList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALeffect *ALEffect;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_EFFECT_TYPE:
|
||||
alGetEffecti(effect, param, values);
|
||||
return;
|
||||
}
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockEffectList(Device);
|
||||
if((ALEffect=LookupEffect(Device, effect)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_getParamiv(ALEffect, Context, param, values);
|
||||
}
|
||||
UnlockEffectList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALeffect *ALEffect;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockEffectList(Device);
|
||||
if((ALEffect=LookupEffect(Device, effect)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_getParamf(ALEffect, Context, param, value);
|
||||
}
|
||||
UnlockEffectList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALeffect *ALEffect;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockEffectList(Device);
|
||||
if((ALEffect=LookupEffect(Device, effect)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALeffect_getParamfv(ALEffect, Context, param, values);
|
||||
}
|
||||
UnlockEffectList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
|
||||
void InitEffect(ALeffect *effect)
|
||||
{
|
||||
InitEffectParams(effect, AL_EFFECT_NULL);
|
||||
}
|
||||
|
||||
static ALeffect *AllocEffect(ALCcontext *context)
|
||||
{
|
||||
ALCdevice *device = context->Device;
|
||||
EffectSubList *sublist, *subend;
|
||||
ALeffect *effect = NULL;
|
||||
ALsizei lidx = 0;
|
||||
ALsizei slidx;
|
||||
|
||||
almtx_lock(&device->EffectLock);
|
||||
sublist = VECTOR_BEGIN(device->EffectList);
|
||||
subend = VECTOR_END(device->EffectList);
|
||||
for(;sublist != subend;++sublist)
|
||||
{
|
||||
if(sublist->FreeMask)
|
||||
{
|
||||
slidx = CTZ64(sublist->FreeMask);
|
||||
effect = sublist->Effects + slidx;
|
||||
break;
|
||||
}
|
||||
++lidx;
|
||||
}
|
||||
if(UNLIKELY(!effect))
|
||||
{
|
||||
const EffectSubList empty_sublist = { 0, NULL };
|
||||
/* Don't allocate so many list entries that the 32-bit ID could
|
||||
* overflow...
|
||||
*/
|
||||
if(UNLIKELY(VECTOR_SIZE(device->EffectList) >= 1<<25))
|
||||
{
|
||||
almtx_unlock(&device->EffectLock);
|
||||
alSetError(context, AL_OUT_OF_MEMORY, "Too many effects allocated");
|
||||
return NULL;
|
||||
}
|
||||
lidx = (ALsizei)VECTOR_SIZE(device->EffectList);
|
||||
VECTOR_PUSH_BACK(device->EffectList, empty_sublist);
|
||||
sublist = &VECTOR_BACK(device->EffectList);
|
||||
sublist->FreeMask = ~U64(0);
|
||||
sublist->Effects = al_calloc(16, sizeof(ALeffect)*64);
|
||||
if(UNLIKELY(!sublist->Effects))
|
||||
{
|
||||
VECTOR_POP_BACK(device->EffectList);
|
||||
almtx_unlock(&device->EffectLock);
|
||||
alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate effect batch");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
slidx = 0;
|
||||
effect = sublist->Effects + slidx;
|
||||
}
|
||||
|
||||
memset(effect, 0, sizeof(*effect));
|
||||
InitEffectParams(effect, AL_EFFECT_NULL);
|
||||
|
||||
/* Add 1 to avoid effect ID 0. */
|
||||
effect->id = ((lidx<<6) | slidx) + 1;
|
||||
|
||||
sublist->FreeMask &= ~(U64(1)<<slidx);
|
||||
almtx_unlock(&device->EffectLock);
|
||||
|
||||
return effect;
|
||||
}
|
||||
|
||||
static void FreeEffect(ALCdevice *device, ALeffect *effect)
|
||||
{
|
||||
ALuint id = effect->id - 1;
|
||||
ALsizei lidx = id >> 6;
|
||||
ALsizei slidx = id & 0x3f;
|
||||
|
||||
memset(effect, 0, sizeof(*effect));
|
||||
|
||||
VECTOR_ELEM(device->EffectList, lidx).FreeMask |= U64(1) << slidx;
|
||||
}
|
||||
|
||||
void ReleaseALEffects(ALCdevice *device)
|
||||
{
|
||||
EffectSubList *sublist = VECTOR_BEGIN(device->EffectList);
|
||||
EffectSubList *subend = VECTOR_END(device->EffectList);
|
||||
size_t leftover = 0;
|
||||
for(;sublist != subend;++sublist)
|
||||
{
|
||||
ALuint64 usemask = ~sublist->FreeMask;
|
||||
while(usemask)
|
||||
{
|
||||
ALsizei idx = CTZ64(usemask);
|
||||
ALeffect *effect = sublist->Effects + idx;
|
||||
|
||||
memset(effect, 0, sizeof(*effect));
|
||||
++leftover;
|
||||
|
||||
usemask &= ~(U64(1) << idx);
|
||||
}
|
||||
sublist->FreeMask = ~usemask;
|
||||
}
|
||||
if(leftover > 0)
|
||||
WARN("(%p) Deleted "SZFMT" Effect%s\n", device, leftover, (leftover==1)?"":"s");
|
||||
}
|
||||
|
||||
|
||||
static void InitEffectParams(ALeffect *effect, ALenum type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case AL_EFFECT_EAXREVERB:
|
||||
effect->Props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY;
|
||||
effect->Props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION;
|
||||
effect->Props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN;
|
||||
effect->Props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF;
|
||||
effect->Props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF;
|
||||
effect->Props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME;
|
||||
effect->Props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO;
|
||||
effect->Props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO;
|
||||
effect->Props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN;
|
||||
effect->Props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY;
|
||||
effect->Props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
|
||||
effect->Props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
|
||||
effect->Props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
|
||||
effect->Props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN;
|
||||
effect->Props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY;
|
||||
effect->Props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
|
||||
effect->Props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
|
||||
effect->Props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
|
||||
effect->Props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME;
|
||||
effect->Props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH;
|
||||
effect->Props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME;
|
||||
effect->Props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH;
|
||||
effect->Props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
|
||||
effect->Props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE;
|
||||
effect->Props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE;
|
||||
effect->Props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
|
||||
effect->Props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT;
|
||||
effect->vtab = &ALeaxreverb_vtable;
|
||||
break;
|
||||
case AL_EFFECT_REVERB:
|
||||
effect->Props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY;
|
||||
effect->Props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION;
|
||||
effect->Props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN;
|
||||
effect->Props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF;
|
||||
effect->Props.Reverb.GainLF = 1.0f;
|
||||
effect->Props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME;
|
||||
effect->Props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO;
|
||||
effect->Props.Reverb.DecayLFRatio = 1.0f;
|
||||
effect->Props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN;
|
||||
effect->Props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY;
|
||||
effect->Props.Reverb.ReflectionsPan[0] = 0.0f;
|
||||
effect->Props.Reverb.ReflectionsPan[1] = 0.0f;
|
||||
effect->Props.Reverb.ReflectionsPan[2] = 0.0f;
|
||||
effect->Props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN;
|
||||
effect->Props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY;
|
||||
effect->Props.Reverb.LateReverbPan[0] = 0.0f;
|
||||
effect->Props.Reverb.LateReverbPan[1] = 0.0f;
|
||||
effect->Props.Reverb.LateReverbPan[2] = 0.0f;
|
||||
effect->Props.Reverb.EchoTime = 0.25f;
|
||||
effect->Props.Reverb.EchoDepth = 0.0f;
|
||||
effect->Props.Reverb.ModulationTime = 0.25f;
|
||||
effect->Props.Reverb.ModulationDepth = 0.0f;
|
||||
effect->Props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
|
||||
effect->Props.Reverb.HFReference = 5000.0f;
|
||||
effect->Props.Reverb.LFReference = 250.0f;
|
||||
effect->Props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
|
||||
effect->Props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT;
|
||||
effect->vtab = &ALreverb_vtable;
|
||||
break;
|
||||
case AL_EFFECT_AUTOWAH:
|
||||
effect->Props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
|
||||
effect->Props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
|
||||
effect->Props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
|
||||
effect->Props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
|
||||
effect->vtab = &ALautowah_vtable;
|
||||
break;
|
||||
case AL_EFFECT_CHORUS:
|
||||
effect->Props.Chorus.Waveform = AL_CHORUS_DEFAULT_WAVEFORM;
|
||||
effect->Props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE;
|
||||
effect->Props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE;
|
||||
effect->Props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH;
|
||||
effect->Props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
|
||||
effect->Props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY;
|
||||
effect->vtab = &ALchorus_vtable;
|
||||
break;
|
||||
case AL_EFFECT_COMPRESSOR:
|
||||
effect->Props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
|
||||
effect->vtab = &ALcompressor_vtable;
|
||||
break;
|
||||
case AL_EFFECT_DISTORTION:
|
||||
effect->Props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE;
|
||||
effect->Props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN;
|
||||
effect->Props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
|
||||
effect->Props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
|
||||
effect->Props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
|
||||
effect->vtab = &ALdistortion_vtable;
|
||||
break;
|
||||
case AL_EFFECT_ECHO:
|
||||
effect->Props.Echo.Delay = AL_ECHO_DEFAULT_DELAY;
|
||||
effect->Props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
|
||||
effect->Props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING;
|
||||
effect->Props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
|
||||
effect->Props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD;
|
||||
effect->vtab = &ALecho_vtable;
|
||||
break;
|
||||
case AL_EFFECT_EQUALIZER:
|
||||
effect->Props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
|
||||
effect->Props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
|
||||
effect->Props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
|
||||
effect->Props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
|
||||
effect->Props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
|
||||
effect->Props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
|
||||
effect->Props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
|
||||
effect->Props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
|
||||
effect->Props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
|
||||
effect->Props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
|
||||
effect->vtab = &ALequalizer_vtable;
|
||||
break;
|
||||
case AL_EFFECT_FLANGER:
|
||||
effect->Props.Chorus.Waveform = AL_FLANGER_DEFAULT_WAVEFORM;
|
||||
effect->Props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE;
|
||||
effect->Props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE;
|
||||
effect->Props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH;
|
||||
effect->Props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
|
||||
effect->Props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY;
|
||||
effect->vtab = &ALflanger_vtable;
|
||||
break;
|
||||
case AL_EFFECT_FREQUENCY_SHIFTER:
|
||||
effect->Props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
|
||||
effect->Props.Fshifter.LeftDirection = AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION;
|
||||
effect->Props.Fshifter.RightDirection = AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION;
|
||||
effect->vtab = &ALfshifter_vtable;
|
||||
break;
|
||||
case AL_EFFECT_RING_MODULATOR:
|
||||
effect->Props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
|
||||
effect->Props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
|
||||
effect->Props.Modulator.Waveform = AL_RING_MODULATOR_DEFAULT_WAVEFORM;
|
||||
effect->vtab = &ALmodulator_vtable;
|
||||
break;
|
||||
case AL_EFFECT_PITCH_SHIFTER:
|
||||
effect->Props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
|
||||
effect->Props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
|
||||
effect->vtab = &ALpshifter_vtable;
|
||||
break;
|
||||
case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT:
|
||||
case AL_EFFECT_DEDICATED_DIALOGUE:
|
||||
effect->Props.Dedicated.Gain = 1.0f;
|
||||
effect->vtab = &ALdedicated_vtable;
|
||||
break;
|
||||
default:
|
||||
effect->vtab = &ALnull_vtable;
|
||||
break;
|
||||
}
|
||||
effect->type = type;
|
||||
}
|
||||
|
||||
|
||||
#include "AL/efx-presets.h"
|
||||
|
||||
#define DECL(x) { #x, EFX_REVERB_PRESET_##x }
|
||||
static const struct {
|
||||
const char name[32];
|
||||
EFXEAXREVERBPROPERTIES props;
|
||||
} reverblist[] = {
|
||||
DECL(GENERIC),
|
||||
DECL(PADDEDCELL),
|
||||
DECL(ROOM),
|
||||
DECL(BATHROOM),
|
||||
DECL(LIVINGROOM),
|
||||
DECL(STONEROOM),
|
||||
DECL(AUDITORIUM),
|
||||
DECL(CONCERTHALL),
|
||||
DECL(CAVE),
|
||||
DECL(ARENA),
|
||||
DECL(HANGAR),
|
||||
DECL(CARPETEDHALLWAY),
|
||||
DECL(HALLWAY),
|
||||
DECL(STONECORRIDOR),
|
||||
DECL(ALLEY),
|
||||
DECL(FOREST),
|
||||
DECL(CITY),
|
||||
DECL(MOUNTAINS),
|
||||
DECL(QUARRY),
|
||||
DECL(PLAIN),
|
||||
DECL(PARKINGLOT),
|
||||
DECL(SEWERPIPE),
|
||||
DECL(UNDERWATER),
|
||||
DECL(DRUGGED),
|
||||
DECL(DIZZY),
|
||||
DECL(PSYCHOTIC),
|
||||
|
||||
DECL(CASTLE_SMALLROOM),
|
||||
DECL(CASTLE_SHORTPASSAGE),
|
||||
DECL(CASTLE_MEDIUMROOM),
|
||||
DECL(CASTLE_LARGEROOM),
|
||||
DECL(CASTLE_LONGPASSAGE),
|
||||
DECL(CASTLE_HALL),
|
||||
DECL(CASTLE_CUPBOARD),
|
||||
DECL(CASTLE_COURTYARD),
|
||||
DECL(CASTLE_ALCOVE),
|
||||
|
||||
DECL(FACTORY_SMALLROOM),
|
||||
DECL(FACTORY_SHORTPASSAGE),
|
||||
DECL(FACTORY_MEDIUMROOM),
|
||||
DECL(FACTORY_LARGEROOM),
|
||||
DECL(FACTORY_LONGPASSAGE),
|
||||
DECL(FACTORY_HALL),
|
||||
DECL(FACTORY_CUPBOARD),
|
||||
DECL(FACTORY_COURTYARD),
|
||||
DECL(FACTORY_ALCOVE),
|
||||
|
||||
DECL(ICEPALACE_SMALLROOM),
|
||||
DECL(ICEPALACE_SHORTPASSAGE),
|
||||
DECL(ICEPALACE_MEDIUMROOM),
|
||||
DECL(ICEPALACE_LARGEROOM),
|
||||
DECL(ICEPALACE_LONGPASSAGE),
|
||||
DECL(ICEPALACE_HALL),
|
||||
DECL(ICEPALACE_CUPBOARD),
|
||||
DECL(ICEPALACE_COURTYARD),
|
||||
DECL(ICEPALACE_ALCOVE),
|
||||
|
||||
DECL(SPACESTATION_SMALLROOM),
|
||||
DECL(SPACESTATION_SHORTPASSAGE),
|
||||
DECL(SPACESTATION_MEDIUMROOM),
|
||||
DECL(SPACESTATION_LARGEROOM),
|
||||
DECL(SPACESTATION_LONGPASSAGE),
|
||||
DECL(SPACESTATION_HALL),
|
||||
DECL(SPACESTATION_CUPBOARD),
|
||||
DECL(SPACESTATION_ALCOVE),
|
||||
|
||||
DECL(WOODEN_SMALLROOM),
|
||||
DECL(WOODEN_SHORTPASSAGE),
|
||||
DECL(WOODEN_MEDIUMROOM),
|
||||
DECL(WOODEN_LARGEROOM),
|
||||
DECL(WOODEN_LONGPASSAGE),
|
||||
DECL(WOODEN_HALL),
|
||||
DECL(WOODEN_CUPBOARD),
|
||||
DECL(WOODEN_COURTYARD),
|
||||
DECL(WOODEN_ALCOVE),
|
||||
|
||||
DECL(SPORT_EMPTYSTADIUM),
|
||||
DECL(SPORT_SQUASHCOURT),
|
||||
DECL(SPORT_SMALLSWIMMINGPOOL),
|
||||
DECL(SPORT_LARGESWIMMINGPOOL),
|
||||
DECL(SPORT_GYMNASIUM),
|
||||
DECL(SPORT_FULLSTADIUM),
|
||||
DECL(SPORT_STADIUMTANNOY),
|
||||
|
||||
DECL(PREFAB_WORKSHOP),
|
||||
DECL(PREFAB_SCHOOLROOM),
|
||||
DECL(PREFAB_PRACTISEROOM),
|
||||
DECL(PREFAB_OUTHOUSE),
|
||||
DECL(PREFAB_CARAVAN),
|
||||
|
||||
DECL(DOME_TOMB),
|
||||
DECL(PIPE_SMALL),
|
||||
DECL(DOME_SAINTPAULS),
|
||||
DECL(PIPE_LONGTHIN),
|
||||
DECL(PIPE_LARGE),
|
||||
DECL(PIPE_RESONANT),
|
||||
|
||||
DECL(OUTDOORS_BACKYARD),
|
||||
DECL(OUTDOORS_ROLLINGPLAINS),
|
||||
DECL(OUTDOORS_DEEPCANYON),
|
||||
DECL(OUTDOORS_CREEK),
|
||||
DECL(OUTDOORS_VALLEY),
|
||||
|
||||
DECL(MOOD_HEAVEN),
|
||||
DECL(MOOD_HELL),
|
||||
DECL(MOOD_MEMORY),
|
||||
|
||||
DECL(DRIVING_COMMENTATOR),
|
||||
DECL(DRIVING_PITGARAGE),
|
||||
DECL(DRIVING_INCAR_RACER),
|
||||
DECL(DRIVING_INCAR_SPORTS),
|
||||
DECL(DRIVING_INCAR_LUXURY),
|
||||
DECL(DRIVING_FULLGRANDSTAND),
|
||||
DECL(DRIVING_EMPTYGRANDSTAND),
|
||||
DECL(DRIVING_TUNNEL),
|
||||
|
||||
DECL(CITY_STREETS),
|
||||
DECL(CITY_SUBWAY),
|
||||
DECL(CITY_MUSEUM),
|
||||
DECL(CITY_LIBRARY),
|
||||
DECL(CITY_UNDERPASS),
|
||||
DECL(CITY_ABANDONED),
|
||||
|
||||
DECL(DUSTYROOM),
|
||||
DECL(CHAPEL),
|
||||
DECL(SMALLWATERROOM),
|
||||
};
|
||||
#undef DECL
|
||||
|
||||
void LoadReverbPreset(const char *name, ALeffect *effect)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if(strcasecmp(name, "NONE") == 0)
|
||||
{
|
||||
InitEffectParams(effect, AL_EFFECT_NULL);
|
||||
TRACE("Loading reverb '%s'\n", "NONE");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!DisabledEffects[EAXREVERB_EFFECT])
|
||||
InitEffectParams(effect, AL_EFFECT_EAXREVERB);
|
||||
else if(!DisabledEffects[REVERB_EFFECT])
|
||||
InitEffectParams(effect, AL_EFFECT_REVERB);
|
||||
else
|
||||
InitEffectParams(effect, AL_EFFECT_NULL);
|
||||
for(i = 0;i < COUNTOF(reverblist);i++)
|
||||
{
|
||||
const EFXEAXREVERBPROPERTIES *props;
|
||||
|
||||
if(strcasecmp(name, reverblist[i].name) != 0)
|
||||
continue;
|
||||
|
||||
TRACE("Loading reverb '%s'\n", reverblist[i].name);
|
||||
props = &reverblist[i].props;
|
||||
effect->Props.Reverb.Density = props->flDensity;
|
||||
effect->Props.Reverb.Diffusion = props->flDiffusion;
|
||||
effect->Props.Reverb.Gain = props->flGain;
|
||||
effect->Props.Reverb.GainHF = props->flGainHF;
|
||||
effect->Props.Reverb.GainLF = props->flGainLF;
|
||||
effect->Props.Reverb.DecayTime = props->flDecayTime;
|
||||
effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio;
|
||||
effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio;
|
||||
effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain;
|
||||
effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay;
|
||||
effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0];
|
||||
effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1];
|
||||
effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2];
|
||||
effect->Props.Reverb.LateReverbGain = props->flLateReverbGain;
|
||||
effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay;
|
||||
effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0];
|
||||
effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1];
|
||||
effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2];
|
||||
effect->Props.Reverb.EchoTime = props->flEchoTime;
|
||||
effect->Props.Reverb.EchoDepth = props->flEchoDepth;
|
||||
effect->Props.Reverb.ModulationTime = props->flModulationTime;
|
||||
effect->Props.Reverb.ModulationDepth = props->flModulationDepth;
|
||||
effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF;
|
||||
effect->Props.Reverb.HFReference = props->flHFReference;
|
||||
effect->Props.Reverb.LFReference = props->flLFReference;
|
||||
effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor;
|
||||
effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit;
|
||||
return;
|
||||
}
|
||||
|
||||
WARN("Reverb preset '%s' not found\n", name);
|
||||
}
|
||||
@@ -20,52 +20,46 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef HAVE_WINDOWS_H
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "alMain.h"
|
||||
#include "AL/alc.h"
|
||||
#include "alError.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "alexcpt.h"
|
||||
#include "almalloc.h"
|
||||
#include "logging.h"
|
||||
#include "opthelpers.h"
|
||||
#include "vector.h"
|
||||
ALboolean TrapALError = AL_FALSE;
|
||||
|
||||
|
||||
bool TrapALError{false};
|
||||
|
||||
void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
|
||||
void alSetError(ALCcontext *context, ALenum errorCode, const char *msg, ...)
|
||||
{
|
||||
auto message = al::vector<char>(256);
|
||||
ALenum curerr = AL_NO_ERROR;
|
||||
char message[1024] = { 0 };
|
||||
va_list args;
|
||||
int msglen;
|
||||
|
||||
va_list args, args2;
|
||||
va_start(args, msg);
|
||||
va_copy(args2, args);
|
||||
int msglen{std::vsnprintf(message.data(), message.size(), msg, args)};
|
||||
if(msglen >= 0 && static_cast<size_t>(msglen) >= message.size())
|
||||
{
|
||||
message.resize(static_cast<size_t>(msglen) + 1u);
|
||||
msglen = std::vsnprintf(message.data(), message.size(), msg, args2);
|
||||
}
|
||||
va_end(args2);
|
||||
msglen = vsnprintf(message, sizeof(message), msg, args);
|
||||
va_end(args);
|
||||
|
||||
if(msglen >= 0) msg = message.data();
|
||||
else msg = "<internal error constructing message>";
|
||||
if(msglen < 0 || (size_t)msglen >= sizeof(message))
|
||||
{
|
||||
message[sizeof(message)-1] = 0;
|
||||
msglen = (int)strlen(message);
|
||||
}
|
||||
if(msglen > 0)
|
||||
msg = message;
|
||||
else
|
||||
{
|
||||
msg = "<internal error constructing message>";
|
||||
msglen = (int)strlen(msg);
|
||||
}
|
||||
|
||||
WARN("Error generated on context %p, code 0x%04x, \"%s\"\n",
|
||||
decltype(std::declval<void*>()){this}, errorCode, msg);
|
||||
context, errorCode, message);
|
||||
if(TrapALError)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@@ -77,17 +71,28 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
|
||||
#endif
|
||||
}
|
||||
|
||||
ALenum curerr{AL_NO_ERROR};
|
||||
mLastError.compare_exchange_strong(curerr, errorCode);
|
||||
ATOMIC_COMPARE_EXCHANGE_STRONG_SEQ(&context->LastError, &curerr, errorCode);
|
||||
if((ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed)&EventType_Error))
|
||||
{
|
||||
ALbitfieldSOFT enabledevts;
|
||||
almtx_lock(&context->EventCbLock);
|
||||
enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed);
|
||||
if((enabledevts&EventType_Error) && context->EventCb)
|
||||
(*context->EventCb)(AL_EVENT_TYPE_ERROR_SOFT, 0, errorCode, msglen, msg,
|
||||
context->EventParam);
|
||||
almtx_unlock(&context->EventCbLock);
|
||||
}
|
||||
}
|
||||
|
||||
AL_API ALenum AL_APIENTRY alGetError(void)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context)
|
||||
ALCcontext *context;
|
||||
ALenum errorCode;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context)
|
||||
{
|
||||
constexpr ALenum deferror{AL_INVALID_OPERATION};
|
||||
const ALenum deferror = AL_INVALID_OPERATION;
|
||||
WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror);
|
||||
if(TrapALError)
|
||||
{
|
||||
@@ -101,6 +106,8 @@ START_API_FUNC
|
||||
return deferror;
|
||||
}
|
||||
|
||||
return context->mLastError.exchange(AL_NO_ERROR);
|
||||
errorCode = ATOMIC_EXCHANGE_SEQ(&context->LastError, AL_NO_ERROR);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
return errorCode;
|
||||
}
|
||||
END_API_FUNC
|
||||
@@ -20,36 +20,45 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "alError.h"
|
||||
#include "alMain.h"
|
||||
#include "alFilter.h"
|
||||
#include "alEffect.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
#include "alSource.h"
|
||||
#include "alBuffer.h"
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "alexcpt.h"
|
||||
#include "alstring.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return AL_FALSE;
|
||||
ALboolean ret = AL_FALSE;
|
||||
ALCcontext *context;
|
||||
const char *ptr;
|
||||
size_t len;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return AL_FALSE;
|
||||
|
||||
if(!extName)
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer");
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "NULL pointer");
|
||||
|
||||
size_t len{strlen(extName)};
|
||||
const char *ptr{context->mExtensionList};
|
||||
len = strlen(extName);
|
||||
ptr = context->ExtensionList;
|
||||
while(ptr && *ptr)
|
||||
{
|
||||
if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len])))
|
||||
return AL_TRUE;
|
||||
|
||||
if((ptr=strchr(ptr, ' ')) != nullptr)
|
||||
if(strncasecmp(ptr, extName, len) == 0 &&
|
||||
(ptr[len] == '\0' || isspace(ptr[len])))
|
||||
{
|
||||
ret = AL_TRUE;
|
||||
break;
|
||||
}
|
||||
if((ptr=strchr(ptr, ' ')) != NULL)
|
||||
{
|
||||
do {
|
||||
++ptr;
|
||||
@@ -57,23 +66,22 @@ START_API_FUNC
|
||||
}
|
||||
}
|
||||
|
||||
return AL_FALSE;
|
||||
done:
|
||||
ALCcontext_DecRef(context);
|
||||
return ret;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(!funcName) return nullptr;
|
||||
return alcGetProcAddress(nullptr, funcName);
|
||||
if(!funcName)
|
||||
return NULL;
|
||||
return alcGetProcAddress(NULL, funcName);
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(!enumName) return static_cast<ALenum>(0);
|
||||
return alcGetEnumValue(nullptr, enumName);
|
||||
if(!enumName)
|
||||
return (ALenum)0;
|
||||
return alcGetEnumValue(NULL, enumName);
|
||||
}
|
||||
END_API_FUNC
|
||||
@@ -0,0 +1,671 @@
|
||||
/**
|
||||
* 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 <stdlib.h>
|
||||
|
||||
#include "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "alFilter.h"
|
||||
#include "alError.h"
|
||||
|
||||
|
||||
#define FILTER_MIN_GAIN 0.0f
|
||||
#define FILTER_MAX_GAIN 4.0f /* +12dB */
|
||||
|
||||
extern inline void LockFilterList(ALCdevice *device);
|
||||
extern inline void UnlockFilterList(ALCdevice *device);
|
||||
|
||||
static ALfilter *AllocFilter(ALCcontext *context);
|
||||
static void FreeFilter(ALCdevice *device, ALfilter *filter);
|
||||
static void InitFilterParams(ALfilter *filter, ALenum type);
|
||||
|
||||
static inline ALfilter *LookupFilter(ALCdevice *device, ALuint id)
|
||||
{
|
||||
FilterSubList *sublist;
|
||||
ALuint lidx = (id-1) >> 6;
|
||||
ALsizei slidx = (id-1) & 0x3f;
|
||||
|
||||
if(UNLIKELY(lidx >= VECTOR_SIZE(device->FilterList)))
|
||||
return NULL;
|
||||
sublist = &VECTOR_ELEM(device->FilterList, lidx);
|
||||
if(UNLIKELY(sublist->FreeMask & (U64(1)<<slidx)))
|
||||
return NULL;
|
||||
return sublist->Filters + slidx;
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALsizei cur = 0;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!(n >= 0))
|
||||
alSetError(context, AL_INVALID_VALUE, "Generating %d filters", n);
|
||||
else for(cur = 0;cur < n;cur++)
|
||||
{
|
||||
ALfilter *filter = AllocFilter(context);
|
||||
if(!filter)
|
||||
{
|
||||
alDeleteFilters(cur, filters);
|
||||
break;
|
||||
}
|
||||
|
||||
filters[cur] = filter->id;
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters)
|
||||
{
|
||||
ALCdevice *device;
|
||||
ALCcontext *context;
|
||||
ALfilter *filter;
|
||||
ALsizei i;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
device = context->Device;
|
||||
LockFilterList(device);
|
||||
if(!(n >= 0))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Deleting %d filters", n);
|
||||
for(i = 0;i < n;i++)
|
||||
{
|
||||
if(filters[i] && LookupFilter(device, filters[i]) == NULL)
|
||||
SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid filter ID %u", filters[i]);
|
||||
}
|
||||
for(i = 0;i < n;i++)
|
||||
{
|
||||
if((filter=LookupFilter(device, filters[i])) != NULL)
|
||||
FreeFilter(device, filter);
|
||||
}
|
||||
|
||||
done:
|
||||
UnlockFilterList(device);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALboolean result;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return AL_FALSE;
|
||||
|
||||
LockFilterList(Context->Device);
|
||||
result = ((!filter || LookupFilter(Context->Device, filter)) ?
|
||||
AL_TRUE : AL_FALSE);
|
||||
UnlockFilterList(Context->Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALfilter *ALFilter;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockFilterList(Device);
|
||||
if((ALFilter=LookupFilter(Device, filter)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
if(param == AL_FILTER_TYPE)
|
||||
{
|
||||
if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS ||
|
||||
value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)
|
||||
InitFilterParams(ALFilter, value);
|
||||
else
|
||||
alSetError(Context, AL_INVALID_VALUE, "Invalid filter type 0x%04x", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_setParami(ALFilter, Context, param, value);
|
||||
}
|
||||
}
|
||||
UnlockFilterList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *values)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALfilter *ALFilter;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_FILTER_TYPE:
|
||||
alFilteri(filter, param, values[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockFilterList(Device);
|
||||
if((ALFilter=LookupFilter(Device, filter)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_setParamiv(ALFilter, Context, param, values);
|
||||
}
|
||||
UnlockFilterList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALfilter *ALFilter;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockFilterList(Device);
|
||||
if((ALFilter=LookupFilter(Device, filter)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_setParamf(ALFilter, Context, param, value);
|
||||
}
|
||||
UnlockFilterList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALfilter *ALFilter;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockFilterList(Device);
|
||||
if((ALFilter=LookupFilter(Device, filter)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_setParamfv(ALFilter, Context, param, values);
|
||||
}
|
||||
UnlockFilterList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALfilter *ALFilter;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockFilterList(Device);
|
||||
if((ALFilter=LookupFilter(Device, filter)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
if(param == AL_FILTER_TYPE)
|
||||
*value = ALFilter->type;
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_getParami(ALFilter, Context, param, value);
|
||||
}
|
||||
}
|
||||
UnlockFilterList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *values)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALfilter *ALFilter;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_FILTER_TYPE:
|
||||
alGetFilteri(filter, param, values);
|
||||
return;
|
||||
}
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockFilterList(Device);
|
||||
if((ALFilter=LookupFilter(Device, filter)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_getParamiv(ALFilter, Context, param, values);
|
||||
}
|
||||
UnlockFilterList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALfilter *ALFilter;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockFilterList(Device);
|
||||
if((ALFilter=LookupFilter(Device, filter)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_getParamf(ALFilter, Context, param, value);
|
||||
}
|
||||
UnlockFilterList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values)
|
||||
{
|
||||
ALCcontext *Context;
|
||||
ALCdevice *Device;
|
||||
ALfilter *ALFilter;
|
||||
|
||||
Context = GetContextRef();
|
||||
if(!Context) return;
|
||||
|
||||
Device = Context->Device;
|
||||
LockFilterList(Device);
|
||||
if((ALFilter=LookupFilter(Device, filter)) == NULL)
|
||||
alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
/* Call the appropriate handler */
|
||||
ALfilter_getParamfv(ALFilter, Context, param, values);
|
||||
}
|
||||
UnlockFilterList(Device);
|
||||
|
||||
ALCcontext_DecRef(Context);
|
||||
}
|
||||
|
||||
|
||||
static void ALlowpass_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); }
|
||||
static void ALlowpass_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); }
|
||||
static void ALlowpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_LOWPASS_GAIN:
|
||||
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gain %f out of range", val);
|
||||
filter->Gain = val;
|
||||
break;
|
||||
|
||||
case AL_LOWPASS_GAINHF:
|
||||
if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gainhf %f out of range", val);
|
||||
filter->GainHF = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
static void ALlowpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALlowpass_setParamf(filter, context, param, vals[0]); }
|
||||
|
||||
static void ALlowpass_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); }
|
||||
static void ALlowpass_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); }
|
||||
static void ALlowpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_LOWPASS_GAIN:
|
||||
*val = filter->Gain;
|
||||
break;
|
||||
|
||||
case AL_LOWPASS_GAINHF:
|
||||
*val = filter->GainHF;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
static void ALlowpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALlowpass_getParamf(filter, context, param, vals); }
|
||||
|
||||
DEFINE_ALFILTER_VTABLE(ALlowpass);
|
||||
|
||||
|
||||
static void ALhighpass_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); }
|
||||
static void ALhighpass_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); }
|
||||
static void ALhighpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_HIGHPASS_GAIN:
|
||||
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gain out of range");
|
||||
filter->Gain = val;
|
||||
break;
|
||||
|
||||
case AL_HIGHPASS_GAINLF:
|
||||
if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gainlf out of range");
|
||||
filter->GainLF = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
static void ALhighpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALhighpass_setParamf(filter, context, param, vals[0]); }
|
||||
|
||||
static void ALhighpass_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); }
|
||||
static void ALhighpass_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); }
|
||||
static void ALhighpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_HIGHPASS_GAIN:
|
||||
*val = filter->Gain;
|
||||
break;
|
||||
|
||||
case AL_HIGHPASS_GAINLF:
|
||||
*val = filter->GainLF;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
static void ALhighpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALhighpass_getParamf(filter, context, param, vals); }
|
||||
|
||||
DEFINE_ALFILTER_VTABLE(ALhighpass);
|
||||
|
||||
|
||||
static void ALbandpass_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); }
|
||||
static void ALbandpass_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); }
|
||||
static void ALbandpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_BANDPASS_GAIN:
|
||||
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gain out of range");
|
||||
filter->Gain = val;
|
||||
break;
|
||||
|
||||
case AL_BANDPASS_GAINHF:
|
||||
if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainhf out of range");
|
||||
filter->GainHF = val;
|
||||
break;
|
||||
|
||||
case AL_BANDPASS_GAINLF:
|
||||
if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainlf out of range");
|
||||
filter->GainLF = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
static void ALbandpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
|
||||
{ ALbandpass_setParamf(filter, context, param, vals[0]); }
|
||||
|
||||
static void ALbandpass_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); }
|
||||
static void ALbandpass_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); }
|
||||
static void ALbandpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_BANDPASS_GAIN:
|
||||
*val = filter->Gain;
|
||||
break;
|
||||
|
||||
case AL_BANDPASS_GAINHF:
|
||||
*val = filter->GainHF;
|
||||
break;
|
||||
|
||||
case AL_BANDPASS_GAINLF:
|
||||
*val = filter->GainLF;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param);
|
||||
}
|
||||
}
|
||||
static void ALbandpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
|
||||
{ ALbandpass_getParamf(filter, context, param, vals); }
|
||||
|
||||
DEFINE_ALFILTER_VTABLE(ALbandpass);
|
||||
|
||||
|
||||
static void ALnullfilter_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
static void ALnullfilter_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
static void ALnullfilter_setParamf(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALfloat UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
static void ALnullfilter_setParamfv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALfloat *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
|
||||
static void ALnullfilter_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
static void ALnullfilter_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
static void ALnullfilter_getParamf(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALfloat *UNUSED(val))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
static void ALnullfilter_getParamfv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALfloat *UNUSED(vals))
|
||||
{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
|
||||
|
||||
DEFINE_ALFILTER_VTABLE(ALnullfilter);
|
||||
|
||||
|
||||
static ALfilter *AllocFilter(ALCcontext *context)
|
||||
{
|
||||
ALCdevice *device = context->Device;
|
||||
FilterSubList *sublist, *subend;
|
||||
ALfilter *filter = NULL;
|
||||
ALsizei lidx = 0;
|
||||
ALsizei slidx;
|
||||
|
||||
almtx_lock(&device->FilterLock);
|
||||
sublist = VECTOR_BEGIN(device->FilterList);
|
||||
subend = VECTOR_END(device->FilterList);
|
||||
for(;sublist != subend;++sublist)
|
||||
{
|
||||
if(sublist->FreeMask)
|
||||
{
|
||||
slidx = CTZ64(sublist->FreeMask);
|
||||
filter = sublist->Filters + slidx;
|
||||
break;
|
||||
}
|
||||
++lidx;
|
||||
}
|
||||
if(UNLIKELY(!filter))
|
||||
{
|
||||
const FilterSubList empty_sublist = { 0, NULL };
|
||||
/* Don't allocate so many list entries that the 32-bit ID could
|
||||
* overflow...
|
||||
*/
|
||||
if(UNLIKELY(VECTOR_SIZE(device->FilterList) >= 1<<25))
|
||||
{
|
||||
almtx_unlock(&device->FilterLock);
|
||||
alSetError(context, AL_OUT_OF_MEMORY, "Too many filters allocated");
|
||||
return NULL;
|
||||
}
|
||||
lidx = (ALsizei)VECTOR_SIZE(device->FilterList);
|
||||
VECTOR_PUSH_BACK(device->FilterList, empty_sublist);
|
||||
sublist = &VECTOR_BACK(device->FilterList);
|
||||
sublist->FreeMask = ~U64(0);
|
||||
sublist->Filters = al_calloc(16, sizeof(ALfilter)*64);
|
||||
if(UNLIKELY(!sublist->Filters))
|
||||
{
|
||||
VECTOR_POP_BACK(device->FilterList);
|
||||
almtx_unlock(&device->FilterLock);
|
||||
alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate filter batch");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
slidx = 0;
|
||||
filter = sublist->Filters + slidx;
|
||||
}
|
||||
|
||||
memset(filter, 0, sizeof(*filter));
|
||||
InitFilterParams(filter, AL_FILTER_NULL);
|
||||
|
||||
/* Add 1 to avoid filter ID 0. */
|
||||
filter->id = ((lidx<<6) | slidx) + 1;
|
||||
|
||||
sublist->FreeMask &= ~(U64(1)<<slidx);
|
||||
almtx_unlock(&device->FilterLock);
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
static void FreeFilter(ALCdevice *device, ALfilter *filter)
|
||||
{
|
||||
ALuint id = filter->id - 1;
|
||||
ALsizei lidx = id >> 6;
|
||||
ALsizei slidx = id & 0x3f;
|
||||
|
||||
memset(filter, 0, sizeof(*filter));
|
||||
|
||||
VECTOR_ELEM(device->FilterList, lidx).FreeMask |= U64(1) << slidx;
|
||||
}
|
||||
|
||||
void ReleaseALFilters(ALCdevice *device)
|
||||
{
|
||||
FilterSubList *sublist = VECTOR_BEGIN(device->FilterList);
|
||||
FilterSubList *subend = VECTOR_END(device->FilterList);
|
||||
size_t leftover = 0;
|
||||
for(;sublist != subend;++sublist)
|
||||
{
|
||||
ALuint64 usemask = ~sublist->FreeMask;
|
||||
while(usemask)
|
||||
{
|
||||
ALsizei idx = CTZ64(usemask);
|
||||
ALfilter *filter = sublist->Filters + idx;
|
||||
|
||||
memset(filter, 0, sizeof(*filter));
|
||||
++leftover;
|
||||
|
||||
usemask &= ~(U64(1) << idx);
|
||||
}
|
||||
sublist->FreeMask = ~usemask;
|
||||
}
|
||||
if(leftover > 0)
|
||||
WARN("(%p) Deleted "SZFMT" Filter%s\n", device, leftover, (leftover==1)?"":"s");
|
||||
}
|
||||
|
||||
|
||||
static void InitFilterParams(ALfilter *filter, ALenum type)
|
||||
{
|
||||
if(type == AL_FILTER_LOWPASS)
|
||||
{
|
||||
filter->Gain = AL_LOWPASS_DEFAULT_GAIN;
|
||||
filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF;
|
||||
filter->HFReference = LOWPASSFREQREF;
|
||||
filter->GainLF = 1.0f;
|
||||
filter->LFReference = HIGHPASSFREQREF;
|
||||
filter->vtab = &ALlowpass_vtable;
|
||||
}
|
||||
else if(type == AL_FILTER_HIGHPASS)
|
||||
{
|
||||
filter->Gain = AL_HIGHPASS_DEFAULT_GAIN;
|
||||
filter->GainHF = 1.0f;
|
||||
filter->HFReference = LOWPASSFREQREF;
|
||||
filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF;
|
||||
filter->LFReference = HIGHPASSFREQREF;
|
||||
filter->vtab = &ALhighpass_vtable;
|
||||
}
|
||||
else if(type == AL_FILTER_BANDPASS)
|
||||
{
|
||||
filter->Gain = AL_BANDPASS_DEFAULT_GAIN;
|
||||
filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF;
|
||||
filter->HFReference = LOWPASSFREQREF;
|
||||
filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF;
|
||||
filter->LFReference = HIGHPASSFREQREF;
|
||||
filter->vtab = &ALbandpass_vtable;
|
||||
}
|
||||
else
|
||||
{
|
||||
filter->Gain = 1.0f;
|
||||
filter->GainHF = 1.0f;
|
||||
filter->HFReference = LOWPASSFREQREF;
|
||||
filter->GainLF = 1.0f;
|
||||
filter->LFReference = HIGHPASSFREQREF;
|
||||
filter->vtab = &ALnullfilter_vtable;
|
||||
}
|
||||
filter->type = type;
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2000 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 "alMain.h"
|
||||
#include "alu.h"
|
||||
#include "alError.h"
|
||||
#include "alListener.h"
|
||||
#include "alSource.h"
|
||||
|
||||
#define DO_UPDATEPROPS() do { \
|
||||
if(!ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire)) \
|
||||
UpdateListenerProps(context); \
|
||||
else \
|
||||
ATOMIC_FLAG_CLEAR(&listener->PropsClean, almemory_order_release); \
|
||||
} while(0)
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alListenerf(ALenum param, ALfloat value)
|
||||
{
|
||||
ALlistener *listener;
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
listener = context->Listener;
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(param)
|
||||
{
|
||||
case AL_GAIN:
|
||||
if(!(value >= 0.0f && isfinite(value)))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Listener gain out of range");
|
||||
listener->Gain = value;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
case AL_METERS_PER_UNIT:
|
||||
if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Listener meters per unit out of range");
|
||||
context->MetersPerUnit = value;
|
||||
if(!ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
|
||||
UpdateContextProps(context);
|
||||
else
|
||||
ATOMIC_FLAG_CLEAR(&context->PropsClean, almemory_order_release);
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener float property");
|
||||
}
|
||||
|
||||
done:
|
||||
almtx_unlock(&context->PropLock);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
|
||||
{
|
||||
ALlistener *listener;
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
listener = context->Listener;
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
if(!(isfinite(value1) && isfinite(value2) && isfinite(value3)))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Listener position out of range");
|
||||
listener->Position[0] = value1;
|
||||
listener->Position[1] = value2;
|
||||
listener->Position[2] = value3;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
case AL_VELOCITY:
|
||||
if(!(isfinite(value1) && isfinite(value2) && isfinite(value3)))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Listener velocity out of range");
|
||||
listener->Velocity[0] = value1;
|
||||
listener->Velocity[1] = value2;
|
||||
listener->Velocity[2] = value3;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener 3-float property");
|
||||
}
|
||||
|
||||
done:
|
||||
almtx_unlock(&context->PropLock);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values)
|
||||
{
|
||||
ALlistener *listener;
|
||||
ALCcontext *context;
|
||||
|
||||
if(values)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_GAIN:
|
||||
case AL_METERS_PER_UNIT:
|
||||
alListenerf(param, values[0]);
|
||||
return;
|
||||
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alListener3f(param, values[0], values[1], values[2]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
listener = context->Listener;
|
||||
almtx_lock(&context->PropLock);
|
||||
if(!values) SETERR_GOTO(context, AL_INVALID_VALUE, done, "NULL pointer");
|
||||
switch(param)
|
||||
{
|
||||
case AL_ORIENTATION:
|
||||
if(!(isfinite(values[0]) && isfinite(values[1]) && isfinite(values[2]) &&
|
||||
isfinite(values[3]) && isfinite(values[4]) && isfinite(values[5])))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Listener orientation out of range");
|
||||
/* AT then UP */
|
||||
listener->Forward[0] = values[0];
|
||||
listener->Forward[1] = values[1];
|
||||
listener->Forward[2] = values[2];
|
||||
listener->Up[0] = values[3];
|
||||
listener->Up[1] = values[4];
|
||||
listener->Up[2] = values[5];
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener float-vector property");
|
||||
}
|
||||
|
||||
done:
|
||||
almtx_unlock(&context->PropLock);
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alListeneri(ALenum param, ALint UNUSED(value))
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener integer property");
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alListener3f(param, (ALfloat)value1, (ALfloat)value2, (ALfloat)value3);
|
||||
return;
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener 3-integer property");
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
if(values)
|
||||
{
|
||||
ALfloat fvals[6];
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alListener3f(param, (ALfloat)values[0], (ALfloat)values[1], (ALfloat)values[2]);
|
||||
return;
|
||||
|
||||
case AL_ORIENTATION:
|
||||
fvals[0] = (ALfloat)values[0];
|
||||
fvals[1] = (ALfloat)values[1];
|
||||
fvals[2] = (ALfloat)values[2];
|
||||
fvals[3] = (ALfloat)values[3];
|
||||
fvals[4] = (ALfloat)values[4];
|
||||
fvals[5] = (ALfloat)values[5];
|
||||
alListenerfv(param, fvals);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
if(!values)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener integer-vector property");
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
if(!value)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_GAIN:
|
||||
*value = context->Listener->Gain;
|
||||
break;
|
||||
|
||||
case AL_METERS_PER_UNIT:
|
||||
*value = context->MetersPerUnit;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener float property");
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
if(!value1 || !value2 || !value3)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
*value1 = context->Listener->Position[0];
|
||||
*value2 = context->Listener->Position[1];
|
||||
*value3 = context->Listener->Position[2];
|
||||
break;
|
||||
|
||||
case AL_VELOCITY:
|
||||
*value1 = context->Listener->Velocity[0];
|
||||
*value2 = context->Listener->Velocity[1];
|
||||
*value3 = context->Listener->Velocity[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener 3-float property");
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_GAIN:
|
||||
case AL_METERS_PER_UNIT:
|
||||
alGetListenerf(param, values);
|
||||
return;
|
||||
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alGetListener3f(param, values+0, values+1, values+2);
|
||||
return;
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
if(!values)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_ORIENTATION:
|
||||
// AT then UP
|
||||
values[0] = context->Listener->Forward[0];
|
||||
values[1] = context->Listener->Forward[1];
|
||||
values[2] = context->Listener->Forward[2];
|
||||
values[3] = context->Listener->Up[0];
|
||||
values[4] = context->Listener->Up[1];
|
||||
values[5] = context->Listener->Up[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener float-vector property");
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetListeneri(ALenum param, ALint *value)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
if(!value)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener integer property");
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
if(!value1 || !value2 || !value3)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
*value1 = (ALint)context->Listener->Position[0];
|
||||
*value2 = (ALint)context->Listener->Position[1];
|
||||
*value3 = (ALint)context->Listener->Position[2];
|
||||
break;
|
||||
|
||||
case AL_VELOCITY:
|
||||
*value1 = (ALint)context->Listener->Velocity[0];
|
||||
*value2 = (ALint)context->Listener->Velocity[1];
|
||||
*value3 = (ALint)context->Listener->Velocity[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener 3-integer property");
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint* values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alGetListener3i(param, values+0, values+1, values+2);
|
||||
return;
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
if(!values)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_ORIENTATION:
|
||||
// AT then UP
|
||||
values[0] = (ALint)context->Listener->Forward[0];
|
||||
values[1] = (ALint)context->Listener->Forward[1];
|
||||
values[2] = (ALint)context->Listener->Forward[2];
|
||||
values[3] = (ALint)context->Listener->Up[0];
|
||||
values[4] = (ALint)context->Listener->Up[1];
|
||||
values[5] = (ALint)context->Listener->Up[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_ENUM, "Invalid listener integer-vector property");
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
void UpdateListenerProps(ALCcontext *context)
|
||||
{
|
||||
ALlistener *listener = context->Listener;
|
||||
struct ALlistenerProps *props;
|
||||
|
||||
/* Get an unused proprty container, or allocate a new one as needed. */
|
||||
props = ATOMIC_LOAD(&context->FreeListenerProps, almemory_order_acquire);
|
||||
if(!props)
|
||||
props = al_calloc(16, sizeof(*props));
|
||||
else
|
||||
{
|
||||
struct ALlistenerProps *next;
|
||||
do {
|
||||
next = ATOMIC_LOAD(&props->next, almemory_order_relaxed);
|
||||
} while(ATOMIC_COMPARE_EXCHANGE_PTR_WEAK(&context->FreeListenerProps, &props, next,
|
||||
almemory_order_seq_cst, almemory_order_acquire) == 0);
|
||||
}
|
||||
|
||||
/* Copy in current property values. */
|
||||
props->Position[0] = listener->Position[0];
|
||||
props->Position[1] = listener->Position[1];
|
||||
props->Position[2] = listener->Position[2];
|
||||
|
||||
props->Velocity[0] = listener->Velocity[0];
|
||||
props->Velocity[1] = listener->Velocity[1];
|
||||
props->Velocity[2] = listener->Velocity[2];
|
||||
|
||||
props->Forward[0] = listener->Forward[0];
|
||||
props->Forward[1] = listener->Forward[1];
|
||||
props->Forward[2] = listener->Forward[2];
|
||||
props->Up[0] = listener->Up[0];
|
||||
props->Up[1] = listener->Up[1];
|
||||
props->Up[2] = listener->Up[2];
|
||||
|
||||
props->Gain = listener->Gain;
|
||||
|
||||
/* Set the new container for updating internal parameters. */
|
||||
props = ATOMIC_EXCHANGE_PTR(&listener->Update, props, almemory_order_acq_rel);
|
||||
if(props)
|
||||
{
|
||||
/* If there was an unused update container, put it back in the
|
||||
* freelist.
|
||||
*/
|
||||
ATOMIC_REPLACE_HEAD(struct ALlistenerProps*, &context->FreeListenerProps, props);
|
||||
}
|
||||
}
|
||||
+3705
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,900 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2000 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 "version.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "alMain.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/al.h"
|
||||
#include "AL/alext.h"
|
||||
#include "alError.h"
|
||||
#include "alListener.h"
|
||||
#include "alSource.h"
|
||||
#include "alAuxEffectSlot.h"
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
|
||||
static const ALchar alVendor[] = "OpenAL Community";
|
||||
static const ALchar alVersion[] = "1.1 ALSOFT "ALSOFT_VERSION;
|
||||
static const ALchar alRenderer[] = "OpenAL Soft";
|
||||
|
||||
// Error Messages
|
||||
static const ALchar alNoError[] = "No Error";
|
||||
static const ALchar alErrInvalidName[] = "Invalid Name";
|
||||
static const ALchar alErrInvalidEnum[] = "Invalid Enum";
|
||||
static const ALchar alErrInvalidValue[] = "Invalid Value";
|
||||
static const ALchar alErrInvalidOp[] = "Invalid Operation";
|
||||
static const ALchar alErrOutOfMemory[] = "Out of Memory";
|
||||
|
||||
/* Resampler strings */
|
||||
static const ALchar alPointResampler[] = "Nearest";
|
||||
static const ALchar alLinearResampler[] = "Linear";
|
||||
static const ALchar alCubicResampler[] = "Cubic";
|
||||
static const ALchar alBSinc12Resampler[] = "11th order Sinc";
|
||||
static const ALchar alBSinc24Resampler[] = "23rd order Sinc";
|
||||
|
||||
/* WARNING: Non-standard export! Not part of any extension, or exposed in the
|
||||
* alcFunctions list.
|
||||
*/
|
||||
AL_API const ALchar* AL_APIENTRY alsoft_get_version(void)
|
||||
{
|
||||
const char *spoof = getenv("ALSOFT_SPOOF_VERSION");
|
||||
if(spoof && spoof[0] != '\0') return spoof;
|
||||
return ALSOFT_VERSION;
|
||||
}
|
||||
|
||||
#define DO_UPDATEPROPS() do { \
|
||||
if(!ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire)) \
|
||||
UpdateContextProps(context); \
|
||||
else \
|
||||
ATOMIC_FLAG_CLEAR(&context->PropsClean, almemory_order_release); \
|
||||
} while(0)
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alEnable(ALenum capability)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(capability)
|
||||
{
|
||||
case AL_SOURCE_DISTANCE_MODEL:
|
||||
context->SourceDistanceModel = AL_TRUE;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability);
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDisable(ALenum capability)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(capability)
|
||||
{
|
||||
case AL_SOURCE_DISTANCE_MODEL:
|
||||
context->SourceDistanceModel = AL_FALSE;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability);
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALboolean value=AL_FALSE;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return AL_FALSE;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(capability)
|
||||
{
|
||||
case AL_SOURCE_DISTANCE_MODEL:
|
||||
value = context->SourceDistanceModel;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability);
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALboolean value=AL_FALSE;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return AL_FALSE;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
if(context->DopplerFactor != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
if(context->DopplerVelocity != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
if(context->DistanceModel == AL_INVERSE_DISTANCE_CLAMPED)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
if(context->SpeedOfSound != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
if(GAIN_MIX_MAX/context->GainBoost != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
/* Always non-0. */
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
value = ResamplerDefault ? AL_TRUE : AL_FALSE;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid boolean property 0x%04x", pname);
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALdouble value = 0.0;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return 0.0;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = (ALdouble)context->DopplerFactor;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = (ALdouble)context->DopplerVelocity;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = (ALdouble)context->DistanceModel;
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = (ALdouble)context->SpeedOfSound;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
|
||||
value = (ALdouble)AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = (ALdouble)GAIN_MIX_MAX/context->GainBoost;
|
||||
break;
|
||||
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
value = (ALdouble)(ResamplerMax + 1);
|
||||
break;
|
||||
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
value = (ALdouble)ResamplerDefault;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid double property 0x%04x", pname);
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALfloat value = 0.0f;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return 0.0f;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = context->DopplerFactor;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = context->DopplerVelocity;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = (ALfloat)context->DistanceModel;
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = context->SpeedOfSound;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
|
||||
value = (ALfloat)AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = GAIN_MIX_MAX/context->GainBoost;
|
||||
break;
|
||||
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
value = (ALfloat)(ResamplerMax + 1);
|
||||
break;
|
||||
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
value = (ALfloat)ResamplerDefault;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid float property 0x%04x", pname);
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
AL_API ALint AL_APIENTRY alGetInteger(ALenum pname)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALint value = 0;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return 0;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = (ALint)context->DopplerFactor;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = (ALint)context->DopplerVelocity;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = (ALint)context->DistanceModel;
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = (ALint)context->SpeedOfSound;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
|
||||
value = (ALint)AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = (ALint)(GAIN_MIX_MAX/context->GainBoost);
|
||||
break;
|
||||
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
value = ResamplerMax + 1;
|
||||
break;
|
||||
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
value = ResamplerDefault;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname);
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname)
|
||||
{
|
||||
ALCcontext *context;
|
||||
ALint64SOFT value = 0;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return 0;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = (ALint64SOFT)context->DopplerFactor;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = (ALint64SOFT)context->DopplerVelocity;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = (ALint64SOFT)context->DistanceModel;
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = (ALint64SOFT)context->SpeedOfSound;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
|
||||
value = (ALint64SOFT)AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = (ALint64SOFT)(GAIN_MIX_MAX/context->GainBoost);
|
||||
break;
|
||||
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
value = (ALint64SOFT)(ResamplerMax + 1);
|
||||
break;
|
||||
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
value = (ALint64SOFT)ResamplerDefault;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid integer64 property 0x%04x", pname);
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname)
|
||||
{
|
||||
ALCcontext *context;
|
||||
void *value = NULL;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return NULL;
|
||||
|
||||
almtx_lock(&context->PropLock);
|
||||
switch(pname)
|
||||
{
|
||||
case AL_EVENT_CALLBACK_FUNCTION_SOFT:
|
||||
value = context->EventCb;
|
||||
break;
|
||||
|
||||
case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
|
||||
value = context->EventParam;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid pointer property 0x%04x", pname);
|
||||
}
|
||||
almtx_unlock(&context->PropLock);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetBooleanv(ALenum pname, ALboolean *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetBoolean(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!values)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
switch(pname)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid boolean-vector property 0x%04x", pname);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetDoublev(ALenum pname, ALdouble *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetDouble(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!values)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
switch(pname)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid double-vector property 0x%04x", pname);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetFloatv(ALenum pname, ALfloat *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetFloat(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!values)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
switch(pname)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid float-vector property 0x%04x", pname);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alGetIntegerv(ALenum pname, ALint *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetInteger(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!values)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
switch(pname)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid integer-vector property 0x%04x", pname);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetInteger64SOFT(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!values)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
switch(pname)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid integer64-vector property 0x%04x", pname);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_EVENT_CALLBACK_FUNCTION_SOFT:
|
||||
case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
|
||||
values[0] = alGetPointerSOFT(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!values)
|
||||
alSetError(context, AL_INVALID_VALUE, "NULL pointer");
|
||||
switch(pname)
|
||||
{
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid pointer-vector property 0x%04x", pname);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname)
|
||||
{
|
||||
const ALchar *value = NULL;
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return NULL;
|
||||
|
||||
switch(pname)
|
||||
{
|
||||
case AL_VENDOR:
|
||||
value = alVendor;
|
||||
break;
|
||||
|
||||
case AL_VERSION:
|
||||
value = alVersion;
|
||||
break;
|
||||
|
||||
case AL_RENDERER:
|
||||
value = alRenderer;
|
||||
break;
|
||||
|
||||
case AL_EXTENSIONS:
|
||||
value = context->ExtensionList;
|
||||
break;
|
||||
|
||||
case AL_NO_ERROR:
|
||||
value = alNoError;
|
||||
break;
|
||||
|
||||
case AL_INVALID_NAME:
|
||||
value = alErrInvalidName;
|
||||
break;
|
||||
|
||||
case AL_INVALID_ENUM:
|
||||
value = alErrInvalidEnum;
|
||||
break;
|
||||
|
||||
case AL_INVALID_VALUE:
|
||||
value = alErrInvalidValue;
|
||||
break;
|
||||
|
||||
case AL_INVALID_OPERATION:
|
||||
value = alErrInvalidOp;
|
||||
break;
|
||||
|
||||
case AL_OUT_OF_MEMORY:
|
||||
value = alErrOutOfMemory;
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid string property 0x%04x", pname);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDopplerFactor(ALfloat value)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!(value >= 0.0f && isfinite(value)))
|
||||
alSetError(context, AL_INVALID_VALUE, "Doppler factor %f out of range", value);
|
||||
else
|
||||
{
|
||||
almtx_lock(&context->PropLock);
|
||||
context->DopplerFactor = value;
|
||||
DO_UPDATEPROPS();
|
||||
almtx_unlock(&context->PropLock);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDopplerVelocity(ALfloat value)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if((ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed)&EventType_Deprecated))
|
||||
{
|
||||
static const ALCchar msg[] =
|
||||
"alDopplerVelocity is deprecated in AL1.1, use alSpeedOfSound";
|
||||
const ALsizei msglen = (ALsizei)strlen(msg);
|
||||
ALbitfieldSOFT enabledevts;
|
||||
almtx_lock(&context->EventCbLock);
|
||||
enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed);
|
||||
if((enabledevts&EventType_Deprecated) && context->EventCb)
|
||||
(*context->EventCb)(AL_EVENT_TYPE_DEPRECATED_SOFT, 0, 0, msglen, msg,
|
||||
context->EventParam);
|
||||
almtx_unlock(&context->EventCbLock);
|
||||
}
|
||||
|
||||
if(!(value >= 0.0f && isfinite(value)))
|
||||
alSetError(context, AL_INVALID_VALUE, "Doppler velocity %f out of range", value);
|
||||
else
|
||||
{
|
||||
almtx_lock(&context->PropLock);
|
||||
context->DopplerVelocity = value;
|
||||
DO_UPDATEPROPS();
|
||||
almtx_unlock(&context->PropLock);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alSpeedOfSound(ALfloat value)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!(value > 0.0f && isfinite(value)))
|
||||
alSetError(context, AL_INVALID_VALUE, "Speed of sound %f out of range", value);
|
||||
else
|
||||
{
|
||||
almtx_lock(&context->PropLock);
|
||||
context->SpeedOfSound = value;
|
||||
DO_UPDATEPROPS();
|
||||
almtx_unlock(&context->PropLock);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDistanceModel(ALenum value)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
if(!(value == AL_INVERSE_DISTANCE || value == AL_INVERSE_DISTANCE_CLAMPED ||
|
||||
value == AL_LINEAR_DISTANCE || value == AL_LINEAR_DISTANCE_CLAMPED ||
|
||||
value == AL_EXPONENT_DISTANCE || value == AL_EXPONENT_DISTANCE_CLAMPED ||
|
||||
value == AL_NONE))
|
||||
alSetError(context, AL_INVALID_VALUE, "Distance model 0x%04x out of range", value);
|
||||
else
|
||||
{
|
||||
almtx_lock(&context->PropLock);
|
||||
context->DistanceModel = value;
|
||||
if(!context->SourceDistanceModel)
|
||||
DO_UPDATEPROPS();
|
||||
almtx_unlock(&context->PropLock);
|
||||
}
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alDeferUpdatesSOFT(void)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
ALCcontext_DeferUpdates(context);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void)
|
||||
{
|
||||
ALCcontext *context;
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return;
|
||||
|
||||
ALCcontext_ProcessUpdates(context);
|
||||
|
||||
ALCcontext_DecRef(context);
|
||||
}
|
||||
|
||||
|
||||
AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index)
|
||||
{
|
||||
const char *ResamplerNames[] = {
|
||||
alPointResampler, alLinearResampler,
|
||||
alCubicResampler, alBSinc12Resampler,
|
||||
alBSinc24Resampler,
|
||||
};
|
||||
const ALchar *value = NULL;
|
||||
ALCcontext *context;
|
||||
|
||||
static_assert(COUNTOF(ResamplerNames) == ResamplerMax+1, "Incorrect ResamplerNames list");
|
||||
|
||||
context = GetContextRef();
|
||||
if(!context) return NULL;
|
||||
|
||||
switch(pname)
|
||||
{
|
||||
case AL_RESAMPLER_NAME_SOFT:
|
||||
if(index < 0 || (size_t)index >= COUNTOF(ResamplerNames))
|
||||
SETERR_GOTO(context, AL_INVALID_VALUE, done, "Resampler name index %d out of range",
|
||||
index);
|
||||
value = ResamplerNames[index];
|
||||
break;
|
||||
|
||||
default:
|
||||
alSetError(context, AL_INVALID_VALUE, "Invalid string indexed property");
|
||||
}
|
||||
|
||||
done:
|
||||
ALCcontext_DecRef(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
void UpdateContextProps(ALCcontext *context)
|
||||
{
|
||||
struct ALcontextProps *props;
|
||||
|
||||
/* Get an unused proprty container, or allocate a new one as needed. */
|
||||
props = ATOMIC_LOAD(&context->FreeContextProps, almemory_order_acquire);
|
||||
if(!props)
|
||||
props = al_calloc(16, sizeof(*props));
|
||||
else
|
||||
{
|
||||
struct ALcontextProps *next;
|
||||
do {
|
||||
next = ATOMIC_LOAD(&props->next, almemory_order_relaxed);
|
||||
} while(ATOMIC_COMPARE_EXCHANGE_PTR_WEAK(&context->FreeContextProps, &props, next,
|
||||
almemory_order_seq_cst, almemory_order_acquire) == 0);
|
||||
}
|
||||
|
||||
/* Copy in current property values. */
|
||||
props->MetersPerUnit = context->MetersPerUnit;
|
||||
|
||||
props->DopplerFactor = context->DopplerFactor;
|
||||
props->DopplerVelocity = context->DopplerVelocity;
|
||||
props->SpeedOfSound = context->SpeedOfSound;
|
||||
|
||||
props->SourceDistanceModel = context->SourceDistanceModel;
|
||||
props->DistanceModel = context->DistanceModel;
|
||||
|
||||
/* Set the new container for updating internal parameters. */
|
||||
props = ATOMIC_EXCHANGE_PTR(&context->Update, props, almemory_order_acq_rel);
|
||||
if(props)
|
||||
{
|
||||
/* If there was an unused update container, put it back in the
|
||||
* freelist.
|
||||
*/
|
||||
ATOMIC_REPLACE_HEAD(struct ALcontextProps*, &context->FreeContextProps, props);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user