From bfb1c2ec84131ad23e86a09a23609fd0318908fd Mon Sep 17 00:00:00 2001 From: Marc Salem Date: Fri, 16 Dec 2011 18:14:00 -0800 Subject: [PATCH] Android .so for OpenAL --- AndroidManifest.xml | 15 + build.properties | 17 + build.xml | 79 + default.properties | 11 + jni/Android.mk | 67 + jni/Application.mk | 1 + jni/OpenAL/Alc/ALc.c | 2343 +++++++++++++++++ jni/OpenAL/Alc/ALu.c | 1092 ++++++++ jni/OpenAL/Alc/alcConfig.c | 338 +++ jni/OpenAL/Alc/alcEcho.c | 203 ++ jni/OpenAL/Alc/alcModulator.c | 229 ++ jni/OpenAL/Alc/alcReverb.c | 1371 ++++++++++ jni/OpenAL/Alc/alcRing.c | 131 + jni/OpenAL/Alc/alcThread.c | 128 + jni/OpenAL/Alc/alsa.c | 1048 ++++++++ jni/OpenAL/Alc/apportable_openal_funcs.h | 11 + jni/OpenAL/Alc/audiotrack.c | 320 +++ jni/OpenAL/Alc/bs2b.c | 209 ++ jni/OpenAL/Alc/dsound.c | 612 +++++ jni/OpenAL/Alc/mixer.c | 813 ++++++ jni/OpenAL/Alc/null.c | 182 ++ jni/OpenAL/Alc/opensles.c | 411 +++ jni/OpenAL/Alc/oss.c | 521 ++++ jni/OpenAL/Alc/panning.c | 364 +++ jni/OpenAL/Alc/portaudio.c | 442 ++++ jni/OpenAL/Alc/pulseaudio.c | 1358 ++++++++++ jni/OpenAL/Alc/solaris.c | 304 +++ jni/OpenAL/Alc/wave.c | 355 +++ jni/OpenAL/Alc/winmm.c | 784 ++++++ jni/OpenAL/Makefile | 5 + jni/OpenAL/OpenAL32/Include/alAuxEffectSlot.h | 63 + jni/OpenAL/OpenAL32/Include/alBuffer.h | 98 + jni/OpenAL/OpenAL32/Include/alDatabuffer.h | 33 + jni/OpenAL/OpenAL32/Include/alEffect.h | 83 + jni/OpenAL/OpenAL32/Include/alError.h | 17 + jni/OpenAL/OpenAL32/Include/alFilter.h | 139 + jni/OpenAL/OpenAL32/Include/alListener.h | 24 + jni/OpenAL/OpenAL32/Include/alMain.h | 500 ++++ jni/OpenAL/OpenAL32/Include/alSource.h | 121 + jni/OpenAL/OpenAL32/Include/alState.h | 14 + jni/OpenAL/OpenAL32/Include/alThunk.h | 42 + jni/OpenAL/OpenAL32/Include/alu.h | 135 + jni/OpenAL/OpenAL32/Include/bs2b.h | 111 + jni/OpenAL/OpenAL32/alAuxEffectSlot.c | 528 ++++ jni/OpenAL/OpenAL32/alBuffer.c | 1898 +++++++++++++ jni/OpenAL/OpenAL32/alDatabuffer.c | 648 +++++ jni/OpenAL/OpenAL32/alEffect.c | 1377 ++++++++++ jni/OpenAL/OpenAL32/alError.c | 47 + jni/OpenAL/OpenAL32/alExtension.c | 331 +++ jni/OpenAL/OpenAL32/alFilter.c | 432 +++ jni/OpenAL/OpenAL32/alListener.c | 489 ++++ jni/OpenAL/OpenAL32/alSource.c | 2091 +++++++++++++++ jni/OpenAL/OpenAL32/alState.c | 661 +++++ jni/OpenAL/OpenAL32/alThunk.c | 111 + jni/OpenAL/al.h | 724 +++++ jni/OpenAL/alc.h | 277 ++ jni/OpenAL/build.mk | 54 + jni/OpenAL/include/AL/al.h | 814 ++++++ jni/OpenAL/include/AL/alc.h | 277 ++ jni/OpenAL/include/AL/alext.h | 165 ++ jni/OpenAL/include/AL/efx-creative.h | 3 + jni/OpenAL/include/AL/efx.h | 758 ++++++ jni/OpenAL/include/config.h | 110 + jni/OpenAL/oalStaticBufferExtension.h | 0 libs/armeabi/libopenal.so | Bin 0 -> 389156 bytes local.properties | 10 + proguard.cfg | 40 + res/drawable-hdpi/icon.png | Bin 0 -> 4147 bytes res/drawable-ldpi/icon.png | Bin 0 -> 1723 bytes res/drawable-mdpi/icon.png | Bin 0 -> 2574 bytes res/layout/main.xml | 13 + res/values/strings.xml | 4 + .../apportable/openal_soft/openal_soft.java | 15 + 73 files changed, 27021 insertions(+) create mode 100644 AndroidManifest.xml create mode 100644 build.properties create mode 100644 build.xml create mode 100644 default.properties create mode 100644 jni/Android.mk create mode 100644 jni/Application.mk create mode 100644 jni/OpenAL/Alc/ALc.c create mode 100644 jni/OpenAL/Alc/ALu.c create mode 100644 jni/OpenAL/Alc/alcConfig.c create mode 100644 jni/OpenAL/Alc/alcEcho.c create mode 100644 jni/OpenAL/Alc/alcModulator.c create mode 100644 jni/OpenAL/Alc/alcReverb.c create mode 100644 jni/OpenAL/Alc/alcRing.c create mode 100644 jni/OpenAL/Alc/alcThread.c create mode 100644 jni/OpenAL/Alc/alsa.c create mode 100644 jni/OpenAL/Alc/apportable_openal_funcs.h create mode 100755 jni/OpenAL/Alc/audiotrack.c create mode 100644 jni/OpenAL/Alc/bs2b.c create mode 100644 jni/OpenAL/Alc/dsound.c create mode 100644 jni/OpenAL/Alc/mixer.c create mode 100644 jni/OpenAL/Alc/null.c create mode 100644 jni/OpenAL/Alc/opensles.c create mode 100644 jni/OpenAL/Alc/oss.c create mode 100644 jni/OpenAL/Alc/panning.c create mode 100644 jni/OpenAL/Alc/portaudio.c create mode 100644 jni/OpenAL/Alc/pulseaudio.c create mode 100644 jni/OpenAL/Alc/solaris.c create mode 100644 jni/OpenAL/Alc/wave.c create mode 100644 jni/OpenAL/Alc/winmm.c create mode 100644 jni/OpenAL/Makefile create mode 100644 jni/OpenAL/OpenAL32/Include/alAuxEffectSlot.h create mode 100644 jni/OpenAL/OpenAL32/Include/alBuffer.h create mode 100644 jni/OpenAL/OpenAL32/Include/alDatabuffer.h create mode 100644 jni/OpenAL/OpenAL32/Include/alEffect.h create mode 100644 jni/OpenAL/OpenAL32/Include/alError.h create mode 100644 jni/OpenAL/OpenAL32/Include/alFilter.h create mode 100644 jni/OpenAL/OpenAL32/Include/alListener.h create mode 100644 jni/OpenAL/OpenAL32/Include/alMain.h create mode 100644 jni/OpenAL/OpenAL32/Include/alSource.h create mode 100644 jni/OpenAL/OpenAL32/Include/alState.h create mode 100644 jni/OpenAL/OpenAL32/Include/alThunk.h create mode 100644 jni/OpenAL/OpenAL32/Include/alu.h create mode 100644 jni/OpenAL/OpenAL32/Include/bs2b.h create mode 100644 jni/OpenAL/OpenAL32/alAuxEffectSlot.c create mode 100644 jni/OpenAL/OpenAL32/alBuffer.c create mode 100644 jni/OpenAL/OpenAL32/alDatabuffer.c create mode 100644 jni/OpenAL/OpenAL32/alEffect.c create mode 100644 jni/OpenAL/OpenAL32/alError.c create mode 100644 jni/OpenAL/OpenAL32/alExtension.c create mode 100644 jni/OpenAL/OpenAL32/alFilter.c create mode 100644 jni/OpenAL/OpenAL32/alListener.c create mode 100644 jni/OpenAL/OpenAL32/alSource.c create mode 100644 jni/OpenAL/OpenAL32/alState.c create mode 100644 jni/OpenAL/OpenAL32/alThunk.c create mode 100644 jni/OpenAL/al.h create mode 100644 jni/OpenAL/alc.h create mode 100644 jni/OpenAL/build.mk create mode 100644 jni/OpenAL/include/AL/al.h create mode 100644 jni/OpenAL/include/AL/alc.h create mode 100644 jni/OpenAL/include/AL/alext.h create mode 100644 jni/OpenAL/include/AL/efx-creative.h create mode 100644 jni/OpenAL/include/AL/efx.h create mode 100755 jni/OpenAL/include/config.h create mode 100644 jni/OpenAL/oalStaticBufferExtension.h create mode 100755 libs/armeabi/libopenal.so create mode 100644 local.properties create mode 100644 proguard.cfg create mode 100644 res/drawable-hdpi/icon.png create mode 100644 res/drawable-ldpi/icon.png create mode 100644 res/drawable-mdpi/icon.png create mode 100644 res/layout/main.xml create mode 100644 res/values/strings.xml create mode 100644 src/com/apportable/openal_soft/openal_soft.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..48ab82e --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/build.properties b/build.properties new file mode 100644 index 0000000..ee52d86 --- /dev/null +++ b/build.properties @@ -0,0 +1,17 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..3520983 --- /dev/null +++ b/build.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/default.properties b/default.properties new file mode 100644 index 0000000..e2e8061 --- /dev/null +++ b/default.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-8 diff --git a/jni/Android.mk b/jni/Android.mk new file mode 100644 index 0000000..60ad9e4 --- /dev/null +++ b/jni/Android.mk @@ -0,0 +1,67 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +OPENAL_DIR := OpenAL + +LOCAL_LDLIBS := -llog +LOCAL_MODULE := openal +LOCAL_ARM_MODE := arm +LOCAL_CFLAGS += -I$(OPENAL_DIR) \ + -I$(OPENAL_DIR)/include \ + -I$(OPENAL_DIR)/OpenAL32/Include \ + -DAL_BUILD_LIBRARY \ + -DAL_ALEXT_PROTOTYPES \ + -DANDROID \ + -fpic \ + -ffunction-sections \ + -funwind-tables \ + -fstack-protector \ + -fno-short-enums \ + -D__ARM_ARCH_5__ \ + -D__ANDROID__ \ + -march=armv5 \ + -msoft-float \ + + +# -DVERDE_USE_REAL_FILE_IO \ + +# FIXME +LOCAL_CFLAGS += -I/Developer/AndroidNDK/platforms/android-8/arch-arm/usr/include + +# Default to Fixed-point math +LOCAL_CFLAGS += -DOPENAL_FIXED_POINT -DOPENAL_FIXED_POINT_SHIFT=16 + +LOCAL_SRC_FILES := $(OPENAL_DIR)/OpenAL32/alAuxEffectSlot.c \ + $(OPENAL_DIR)/OpenAL32/alBuffer.c \ + $(OPENAL_DIR)/OpenAL32/alDatabuffer.c \ + $(OPENAL_DIR)/OpenAL32/alEffect.c \ + $(OPENAL_DIR)/OpenAL32/alError.c \ + $(OPENAL_DIR)/OpenAL32/alExtension.c \ + $(OPENAL_DIR)/OpenAL32/alFilter.c \ + $(OPENAL_DIR)/OpenAL32/alListener.c \ + $(OPENAL_DIR)/OpenAL32/alSource.c \ + $(OPENAL_DIR)/OpenAL32/alState.c \ + $(OPENAL_DIR)/OpenAL32/alThunk.c \ + $(OPENAL_DIR)/Alc/ALc.c \ + $(OPENAL_DIR)/Alc/alcConfig.c \ + $(OPENAL_DIR)/Alc/alcEcho.c \ + $(OPENAL_DIR)/Alc/alcModulator.c \ + $(OPENAL_DIR)/Alc/alcReverb.c \ + $(OPENAL_DIR)/Alc/alcRing.c \ + $(OPENAL_DIR)/Alc/alcThread.c \ + $(OPENAL_DIR)/Alc/ALu.c \ + $(OPENAL_DIR)/Alc/bs2b.c \ + $(OPENAL_DIR)/Alc/null.c \ + $(OPENAL_DIR)/Alc/panning.c \ + $(OPENAL_DIR)/Alc/mixer.c \ + $(OPENAL_DIR)/Alc/audiotrack.c \ + + +# If building for versions after FROYO +#LOCAL_CFLAGS += -DPOST_FROYO +#LOCAL_SRC_FILES += $(OPENAL_DIR)/Alc/opensles.o + +include $(BUILD_SHARED_LIBRARY) + + diff --git a/jni/Application.mk b/jni/Application.mk new file mode 100644 index 0000000..ed79cc6 --- /dev/null +++ b/jni/Application.mk @@ -0,0 +1 @@ + APP_ABI ?= armeabi \ No newline at end of file diff --git a/jni/OpenAL/Alc/ALc.c b/jni/OpenAL/Alc/ALc.c new file mode 100644 index 0000000..cf46706 --- /dev/null +++ b/jni/OpenAL/Alc/ALc.c @@ -0,0 +1,2343 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "alMain.h" +#include "alSource.h" +#include "AL/al.h" +#include "AL/alc.h" +#include "alThunk.h" +#include "alSource.h" +#include "alBuffer.h" +#include "alAuxEffectSlot.h" +#include "alDatabuffer.h" +#include "bs2b.h" +#include "alu.h" + + +#define EmptyFuncs { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } +typedef struct BackendInfo { + const char *name; + void (*Init)(BackendFuncs*); + void (*Deinit)(void); + void (*Probe)(int); + BackendFuncs Funcs; +} BackendInfo; +static BackendInfo BackendList[] = { +#ifdef HAVE_PULSEAUDIO + { "pulse", alc_pulse_init, alc_pulse_deinit, alc_pulse_probe, EmptyFuncs }, +#endif +#ifdef HAVE_ALSA + { "alsa", alc_alsa_init, alc_alsa_deinit, alc_alsa_probe, EmptyFuncs }, +#endif +#ifdef HAVE_OSS + { "oss", alc_oss_init, alc_oss_deinit, alc_oss_probe, EmptyFuncs }, +#endif +#ifdef HAVE_SOLARIS + { "solaris", alc_solaris_init, alc_solaris_deinit, alc_solaris_probe, EmptyFuncs }, +#endif +#ifdef HAVE_DSOUND + { "dsound", alcDSoundInit, alcDSoundDeinit, alcDSoundProbe, EmptyFuncs }, +#endif +#ifdef HAVE_WINMM + { "winmm", alcWinMMInit, alcWinMMDeinit, alcWinMMProbe, EmptyFuncs }, +#endif +#ifdef HAVE_PORTAUDIO + { "port", alc_pa_init, alc_pa_deinit, alc_pa_probe, EmptyFuncs }, +#endif +#ifdef HAVE_AUDIOTRACK + { "audiotrack", alc_audiotrack_init, alc_audiotrack_deinit, alc_audiotrack_probe, EmptyFuncs }, +#endif +#ifdef HAVE_OPENSLES + { "opensles", alc_opensles_init, alc_opensles_deinit, alc_opensles_probe, EmptyFuncs }, +#endif + + { "null", alc_null_init, alc_null_deinit, alc_null_probe, EmptyFuncs }, +#ifdef HAVE_WAVE + { "wave", alc_wave_init, alc_wave_deinit, alc_wave_probe, EmptyFuncs }, +#endif + + { NULL, NULL, NULL, NULL, EmptyFuncs } +}; +#undef EmptyFuncs + +/////////////////////////////////////////////////////// + +#define ALC_EFX_MAJOR_VERSION 0x20001 +#define ALC_EFX_MINOR_VERSION 0x20002 +#define ALC_MAX_AUXILIARY_SENDS 0x20003 + +/////////////////////////////////////////////////////// +// STRING and EXTENSIONS + +typedef struct ALCfunction { + const ALCchar *funcName; + ALCvoid *address; +} ALCfunction; + +typedef struct ALCenums { + const ALCchar *enumName; + ALCenum value; +} ALCenums; + + +static const ALCfunction alcFunctions[] = { + { "alcCreateContext", (ALCvoid *) alcCreateContext }, + { "alcMakeContextCurrent", (ALCvoid *) alcMakeContextCurrent }, + { "alcProcessContext", (ALCvoid *) alcProcessContext }, + { "alcSuspendContext", (ALCvoid *) alcSuspendContext }, + { "alcDestroyContext", (ALCvoid *) alcDestroyContext }, + { "alcGetCurrentContext", (ALCvoid *) alcGetCurrentContext }, + { "alcGetContextsDevice", (ALCvoid *) alcGetContextsDevice }, + { "alcOpenDevice", (ALCvoid *) alcOpenDevice }, + { "alcCloseDevice", (ALCvoid *) alcCloseDevice }, + { "alcGetError", (ALCvoid *) alcGetError }, + { "alcIsExtensionPresent", (ALCvoid *) alcIsExtensionPresent }, + { "alcGetProcAddress", (ALCvoid *) alcGetProcAddress }, + { "alcGetEnumValue", (ALCvoid *) alcGetEnumValue }, + { "alcGetString", (ALCvoid *) alcGetString }, + { "alcGetIntegerv", (ALCvoid *) alcGetIntegerv }, + { "alcCaptureOpenDevice", (ALCvoid *) alcCaptureOpenDevice }, + { "alcCaptureCloseDevice", (ALCvoid *) alcCaptureCloseDevice }, + { "alcCaptureStart", (ALCvoid *) alcCaptureStart }, + { "alcCaptureStop", (ALCvoid *) alcCaptureStop }, + { "alcCaptureSamples", (ALCvoid *) alcCaptureSamples }, + + { "alcSetThreadContext", (ALCvoid *) alcSetThreadContext }, + { "alcGetThreadContext", (ALCvoid *) alcGetThreadContext }, + + { "alEnable", (ALCvoid *) alEnable }, + { "alDisable", (ALCvoid *) alDisable }, + { "alIsEnabled", (ALCvoid *) alIsEnabled }, + { "alGetString", (ALCvoid *) alGetString }, + { "alGetBooleanv", (ALCvoid *) alGetBooleanv }, + { "alGetIntegerv", (ALCvoid *) alGetIntegerv }, + { "alGetFloatv", (ALCvoid *) alGetFloatv }, + { "alGetDoublev", (ALCvoid *) alGetDoublev }, + { "alGetBoolean", (ALCvoid *) alGetBoolean }, + { "alGetInteger", (ALCvoid *) alGetInteger }, + { "alGetFloat", (ALCvoid *) alGetFloat }, + { "alGetDouble", (ALCvoid *) alGetDouble }, + { "alGetError", (ALCvoid *) alGetError }, + { "alIsExtensionPresent", (ALCvoid *) alIsExtensionPresent }, + { "alGetProcAddress", (ALCvoid *) alGetProcAddress }, + { "alGetEnumValue", (ALCvoid *) alGetEnumValue }, + { "alListenerf", (ALCvoid *) alListenerf }, + { "alListener3f", (ALCvoid *) alListener3f }, + { "alListenerfv", (ALCvoid *) alListenerfv }, + { "alListeneri", (ALCvoid *) alListeneri }, + { "alListener3i", (ALCvoid *) alListener3i }, + { "alListeneriv", (ALCvoid *) alListeneriv }, + { "alGetListenerf", (ALCvoid *) alGetListenerf }, + { "alGetListener3f", (ALCvoid *) alGetListener3f }, + { "alGetListenerfv", (ALCvoid *) alGetListenerfv }, + { "alGetListeneri", (ALCvoid *) alGetListeneri }, + { "alGetListener3i", (ALCvoid *) alGetListener3i }, + { "alGetListeneriv", (ALCvoid *) alGetListeneriv }, + { "alGenSources", (ALCvoid *) alGenSources }, + { "alDeleteSources", (ALCvoid *) alDeleteSources }, + { "alIsSource", (ALCvoid *) alIsSource }, + { "alSourcef", (ALCvoid *) alSourcef }, + { "alSource3f", (ALCvoid *) alSource3f }, + { "alSourcefv", (ALCvoid *) alSourcefv }, + { "alSourcei", (ALCvoid *) alSourcei }, + { "alSource3i", (ALCvoid *) alSource3i }, + { "alSourceiv", (ALCvoid *) alSourceiv }, + { "alGetSourcef", (ALCvoid *) alGetSourcef }, + { "alGetSource3f", (ALCvoid *) alGetSource3f }, + { "alGetSourcefv", (ALCvoid *) alGetSourcefv }, + { "alGetSourcei", (ALCvoid *) alGetSourcei }, + { "alGetSource3i", (ALCvoid *) alGetSource3i }, + { "alGetSourceiv", (ALCvoid *) alGetSourceiv }, + { "alSourcePlayv", (ALCvoid *) alSourcePlayv }, + { "alSourceStopv", (ALCvoid *) alSourceStopv }, + { "alSourceRewindv", (ALCvoid *) alSourceRewindv }, + { "alSourcePausev", (ALCvoid *) alSourcePausev }, + { "alSourcePlay", (ALCvoid *) alSourcePlay }, + { "alSourceStop", (ALCvoid *) alSourceStop }, + { "alSourceRewind", (ALCvoid *) alSourceRewind }, + { "alSourcePause", (ALCvoid *) alSourcePause }, + { "alSourceQueueBuffers", (ALCvoid *) alSourceQueueBuffers }, + { "alSourceUnqueueBuffers", (ALCvoid *) alSourceUnqueueBuffers }, + { "alGenBuffers", (ALCvoid *) alGenBuffers }, + { "alDeleteBuffers", (ALCvoid *) alDeleteBuffers }, + { "alIsBuffer", (ALCvoid *) alIsBuffer }, + { "alBufferData", (ALCvoid *) alBufferData }, + { "alBufferf", (ALCvoid *) alBufferf }, + { "alBuffer3f", (ALCvoid *) alBuffer3f }, + { "alBufferfv", (ALCvoid *) alBufferfv }, + { "alBufferi", (ALCvoid *) alBufferi }, + { "alBuffer3i", (ALCvoid *) alBuffer3i }, + { "alBufferiv", (ALCvoid *) alBufferiv }, + { "alGetBufferf", (ALCvoid *) alGetBufferf }, + { "alGetBuffer3f", (ALCvoid *) alGetBuffer3f }, + { "alGetBufferfv", (ALCvoid *) alGetBufferfv }, + { "alGetBufferi", (ALCvoid *) alGetBufferi }, + { "alGetBuffer3i", (ALCvoid *) alGetBuffer3i }, + { "alGetBufferiv", (ALCvoid *) alGetBufferiv }, + { "alDopplerFactor", (ALCvoid *) alDopplerFactor }, + { "alDopplerVelocity", (ALCvoid *) alDopplerVelocity }, + { "alSpeedOfSound", (ALCvoid *) alSpeedOfSound }, + { "alDistanceModel", (ALCvoid *) alDistanceModel }, + + { "alGenFilters", (ALCvoid *) alGenFilters }, + { "alDeleteFilters", (ALCvoid *) alDeleteFilters }, + { "alIsFilter", (ALCvoid *) alIsFilter }, + { "alFilteri", (ALCvoid *) alFilteri }, + { "alFilteriv", (ALCvoid *) alFilteriv }, + { "alFilterf", (ALCvoid *) alFilterf }, + { "alFilterfv", (ALCvoid *) alFilterfv }, + { "alGetFilteri", (ALCvoid *) alGetFilteri }, + { "alGetFilteriv", (ALCvoid *) alGetFilteriv }, + { "alGetFilterf", (ALCvoid *) alGetFilterf }, + { "alGetFilterfv", (ALCvoid *) alGetFilterfv }, + + { "alGenEffects", (ALCvoid *) alGenEffects }, + { "alDeleteEffects", (ALCvoid *) alDeleteEffects }, + { "alIsEffect", (ALCvoid *) alIsEffect }, + { "alEffecti", (ALCvoid *) alEffecti }, + { "alEffectiv", (ALCvoid *) alEffectiv }, + { "alEffectf", (ALCvoid *) alEffectf }, + { "alEffectfv", (ALCvoid *) alEffectfv }, + { "alGetEffecti", (ALCvoid *) alGetEffecti }, + { "alGetEffectiv", (ALCvoid *) alGetEffectiv }, + { "alGetEffectf", (ALCvoid *) alGetEffectf }, + { "alGetEffectfv", (ALCvoid *) alGetEffectfv }, + + { "alGenAuxiliaryEffectSlots", (ALCvoid *) alGenAuxiliaryEffectSlots}, + { "alDeleteAuxiliaryEffectSlots",(ALCvoid *) alDeleteAuxiliaryEffectSlots}, + { "alIsAuxiliaryEffectSlot", (ALCvoid *) alIsAuxiliaryEffectSlot }, + { "alAuxiliaryEffectSloti", (ALCvoid *) alAuxiliaryEffectSloti }, + { "alAuxiliaryEffectSlotiv", (ALCvoid *) alAuxiliaryEffectSlotiv }, + { "alAuxiliaryEffectSlotf", (ALCvoid *) alAuxiliaryEffectSlotf }, + { "alAuxiliaryEffectSlotfv", (ALCvoid *) alAuxiliaryEffectSlotfv }, + { "alGetAuxiliaryEffectSloti", (ALCvoid *) alGetAuxiliaryEffectSloti}, + { "alGetAuxiliaryEffectSlotiv", (ALCvoid *) alGetAuxiliaryEffectSlotiv}, + { "alGetAuxiliaryEffectSlotf", (ALCvoid *) alGetAuxiliaryEffectSlotf}, + { "alGetAuxiliaryEffectSlotfv", (ALCvoid *) alGetAuxiliaryEffectSlotfv}, + + { "alBufferSubDataSOFT", (ALCvoid *) alBufferSubDataSOFT }, +#if 0 + { "alGenDatabuffersEXT", (ALCvoid *) alGenDatabuffersEXT }, + { "alDeleteDatabuffersEXT", (ALCvoid *) alDeleteDatabuffersEXT }, + { "alIsDatabufferEXT", (ALCvoid *) alIsDatabufferEXT }, + { "alDatabufferDataEXT", (ALCvoid *) alDatabufferDataEXT }, + { "alDatabufferSubDataEXT", (ALCvoid *) alDatabufferSubDataEXT }, + { "alGetDatabufferSubDataEXT", (ALCvoid *) alGetDatabufferSubDataEXT}, + { "alDatabufferfEXT", (ALCvoid *) alDatabufferfEXT }, + { "alDatabufferfvEXT", (ALCvoid *) alDatabufferfvEXT }, + { "alDatabufferiEXT", (ALCvoid *) alDatabufferiEXT }, + { "alDatabufferivEXT", (ALCvoid *) alDatabufferivEXT }, + { "alGetDatabufferfEXT", (ALCvoid *) alGetDatabufferfEXT }, + { "alGetDatabufferfvEXT", (ALCvoid *) alGetDatabufferfvEXT }, + { "alGetDatabufferiEXT", (ALCvoid *) alGetDatabufferiEXT }, + { "alGetDatabufferivEXT", (ALCvoid *) alGetDatabufferivEXT }, + { "alSelectDatabufferEXT", (ALCvoid *) alSelectDatabufferEXT }, + { "alMapDatabufferEXT", (ALCvoid *) alMapDatabufferEXT }, + { "alUnmapDatabufferEXT", (ALCvoid *) alUnmapDatabufferEXT }, +#endif + { NULL, (ALCvoid *) NULL } +}; + +static const ALCenums enumeration[] = { + // Types + { "ALC_INVALID", ALC_INVALID }, + { "ALC_FALSE", ALC_FALSE }, + { "ALC_TRUE", ALC_TRUE }, + + // ALC Properties + { "ALC_MAJOR_VERSION", ALC_MAJOR_VERSION }, + { "ALC_MINOR_VERSION", ALC_MINOR_VERSION }, + { "ALC_ATTRIBUTES_SIZE", ALC_ATTRIBUTES_SIZE }, + { "ALC_ALL_ATTRIBUTES", ALC_ALL_ATTRIBUTES }, + { "ALC_DEFAULT_DEVICE_SPECIFIER", ALC_DEFAULT_DEVICE_SPECIFIER }, + { "ALC_DEVICE_SPECIFIER", ALC_DEVICE_SPECIFIER }, + { "ALC_ALL_DEVICES_SPECIFIER", ALC_ALL_DEVICES_SPECIFIER }, + { "ALC_DEFAULT_ALL_DEVICES_SPECIFIER", ALC_DEFAULT_ALL_DEVICES_SPECIFIER }, + { "ALC_EXTENSIONS", ALC_EXTENSIONS }, + { "ALC_FREQUENCY", ALC_FREQUENCY }, + { "ALC_REFRESH", ALC_REFRESH }, + { "ALC_SYNC", ALC_SYNC }, + { "ALC_MONO_SOURCES", ALC_MONO_SOURCES }, + { "ALC_STEREO_SOURCES", ALC_STEREO_SOURCES }, + { "ALC_CAPTURE_DEVICE_SPECIFIER", ALC_CAPTURE_DEVICE_SPECIFIER }, + { "ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER", ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER}, + { "ALC_CAPTURE_SAMPLES", ALC_CAPTURE_SAMPLES }, + { "ALC_CONNECTED", ALC_CONNECTED }, + + // EFX Properties + { "ALC_EFX_MAJOR_VERSION", ALC_EFX_MAJOR_VERSION }, + { "ALC_EFX_MINOR_VERSION", ALC_EFX_MINOR_VERSION }, + { "ALC_MAX_AUXILIARY_SENDS", ALC_MAX_AUXILIARY_SENDS }, + + // ALC Error Message + { "ALC_NO_ERROR", ALC_NO_ERROR }, + { "ALC_INVALID_DEVICE", ALC_INVALID_DEVICE }, + { "ALC_INVALID_CONTEXT", ALC_INVALID_CONTEXT }, + { "ALC_INVALID_ENUM", ALC_INVALID_ENUM }, + { "ALC_INVALID_VALUE", ALC_INVALID_VALUE }, + { "ALC_OUT_OF_MEMORY", ALC_OUT_OF_MEMORY }, + { NULL, (ALCenum)0 } +}; +// Error strings +static const ALCchar alcNoError[] = "No Error"; +static const ALCchar alcErrInvalidDevice[] = "Invalid Device"; +static const ALCchar alcErrInvalidContext[] = "Invalid Context"; +static const ALCchar alcErrInvalidEnum[] = "Invalid Enum"; +static const ALCchar alcErrInvalidValue[] = "Invalid Value"; +static const ALCchar alcErrOutOfMemory[] = "Out of Memory"; + +/* Device lists. Sizes only include the first ending null character, not the + * second */ +static ALCchar *alcDeviceList; +static size_t alcDeviceListSize; +static ALCchar *alcAllDeviceList; +static size_t alcAllDeviceListSize; +static ALCchar *alcCaptureDeviceList; +static size_t alcCaptureDeviceListSize; +// Default is always the first in the list +static ALCchar *alcDefaultDeviceSpecifier; +static ALCchar *alcDefaultAllDeviceSpecifier; +static ALCchar *alcCaptureDefaultDeviceSpecifier; + + +static const ALCchar alcNoDeviceExtList[] = + "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " + "ALC_EXT_thread_local_context"; +static const ALCchar alcExtensionList[] = + "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " + "ALC_EXT_disconnect ALC_EXT_EFX ALC_EXT_thread_local_context"; +static const ALCint alcMajorVersion = 1; +static const ALCint alcMinorVersion = 1; + +static const ALCint alcEFXMajorVersion = 1; +static const ALCint alcEFXMinorVersion = 0; + +/////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////// +// Global Variables + +static ALCdevice *g_pDeviceList = NULL; +static ALCuint g_ulDeviceCount = 0; + +static CRITICAL_SECTION g_csMutex; + +// Context List +static ALCcontext *g_pContextList = NULL; +static ALCuint g_ulContextCount = 0; + +// Thread-local current context +static tls_type LocalContext; +// Process-wide current context +static ALCcontext *GlobalContext; + +// Context Error +static ALCenum g_eLastNullDeviceError = ALC_NO_ERROR; + +// Default context extensions +static const ALchar alExtList[] = + "AL_EXT_DOUBLE AL_EXT_EXPONENT_DISTANCE AL_EXT_FLOAT32 AL_EXT_IMA4 " + "AL_EXT_LINEAR_DISTANCE AL_EXT_MCFORMATS AL_EXT_MULAW " + "AL_EXT_MULAW_MCFORMATS AL_EXT_OFFSET AL_EXT_source_distance_model " + "AL_LOKI_quadriphonic AL_SOFT_buffer_sub_data AL_SOFT_loop_points"; + +// Mixing Priority Level +static ALint RTPrioLevel; + +// Output Log File +static FILE *LogFile; + +/////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////// +// ALC Related helper functions +static void ReleaseALC(void); + +#ifdef HAVE_GCC_DESTRUCTOR +static void alc_init(void) __attribute__((constructor)); +static void alc_deinit(void) __attribute__((destructor)); +#else +#ifdef _WIN32 +static void alc_init(void); +static void alc_deinit(void); + +BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) +{ + (void)lpReserved; + + // Perform actions based on the reason for calling. + switch(ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hModule); + alc_init(); + break; + + case DLL_PROCESS_DETACH: + alc_deinit(); + break; + } + return TRUE; +} +#endif +#endif + +static void alc_init(void) +{ + int i; + const char *devs, *str; + + str = getenv("ALSOFT_LOGFILE"); + if(str && str[0]) + { + LogFile = fopen(str, "w"); + if(!LogFile) + fprintf(stderr, "AL lib: Failed to open log file '%s'\n", str); + } + if(!LogFile) + LogFile = stderr; + + InitializeCriticalSection(&g_csMutex); + ALTHUNK_INIT(); + ReadALConfig(); + + tls_create(&LocalContext); + + RTPrioLevel = GetConfigValueInt(NULL, "rt-prio", 0); + + DefaultResampler = GetConfigValueInt(NULL, "resampler", RESAMPLER_DEFAULT); + if(DefaultResampler >= RESAMPLER_MAX || DefaultResampler <= RESAMPLER_MIN) + DefaultResampler = RESAMPLER_DEFAULT; + + devs = GetConfigValue(NULL, "drivers", ""); + if(devs[0]) + { + int n; + size_t len; + const char *next = devs; + int endlist, delitem; + + i = 0; + do { + devs = next; + next = strchr(devs, ','); + + delitem = (devs[0] == '-'); + if(devs[0] == '-') devs++; + + if(!devs[0] || devs[0] == ',') + { + endlist = 0; + continue; + } + endlist = 1; + + len = (next ? ((size_t)(next-devs)) : strlen(devs)); + for(n = i;BackendList[n].Init;n++) + { + if(len == strlen(BackendList[n].name) && + strncmp(BackendList[n].name, devs, len) == 0) + { + if(delitem) + { + do { + BackendList[n] = BackendList[n+1]; + ++n; + } while(BackendList[n].Init); + } + else + { + BackendInfo Bkp = BackendList[n]; + while(n > i) + { + BackendList[n] = BackendList[n-1]; + --n; + } + BackendList[n] = Bkp; + + i++; + } + break; + } + } + } while(next++); + + if(endlist) + { + BackendList[i].name = NULL; + BackendList[i].Init = NULL; + BackendList[i].Deinit = NULL; + BackendList[i].Probe = NULL; + } + } + + for(i = 0;BackendList[i].Init;i++) + BackendList[i].Init(&BackendList[i].Funcs); + + str = GetConfigValue(NULL, "excludefx", ""); + if(str[0]) + { + const struct { + const char *name; + int type; + } EffectList[] = { + { "eaxreverb", EAXREVERB }, + { "reverb", REVERB }, + { "echo", ECHO }, + { "modulator", MODULATOR }, + { NULL, 0 } + }; + int n; + size_t len; + const char *next = str; + + do { + str = next; + next = strchr(str, ','); + + if(!str[0] || next == str) + continue; + + len = (next ? ((size_t)(next-str)) : strlen(str)); + for(n = 0;EffectList[n].name;n++) + { + if(len == strlen(EffectList[n].name) && + strncmp(EffectList[n].name, str, len) == 0) + DisabledEffects[EffectList[n].type] = AL_TRUE; + } + } while(next++); + } +} + +static void alc_deinit(void) +{ + int i; + + ReleaseALC(); + + for(i = 0;BackendList[i].Deinit;i++) + BackendList[i].Deinit(); + + tls_delete(LocalContext); + + FreeALConfig(); + ALTHUNK_EXIT(); + DeleteCriticalSection(&g_csMutex); + + if(LogFile != stderr) + fclose(LogFile); + LogFile = NULL; +} + + +static void ProbeDeviceList() +{ + ALint i; + + free(alcDeviceList); alcDeviceList = NULL; + alcDeviceListSize = 0; + + for(i = 0;BackendList[i].Probe;i++) + BackendList[i].Probe(DEVICE_PROBE); +} + +static void ProbeAllDeviceList() +{ + ALint i; + + free(alcAllDeviceList); alcAllDeviceList = NULL; + alcAllDeviceListSize = 0; + + for(i = 0;BackendList[i].Probe;i++) + BackendList[i].Probe(ALL_DEVICE_PROBE); +} + +static void ProbeCaptureDeviceList() +{ + ALint i; + + free(alcCaptureDeviceList); alcCaptureDeviceList = NULL; + alcCaptureDeviceListSize = 0; + + for(i = 0;BackendList[i].Probe;i++) + BackendList[i].Probe(CAPTURE_DEVICE_PROBE); +} + + +static void AppendList(const ALCchar *name, ALCchar **List, size_t *ListSize) +{ + size_t len = strlen(name); + void *temp; + + if(len == 0) + return; + + temp = realloc(*List, (*ListSize) + len + 2); + if(!temp) + { + AL_PRINT("Realloc failed to add %s!\n", name); + return; + } + *List = temp; + + memcpy((*List)+(*ListSize), name, len+1); + *ListSize += len+1; + (*List)[*ListSize] = 0; +} + +#define DECL_APPEND_LIST_FUNC(type) \ +void Append##type##List(const ALCchar *name) \ +{ AppendList(name, &alc##type##List, &alc##type##ListSize); } + +DECL_APPEND_LIST_FUNC(Device) +DECL_APPEND_LIST_FUNC(AllDevice) +DECL_APPEND_LIST_FUNC(CaptureDevice) + +#undef DECL_APPEND_LIST_FUNC + + +void al_print(const char *fname, unsigned int line, const char *fmt, ...) +{ + const char *fn; + char str[256]; + int i; + + fn = strrchr(fname, '/'); + if(!fn) fn = strrchr(fname, '\\');; + if(!fn) fn = fname; + else fn += 1; + + i = snprintf(str, sizeof(str), "AL lib: %s:%d: ", fn, line); + if(i < (int)sizeof(str) && i > 0) + { + va_list ap; + va_start(ap, fmt); + vsnprintf(str+i, sizeof(str)-i, fmt, ap); + va_end(ap); + } + str[sizeof(str)-1] = 0; + + fprintf(LogFile, "%s", str); + fflush(LogFile); +} + +void SetRTPriority(void) +{ + ALboolean failed; + +#ifdef _WIN32 + if(RTPrioLevel > 0) + failed = !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + else + failed = !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#elif defined(HAVE_PTHREAD_SETSCHEDPARAM) + struct sched_param param; + + if(RTPrioLevel > 0) + { + /* Use the minimum real-time priority possible for now (on Linux this + * should be 1 for SCHED_RR) */ + param.sched_priority = sched_get_priority_min(SCHED_RR); + failed = !!pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); + } + else + { + param.sched_priority = 0; + failed = !!pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶m); + } +#else + /* Real-time priority not available */ + failed = (RTPrioLevel>0); +#endif + if(failed) + AL_PRINT("Failed to set priority level for thread\n"); +} + + +void InitUIntMap(UIntMap *map) +{ + map->array = NULL; + map->size = 0; + map->maxsize = 0; +} + +void ResetUIntMap(UIntMap *map) +{ + free(map->array); + map->array = NULL; + map->size = 0; + map->maxsize = 0; +} + +ALenum InsertUIntMapEntry(UIntMap *map, ALuint key, ALvoid *value) +{ + ALsizei pos = 0; + + if(map->size > 0) + { + ALsizei low = 0; + ALsizei high = map->size - 1; + while(low < high) + { + ALsizei mid = low + (high-low)/2; + if(map->array[mid].key < key) + low = mid + 1; + else + high = mid; + } + if(map->array[low].key < key) + low++; + pos = low; + } + + if(pos == map->size || map->array[pos].key != key) + { + if(map->size == map->maxsize) + { + ALvoid *temp; + ALsizei newsize; + + newsize = (map->maxsize ? (map->maxsize<<1) : 4); + if(newsize < map->maxsize) + return AL_OUT_OF_MEMORY; + + temp = realloc(map->array, newsize*sizeof(map->array[0])); + if(!temp) return AL_OUT_OF_MEMORY; + map->array = temp; + map->maxsize = newsize; + } + + map->size++; + if(pos < map->size-1) + memmove(&map->array[pos+1], &map->array[pos], + (map->size-1-pos)*sizeof(map->array[0])); + } + map->array[pos].key = key; + map->array[pos].value = value; + + return AL_NO_ERROR; +} + +void RemoveUIntMapKey(UIntMap *map, ALuint key) +{ + if(map->size > 0) + { + ALsizei low = 0; + ALsizei high = map->size - 1; + while(low < high) + { + ALsizei mid = low + (high-low)/2; + if(map->array[mid].key < key) + low = mid + 1; + else + high = mid; + } + if(map->array[low].key == key) + { + if(low < map->size-1) + memmove(&map->array[low], &map->array[low+1], + (map->size-1-low)*sizeof(map->array[0])); + map->size--; + } + } +} + +ALvoid *LookupUIntMapKey(UIntMap *map, ALuint key) +{ + if(map->size > 0) + { + ALsizei low = 0; + ALsizei high = map->size - 1; + while(low < high) + { + ALsizei mid = low + (high-low)/2; + if(map->array[mid].key < key) + low = mid + 1; + else + high = mid; + } + if(map->array[low].key == key) + return map->array[low].value; + } + return NULL; +} + + +ALuint BytesFromDevFmt(enum DevFmtType type) +{ + switch(type) + { + case DevFmtByte: return sizeof(ALbyte); + case DevFmtUByte: return sizeof(ALubyte); + case DevFmtShort: return sizeof(ALshort); + case DevFmtUShort: return sizeof(ALushort); + case DevFmtFloat: return sizeof(ALfloat); + } + return 0; +} +ALuint ChannelsFromDevFmt(enum DevFmtChannels chans) +{ + switch(chans) + { + case DevFmtMono: return 1; + case DevFmtStereo: return 2; + case DevFmtQuad: return 4; + case DevFmtX51: return 6; + case DevFmtX61: return 7; + case DevFmtX71: return 8; + } + return 0; +} +ALboolean DecomposeDevFormat(ALenum format, enum DevFmtChannels *chans, + enum DevFmtType *type) +{ + switch(format) + { + case AL_FORMAT_MONO8: + *chans = DevFmtMono; + *type = DevFmtUByte; + return AL_TRUE; + case AL_FORMAT_MONO16: + *chans = DevFmtMono; + *type = DevFmtShort; + return AL_TRUE; + case AL_FORMAT_MONO_FLOAT32: + *chans = DevFmtMono; + *type = DevFmtFloat; + return AL_TRUE; + case AL_FORMAT_STEREO8: + *chans = DevFmtStereo; + *type = DevFmtUByte; + return AL_TRUE; + case AL_FORMAT_STEREO16: + *chans = DevFmtStereo; + *type = DevFmtShort; + return AL_TRUE; + case AL_FORMAT_STEREO_FLOAT32: + *chans = DevFmtStereo; + *type = DevFmtFloat; + return AL_TRUE; + case AL_FORMAT_QUAD8: + *chans = DevFmtQuad; + *type = DevFmtUByte; + return AL_TRUE; + case AL_FORMAT_QUAD16: + *chans = DevFmtQuad; + *type = DevFmtShort; + return AL_TRUE; + case AL_FORMAT_QUAD32: + *chans = DevFmtQuad; + *type = DevFmtFloat; + return AL_TRUE; + case AL_FORMAT_51CHN8: + *chans = DevFmtX51; + *type = DevFmtUByte; + return AL_TRUE; + case AL_FORMAT_51CHN16: + *chans = DevFmtX51; + *type = DevFmtShort; + return AL_TRUE; + case AL_FORMAT_51CHN32: + *chans = DevFmtX51; + *type = DevFmtFloat; + return AL_TRUE; + case AL_FORMAT_61CHN8: + *chans = DevFmtX61; + *type = DevFmtUByte; + return AL_TRUE; + case AL_FORMAT_61CHN16: + *chans = DevFmtX61; + *type = DevFmtShort; + return AL_TRUE; + case AL_FORMAT_61CHN32: + *chans = DevFmtX61; + *type = DevFmtFloat; + return AL_TRUE; + case AL_FORMAT_71CHN8: + *chans = DevFmtX71; + *type = DevFmtUByte; + return AL_TRUE; + case AL_FORMAT_71CHN16: + *chans = DevFmtX71; + *type = DevFmtShort; + return AL_TRUE; + case AL_FORMAT_71CHN32: + *chans = DevFmtX71; + *type = DevFmtFloat; + return AL_TRUE; + } + return AL_FALSE; +} + +/* + IsDevice + + Check pDevice is a valid Device pointer +*/ +static ALCboolean IsDevice(ALCdevice *pDevice) +{ + ALCdevice *pTempDevice; + + SuspendContext(NULL); + + pTempDevice = g_pDeviceList; + while(pTempDevice && pTempDevice != pDevice) + pTempDevice = pTempDevice->next; + + ProcessContext(NULL); + + return (pTempDevice ? ALC_TRUE : ALC_FALSE); +} + +/* + IsContext + + Check pContext is a valid Context pointer +*/ +static ALCboolean IsContext(ALCcontext *pContext) +{ + ALCcontext *pTempContext; + + SuspendContext(NULL); + + pTempContext = g_pContextList; + while (pTempContext && pTempContext != pContext) + pTempContext = pTempContext->next; + + ProcessContext(NULL); + + return (pTempContext ? ALC_TRUE : ALC_FALSE); +} + + +/* + alcSetError + + Store latest ALC Error +*/ +ALCvoid alcSetError(ALCdevice *device, ALenum errorCode) +{ + if(IsDevice(device)) + device->LastError = errorCode; + else + g_eLastNullDeviceError = errorCode; +} + + +/* UpdateDeviceParams: + * + * Updates device parameters according to the attribute list. + */ +static ALCboolean UpdateDeviceParams(ALCdevice *device, const ALCint *attrList) +{ + ALCuint freq, numMono, numStereo, numSends; + ALboolean running; + ALuint oldRate; + ALuint attrIdx; + ALuint i; + + running = ((device->NumContexts > 0) ? AL_TRUE : AL_FALSE); + oldRate = device->Frequency; + + // Check for attributes + if(attrList && attrList[0]) + { + // If a context is already running on the device, stop playback so the + // device attributes can be updated + if(running) + { + ProcessContext(NULL); + ALCdevice_StopPlayback(device); + SuspendContext(NULL); + running = AL_FALSE; + } + + freq = device->Frequency; + numMono = device->NumMonoSources; + numStereo = device->NumStereoSources; + numSends = device->NumAuxSends; + + attrIdx = 0; + while(attrList[attrIdx]) + { + if(attrList[attrIdx] == ALC_FREQUENCY && + !ConfigValueExists(NULL, "frequency")) + { + freq = attrList[attrIdx + 1]; + if(freq < 8000) + freq = 8000; + } + + if(attrList[attrIdx] == ALC_STEREO_SOURCES) + { + numStereo = attrList[attrIdx + 1]; + if(numStereo > device->MaxNoOfSources) + numStereo = device->MaxNoOfSources; + + numMono = device->MaxNoOfSources - numStereo; + } + + if(attrList[attrIdx] == ALC_MAX_AUXILIARY_SENDS && + !ConfigValueExists(NULL, "sends")) + { + numSends = attrList[attrIdx + 1]; + if(numSends > MAX_SENDS) + numSends = MAX_SENDS; + } + + attrIdx += 2; + } + + device->UpdateSize = (ALuint64)device->UpdateSize * freq / + device->Frequency; + + device->Frequency = freq; + device->NumMonoSources = numMono; + device->NumStereoSources = numStereo; + device->NumAuxSends = numSends; + } + + if(running) + return ALC_TRUE; + + if(ALCdevice_ResetPlayback(device) == ALC_FALSE) + return ALC_FALSE; + + aluInitPanning(device); + + for(i = 0;i < MAXCHANNELS;i++) + { + device->ClickRemoval[i] = int2ALfp(0); + device->PendingClicks[i] = int2ALfp(0); + } + + for(i = 0;i < device->NumContexts;i++) + { + ALCcontext *context = device->Contexts[i]; + ALsizei pos; + + SuspendContext(context); + for(pos = 0;pos < context->EffectSlotMap.size;pos++) + { + ALeffectslot *slot = context->EffectSlotMap.array[pos].value; + + if(ALEffect_DeviceUpdate(slot->EffectState, device) == AL_FALSE) + { + ProcessContext(context); + return ALC_FALSE; + } + ALEffect_Update(slot->EffectState, context, &slot->effect); + } + + for(pos = 0;pos < context->SourceMap.size;pos++) + { + ALsource *source = context->SourceMap.array[pos].value; + ALuint s = device->NumAuxSends; + while(s < MAX_SENDS) + { + if(source->Send[s].Slot) + source->Send[s].Slot->refcount--; + source->Send[s].Slot = NULL; + source->Send[s].WetFilter.type = 0; + source->Send[s].WetFilter.filter = 0; + s++; + } + source->NeedsUpdate = AL_TRUE; + } + ProcessContext(context); + } + + if(device->Bs2bLevel > 0 && device->Bs2bLevel <= 6) + { + if(!device->Bs2b) + { + device->Bs2b = calloc(1, sizeof(*device->Bs2b)); + bs2b_clear(device->Bs2b); + } + bs2b_set_srate(device->Bs2b, device->Frequency); + bs2b_set_level(device->Bs2b, device->Bs2bLevel); + } + else + { + free(device->Bs2b); + device->Bs2b = NULL; + } + + if(ChannelsFromDevFmt(device->FmtChans) <= 2) + { + device->HeadDampen = float2ALfp(GetConfigValueFloat(NULL, "head_dampen", DEFAULT_HEAD_DAMPEN)); + device->HeadDampen = __min(device->HeadDampen, int2ALfp(1)); + device->HeadDampen = __max(device->HeadDampen, int2ALfp(0)); + } + else + device->HeadDampen = int2ALfp(0); + + return ALC_TRUE; +} + + +/* + SuspendContext + + Thread-safe entry +*/ +ALCvoid SuspendContext(ALCcontext *pContext) +{ + (void)pContext; + EnterCriticalSection(&g_csMutex); +} + + +/* + ProcessContext + + Thread-safe exit +*/ +ALCvoid ProcessContext(ALCcontext *pContext) +{ + (void)pContext; + LeaveCriticalSection(&g_csMutex); +} + + +/* + GetContextSuspended + + Returns the currently active Context, in a locked state +*/ +ALCcontext *GetContextSuspended(void) +{ + ALCcontext *pContext = NULL; + + SuspendContext(NULL); + + pContext = tls_get(LocalContext); + if(pContext && !IsContext(pContext)) + { + tls_set(LocalContext, NULL); + pContext = NULL; + } + if(!pContext) + pContext = GlobalContext; + + if(pContext) + SuspendContext(pContext); + + ProcessContext(NULL); + + return pContext; +} + + +/* + InitContext + + Initialize Context variables +*/ +static ALvoid InitContext(ALCcontext *pContext) +{ + //Initialise listener + pContext->Listener.Gain = int2ALfp(1); + pContext->Listener.MetersPerUnit = int2ALfp(1); + pContext->Listener.Position[0] = int2ALfp(0); + pContext->Listener.Position[1] = int2ALfp(0); + pContext->Listener.Position[2] = int2ALfp(0); + pContext->Listener.Velocity[0] = int2ALfp(0); + pContext->Listener.Velocity[1] = int2ALfp(0); + pContext->Listener.Velocity[2] = int2ALfp(0); + pContext->Listener.Forward[0] = int2ALfp(0); + pContext->Listener.Forward[1] = int2ALfp(0); + pContext->Listener.Forward[2] = int2ALfp(-1); + pContext->Listener.Up[0] = int2ALfp(0); + pContext->Listener.Up[1] = int2ALfp(1); + pContext->Listener.Up[2] = int2ALfp(0); + + //Validate pContext + pContext->LastError = AL_NO_ERROR; + pContext->Suspended = AL_FALSE; + pContext->ActiveSourceCount = 0; + InitUIntMap(&pContext->SourceMap); + InitUIntMap(&pContext->EffectSlotMap); + + //Set globals + pContext->DistanceModel = AL_INVERSE_DISTANCE_CLAMPED; + pContext->SourceDistanceModel = AL_FALSE; + pContext->DopplerFactor = int2ALfp(1); + pContext->DopplerVelocity = int2ALfp(1); + pContext->flSpeedOfSound = float2ALfp(SPEEDOFSOUNDMETRESPERSEC); + + pContext->ExtensionList = alExtList; +} + + +/* + ExitContext + + Clean up Context, destroy any remaining Sources +*/ +static ALCvoid ExitContext(ALCcontext *pContext) +{ + //Invalidate context + pContext->LastError = AL_NO_ERROR; +} + +/////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////// +// ALC Functions calls + + +// This should probably move to another c file but for now ... +ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei SampleSize) +{ + ALCboolean DeviceFound = ALC_FALSE; + ALCdevice *device = NULL; + ALCint i; + + if(SampleSize <= 0) + { + alcSetError(NULL, ALC_INVALID_VALUE); + return NULL; + } + + if(deviceName && !deviceName[0]) + deviceName = NULL; + + device = calloc(1, sizeof(ALCdevice)); + if(!device) + { + alcSetError(NULL, ALC_OUT_OF_MEMORY); + return NULL; + } + + //Validate device + device->Connected = ALC_TRUE; + device->IsCaptureDevice = AL_TRUE; + + device->szDeviceName = NULL; + + device->Frequency = frequency; + if(DecomposeDevFormat(format, &device->FmtChans, &device->FmtType) == AL_FALSE) + { + free(device); + alcSetError(NULL, ALC_INVALID_ENUM); + return NULL; + } + + device->UpdateSize = SampleSize; + device->NumUpdates = 1; + + SuspendContext(NULL); + for(i = 0;BackendList[i].Init;i++) + { + device->Funcs = &BackendList[i].Funcs; + if(ALCdevice_OpenCapture(device, deviceName)) + { + device->next = g_pDeviceList; + g_pDeviceList = device; + g_ulDeviceCount++; + + DeviceFound = ALC_TRUE; + break; + } + } + ProcessContext(NULL); + + if(!DeviceFound) + { + alcSetError(NULL, ALC_INVALID_VALUE); + free(device); + device = NULL; + } + + return device; +} + +ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *pDevice) +{ + ALCdevice **list; + + if(!IsDevice(pDevice) || !pDevice->IsCaptureDevice) + { + alcSetError(pDevice, ALC_INVALID_DEVICE); + return ALC_FALSE; + } + + SuspendContext(NULL); + + list = &g_pDeviceList; + while(*list != pDevice) + list = &(*list)->next; + + *list = (*list)->next; + g_ulDeviceCount--; + + ProcessContext(NULL); + + ALCdevice_CloseCapture(pDevice); + + free(pDevice->szDeviceName); + pDevice->szDeviceName = NULL; + + free(pDevice); + + return ALC_TRUE; +} + +ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) +{ + SuspendContext(NULL); + if(!IsDevice(device) || !device->IsCaptureDevice) + alcSetError(device, ALC_INVALID_DEVICE); + else if(device->Connected) + ALCdevice_StartCapture(device); + ProcessContext(NULL); +} + +ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) +{ + SuspendContext(NULL); + if(!IsDevice(device) || !device->IsCaptureDevice) + alcSetError(device, ALC_INVALID_DEVICE); + else + ALCdevice_StopCapture(device); + ProcessContext(NULL); +} + +ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) +{ + SuspendContext(NULL); + if(!IsDevice(device) || !device->IsCaptureDevice) + alcSetError(device, ALC_INVALID_DEVICE); + else + ALCdevice_CaptureSamples(device, buffer, samples); + ProcessContext(NULL); +} + +/* + alcGetError + + Return last ALC generated error code +*/ +ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) +{ + ALCenum errorCode; + + if(IsDevice(device)) + { + errorCode = device->LastError; + device->LastError = ALC_NO_ERROR; + } + else + { + errorCode = g_eLastNullDeviceError; + g_eLastNullDeviceError = ALC_NO_ERROR; + } + return errorCode; +} + + +/* + alcSuspendContext + + Not functional +*/ +ALC_API ALCvoid ALC_APIENTRY alcSuspendContext(ALCcontext *pContext) +{ + SuspendContext(NULL); + if(IsContext(pContext)) + pContext->Suspended = AL_TRUE; + ProcessContext(NULL); +} + + +/* + alcProcessContext + + Not functional +*/ +ALC_API ALCvoid ALC_APIENTRY alcProcessContext(ALCcontext *pContext) +{ + SuspendContext(NULL); + if(IsContext(pContext)) + pContext->Suspended = AL_FALSE; + ProcessContext(NULL); +} + + +/* + alcGetString + + Returns information about the Device, and error strings +*/ +ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *pDevice,ALCenum param) +{ + const ALCchar *value = NULL; + + switch (param) + { + case ALC_NO_ERROR: + value = alcNoError; + break; + + case ALC_INVALID_ENUM: + value = alcErrInvalidEnum; + break; + + case ALC_INVALID_VALUE: + value = alcErrInvalidValue; + break; + + case ALC_INVALID_DEVICE: + value = alcErrInvalidDevice; + break; + + case ALC_INVALID_CONTEXT: + value = alcErrInvalidContext; + break; + + case ALC_OUT_OF_MEMORY: + value = alcErrOutOfMemory; + break; + + case ALC_DEVICE_SPECIFIER: + if(IsDevice(pDevice)) + value = pDevice->szDeviceName; + else + { + ProbeDeviceList(); + value = alcDeviceList; + } + break; + + case ALC_ALL_DEVICES_SPECIFIER: + ProbeAllDeviceList(); + value = alcAllDeviceList; + break; + + case ALC_CAPTURE_DEVICE_SPECIFIER: + if(IsDevice(pDevice)) + value = pDevice->szDeviceName; + else + { + ProbeCaptureDeviceList(); + value = alcCaptureDeviceList; + } + break; + + /* Default devices are always first in the list */ + case ALC_DEFAULT_DEVICE_SPECIFIER: + if(!alcDeviceList) + ProbeDeviceList(); + + free(alcDefaultDeviceSpecifier); + alcDefaultDeviceSpecifier = strdup(alcDeviceList ? alcDeviceList : ""); + if(!alcDefaultDeviceSpecifier) + alcSetError(pDevice, ALC_OUT_OF_MEMORY); + value = alcDefaultDeviceSpecifier; + break; + + case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: + if(!alcAllDeviceList) + ProbeAllDeviceList(); + + free(alcDefaultAllDeviceSpecifier); + alcDefaultAllDeviceSpecifier = strdup(alcAllDeviceList ? + alcAllDeviceList : ""); + if(!alcDefaultAllDeviceSpecifier) + alcSetError(pDevice, ALC_OUT_OF_MEMORY); + value = alcDefaultAllDeviceSpecifier; + break; + + case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: + if(!alcCaptureDeviceList) + ProbeCaptureDeviceList(); + + free(alcCaptureDefaultDeviceSpecifier); + alcCaptureDefaultDeviceSpecifier = strdup(alcCaptureDeviceList ? + alcCaptureDeviceList : ""); + if(!alcCaptureDefaultDeviceSpecifier) + alcSetError(pDevice, ALC_OUT_OF_MEMORY); + value = alcCaptureDefaultDeviceSpecifier; + break; + + case ALC_EXTENSIONS: + if(IsDevice(pDevice)) + value = alcExtensionList; + else + value = alcNoDeviceExtList; + break; + + default: + alcSetError(pDevice, ALC_INVALID_ENUM); + break; + } + + return value; +} + + +/* + alcGetIntegerv + + Returns information about the Device and the version of Open AL +*/ +ALC_API ALCvoid ALC_APIENTRY alcGetIntegerv(ALCdevice *device,ALCenum param,ALsizei size,ALCint *data) +{ + if(size == 0 || data == NULL) + { + alcSetError(device, ALC_INVALID_VALUE); + return; + } + + if(IsDevice(device) && device->IsCaptureDevice) + { + SuspendContext(NULL); + + // Capture device + switch (param) + { + case ALC_CAPTURE_SAMPLES: + *data = ALCdevice_AvailableSamples(device); + break; + + case ALC_CONNECTED: + *data = device->Connected; + break; + + default: + alcSetError(device, ALC_INVALID_ENUM); + break; + } + + ProcessContext(NULL); + return; + } + + // Playback Device + switch (param) + { + case ALC_MAJOR_VERSION: + *data = alcMajorVersion; + break; + + case ALC_MINOR_VERSION: + *data = alcMinorVersion; + break; + + case ALC_EFX_MAJOR_VERSION: + *data = alcEFXMajorVersion; + break; + + case ALC_EFX_MINOR_VERSION: + *data = alcEFXMinorVersion; + break; + + case ALC_MAX_AUXILIARY_SENDS: + if(!IsDevice(device)) + alcSetError(device, ALC_INVALID_DEVICE); + else + *data = device->NumAuxSends; + break; + + case ALC_ATTRIBUTES_SIZE: + if(!IsDevice(device)) + alcSetError(device, ALC_INVALID_DEVICE); + else + *data = 13; + break; + + case ALC_ALL_ATTRIBUTES: + if(!IsDevice(device)) + alcSetError(device, ALC_INVALID_DEVICE); + else if (size < 13) + alcSetError(device, ALC_INVALID_VALUE); + else + { + int i = 0; + + SuspendContext(NULL); + data[i++] = ALC_FREQUENCY; + data[i++] = device->Frequency; + + data[i++] = ALC_REFRESH; + data[i++] = device->Frequency / device->UpdateSize; + + data[i++] = ALC_SYNC; + data[i++] = ALC_FALSE; + + data[i++] = ALC_MONO_SOURCES; + data[i++] = device->NumMonoSources; + + data[i++] = ALC_STEREO_SOURCES; + data[i++] = device->NumStereoSources; + + data[i++] = ALC_MAX_AUXILIARY_SENDS; + data[i++] = device->NumAuxSends; + + data[i++] = 0; + ProcessContext(NULL); + } + break; + + case ALC_FREQUENCY: + if(!IsDevice(device)) + alcSetError(device, ALC_INVALID_DEVICE); + else + *data = device->Frequency; + break; + + case ALC_REFRESH: + if(!IsDevice(device)) + alcSetError(device, ALC_INVALID_DEVICE); + else + *data = device->Frequency / device->UpdateSize; + break; + + case ALC_SYNC: + if(!IsDevice(device)) + alcSetError(device, ALC_INVALID_DEVICE); + else + *data = ALC_FALSE; + break; + + case ALC_MONO_SOURCES: + if(!IsDevice(device)) + alcSetError(device, ALC_INVALID_DEVICE); + else + *data = device->NumMonoSources; + break; + + case ALC_STEREO_SOURCES: + if(!IsDevice(device)) + alcSetError(device, ALC_INVALID_DEVICE); + else + *data = device->NumStereoSources; + break; + + case ALC_CONNECTED: + if(!IsDevice(device)) + alcSetError(device, ALC_INVALID_DEVICE); + else + *data = device->Connected; + break; + + default: + alcSetError(device, ALC_INVALID_ENUM); + break; + } +} + + +/* + alcIsExtensionPresent + + Determines if there is support for a particular extension +*/ +ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName) +{ + ALCboolean bResult = ALC_FALSE; + const char *ptr; + size_t len; + + if(!extName) + { + alcSetError(device, ALC_INVALID_VALUE); + return ALC_FALSE; + } + + len = strlen(extName); + ptr = (IsDevice(device) ? alcExtensionList : alcNoDeviceExtList); + while(ptr && *ptr) + { + if(strncasecmp(ptr, extName, len) == 0 && + (ptr[len] == '\0' || isspace(ptr[len]))) + { + bResult = ALC_TRUE; + break; + } + if((ptr=strchr(ptr, ' ')) != NULL) + { + do { + ++ptr; + } while(isspace(*ptr)); + } + } + + return bResult; +} + + +/* + alcGetProcAddress + + Retrieves the function address for a particular extension function +*/ +ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName) +{ + ALsizei i = 0; + + if(!funcName) + { + alcSetError(device, ALC_INVALID_VALUE); + return NULL; + } + + while(alcFunctions[i].funcName && strcmp(alcFunctions[i].funcName,funcName) != 0) + i++; + return alcFunctions[i].address; +} + + +/* + alcGetEnumValue + + Get the value for a particular ALC Enumerated Value +*/ +ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName) +{ + ALsizei i = 0; + + if(!enumName) + { + alcSetError(device, ALC_INVALID_VALUE); + return (ALCenum)0; + } + + while(enumeration[i].enumName && strcmp(enumeration[i].enumName,enumName) != 0) + i++; + return enumeration[i].value; +} + + +/* + alcCreateContext + + Create and attach a Context to a particular Device. +*/ +ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList) +{ + ALCcontext *ALContext; + void *temp; + + SuspendContext(NULL); + + if(!IsDevice(device) || device->IsCaptureDevice || !device->Connected) + { + alcSetError(device, ALC_INVALID_DEVICE); + ProcessContext(NULL); + return NULL; + } + + // Reset Context Last Error code + device->LastError = ALC_NO_ERROR; + + if(UpdateDeviceParams(device, attrList) == ALC_FALSE) + { + alcSetError(device, ALC_INVALID_DEVICE); + aluHandleDisconnect(device); + ProcessContext(NULL); + ALCdevice_StopPlayback(device); + return NULL; + } + + ALContext = NULL; + temp = realloc(device->Contexts, (device->NumContexts+1) * sizeof(*device->Contexts)); + if(temp) + { + device->Contexts = temp; + + ALContext = calloc(1, sizeof(ALCcontext)); + if(ALContext) + { + ALContext->MaxActiveSources = 256; + ALContext->ActiveSources = malloc(sizeof(ALContext->ActiveSources[0]) * + ALContext->MaxActiveSources); + } + } + if(!ALContext || !ALContext->ActiveSources) + { + free(ALContext); + alcSetError(device, ALC_OUT_OF_MEMORY); + ProcessContext(NULL); + if(device->NumContexts == 0) + ALCdevice_StopPlayback(device); + return NULL; + } + + device->Contexts[device->NumContexts++] = ALContext; + ALContext->Device = device; + + InitContext(ALContext); + + ALContext->next = g_pContextList; + g_pContextList = ALContext; + g_ulContextCount++; + + ProcessContext(NULL); + + return ALContext; +} + + +/* + alcDestroyContext + + Remove a Context +*/ +ALC_API ALCvoid ALC_APIENTRY alcDestroyContext(ALCcontext *context) +{ + ALCdevice *Device; + ALCcontext **list; + ALuint i; + + if(!IsContext(context)) + { + alcSetError(NULL, ALC_INVALID_CONTEXT); + return; + } + + Device = context->Device; + + if(Device->NumContexts == 1) + ALCdevice_StopPlayback(Device); + + SuspendContext(NULL); + + if(context == GlobalContext) + GlobalContext = NULL; + + for(i = 0;i < Device->NumContexts;i++) + { + if(Device->Contexts[i] == context) + { + Device->Contexts[i] = Device->Contexts[Device->NumContexts-1]; + Device->NumContexts--; + break; + } + } + + // Lock context + SuspendContext(context); + + if(context->SourceMap.size > 0) + { +#ifdef _DEBUG + AL_PRINT("alcDestroyContext(): deleting %d Source(s)\n", context->SourceMap.size); +#endif + ReleaseALSources(context); + } + ResetUIntMap(&context->SourceMap); + + if(context->EffectSlotMap.size > 0) + { +#ifdef _DEBUG + AL_PRINT("alcDestroyContext(): deleting %d AuxiliaryEffectSlot(s)\n", context->EffectSlotMap.size); +#endif + ReleaseALAuxiliaryEffectSlots(context); + } + ResetUIntMap(&context->EffectSlotMap); + + free(context->ActiveSources); + context->ActiveSources = NULL; + context->MaxActiveSources = 0; + context->ActiveSourceCount = 0; + + list = &g_pContextList; + while(*list != context) + list = &(*list)->next; + + *list = (*list)->next; + g_ulContextCount--; + + // Unlock context + ProcessContext(context); + ProcessContext(NULL); + + ExitContext(context); + + // Free memory (MUST do this after ProcessContext) + memset(context, 0, sizeof(ALCcontext)); + free(context); +} + + +/* + alcGetCurrentContext + + Returns the currently active Context +*/ +ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(ALCvoid) +{ + ALCcontext *pContext; + + if((pContext=GetContextSuspended()) != NULL) + ProcessContext(pContext); + + return pContext; +} + +/* + alcGetThreadContext + + Returns the currently active thread-local Context +*/ +ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) +{ + ALCcontext *pContext = NULL; + + SuspendContext(NULL); + + pContext = tls_get(LocalContext); + if(pContext && !IsContext(pContext)) + { + tls_set(LocalContext, NULL); + pContext = NULL; + } + + ProcessContext(NULL); + + return pContext; +} + + +/* + alcGetContextsDevice + + Returns the Device that a particular Context is attached to +*/ +ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *pContext) +{ + ALCdevice *pDevice = NULL; + + SuspendContext(NULL); + if(IsContext(pContext)) + pDevice = pContext->Device; + else + alcSetError(NULL, ALC_INVALID_CONTEXT); + ProcessContext(NULL); + + return pDevice; +} + + +/* + alcMakeContextCurrent + + Makes the given Context the active Context +*/ +ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) +{ + ALboolean bReturn = AL_TRUE; + + SuspendContext(NULL); + + // context must be a valid Context or NULL + if(context == NULL || IsContext(context)) + { + GlobalContext = context; + tls_set(LocalContext, NULL); + } + else + { + alcSetError(NULL, ALC_INVALID_CONTEXT); + bReturn = AL_FALSE; + } + + ProcessContext(NULL); + + return bReturn; +} + +/* + alcSetThreadContext + + Makes the given Context the active Context for the current thread +*/ +ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) +{ + ALboolean bReturn = AL_TRUE; + + SuspendContext(NULL); + + // context must be a valid Context or NULL + if(context == NULL || IsContext(context)) + tls_set(LocalContext, context); + else + { + alcSetError(NULL, ALC_INVALID_CONTEXT); + bReturn = AL_FALSE; + } + + ProcessContext(NULL); + + return bReturn; +} + + +// Sets the default channel order used by most non-WaveFormatEx-based APIs +void SetDefaultChannelOrder(ALCdevice *device) +{ + switch(device->FmtChans) + { + case DevFmtMono: device->DevChannels[FRONT_CENTER] = 0; break; + + case DevFmtStereo: device->DevChannels[FRONT_LEFT] = 0; + device->DevChannels[FRONT_RIGHT] = 1; break; + + case DevFmtQuad: device->DevChannels[FRONT_LEFT] = 0; + device->DevChannels[FRONT_RIGHT] = 1; + device->DevChannels[BACK_LEFT] = 2; + device->DevChannels[BACK_RIGHT] = 3; break; + + case DevFmtX51: device->DevChannels[FRONT_LEFT] = 0; + device->DevChannels[FRONT_RIGHT] = 1; + device->DevChannels[BACK_LEFT] = 2; + device->DevChannels[BACK_RIGHT] = 3; + device->DevChannels[FRONT_CENTER] = 4; + device->DevChannels[LFE] = 5; break; + + case DevFmtX61: device->DevChannels[FRONT_LEFT] = 0; + device->DevChannels[FRONT_RIGHT] = 1; + device->DevChannels[FRONT_CENTER] = 2; + device->DevChannels[LFE] = 3; + device->DevChannels[BACK_CENTER] = 4; + device->DevChannels[SIDE_LEFT] = 5; + device->DevChannels[SIDE_RIGHT] = 6; break; + + case DevFmtX71: device->DevChannels[FRONT_LEFT] = 0; + device->DevChannels[FRONT_RIGHT] = 1; + device->DevChannels[BACK_LEFT] = 2; + device->DevChannels[BACK_RIGHT] = 3; + device->DevChannels[FRONT_CENTER] = 4; + device->DevChannels[LFE] = 5; + device->DevChannels[SIDE_LEFT] = 6; + device->DevChannels[SIDE_RIGHT] = 7; break; + } +} +// Sets the default order used by WaveFormatEx +void SetDefaultWFXChannelOrder(ALCdevice *device) +{ + switch(device->FmtChans) + { + case DevFmtMono: device->DevChannels[FRONT_CENTER] = 0; break; + + case DevFmtStereo: device->DevChannels[FRONT_LEFT] = 0; + device->DevChannels[FRONT_RIGHT] = 1; break; + + case DevFmtQuad: device->DevChannels[FRONT_LEFT] = 0; + device->DevChannels[FRONT_RIGHT] = 1; + device->DevChannels[BACK_LEFT] = 2; + device->DevChannels[BACK_RIGHT] = 3; break; + + case DevFmtX51: device->DevChannels[FRONT_LEFT] = 0; + device->DevChannels[FRONT_RIGHT] = 1; + device->DevChannels[FRONT_CENTER] = 2; + device->DevChannels[LFE] = 3; + device->DevChannels[BACK_LEFT] = 4; + device->DevChannels[BACK_RIGHT] = 5; break; + + case DevFmtX61: device->DevChannels[FRONT_LEFT] = 0; + device->DevChannels[FRONT_RIGHT] = 1; + device->DevChannels[FRONT_CENTER] = 2; + device->DevChannels[LFE] = 3; + device->DevChannels[BACK_CENTER] = 4; + device->DevChannels[SIDE_LEFT] = 5; + device->DevChannels[SIDE_RIGHT] = 6; break; + + case DevFmtX71: device->DevChannels[FRONT_LEFT] = 0; + device->DevChannels[FRONT_RIGHT] = 1; + device->DevChannels[FRONT_CENTER] = 2; + device->DevChannels[LFE] = 3; + device->DevChannels[BACK_LEFT] = 4; + device->DevChannels[BACK_RIGHT] = 5; + device->DevChannels[SIDE_LEFT] = 6; + device->DevChannels[SIDE_RIGHT] = 7; break; + } +} + +static ALenum GetFormatFromString(const char *str) +{ + if(strcasecmp(str, "AL_FORMAT_MONO32") == 0) return AL_FORMAT_MONO_FLOAT32; + if(strcasecmp(str, "AL_FORMAT_STEREO32") == 0) return AL_FORMAT_STEREO_FLOAT32; + if(strcasecmp(str, "AL_FORMAT_QUAD32") == 0) return AL_FORMAT_QUAD32; + if(strcasecmp(str, "AL_FORMAT_51CHN32") == 0) return AL_FORMAT_51CHN32; + if(strcasecmp(str, "AL_FORMAT_61CHN32") == 0) return AL_FORMAT_61CHN32; + if(strcasecmp(str, "AL_FORMAT_71CHN32") == 0) return AL_FORMAT_71CHN32; + + if(strcasecmp(str, "AL_FORMAT_MONO16") == 0) return AL_FORMAT_MONO16; + if(strcasecmp(str, "AL_FORMAT_STEREO16") == 0) return AL_FORMAT_STEREO16; + if(strcasecmp(str, "AL_FORMAT_QUAD16") == 0) return AL_FORMAT_QUAD16; + if(strcasecmp(str, "AL_FORMAT_51CHN16") == 0) return AL_FORMAT_51CHN16; + if(strcasecmp(str, "AL_FORMAT_61CHN16") == 0) return AL_FORMAT_61CHN16; + if(strcasecmp(str, "AL_FORMAT_71CHN16") == 0) return AL_FORMAT_71CHN16; + + if(strcasecmp(str, "AL_FORMAT_MONO8") == 0) return AL_FORMAT_MONO8; + if(strcasecmp(str, "AL_FORMAT_STEREO8") == 0) return AL_FORMAT_STEREO8; + if(strcasecmp(str, "AL_FORMAT_QUAD8") == 0) return AL_FORMAT_QUAD8; + if(strcasecmp(str, "AL_FORMAT_51CHN8") == 0) return AL_FORMAT_51CHN8; + if(strcasecmp(str, "AL_FORMAT_61CHN8") == 0) return AL_FORMAT_61CHN8; + if(strcasecmp(str, "AL_FORMAT_71CHN8") == 0) return AL_FORMAT_71CHN8; + + AL_PRINT("Unknown format: \"%s\"\n", str); + return AL_FORMAT_STEREO16; +} + +/* + alcOpenDevice + + Open the Device specified. +*/ +ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) +{ + ALboolean bDeviceFound = AL_FALSE; + const ALCchar *fmt; + ALCdevice *device; + ALint i; + + if(deviceName && !deviceName[0]) + deviceName = NULL; + + device = calloc(1, sizeof(ALCdevice)); + if(!device) + { + alcSetError(NULL, ALC_OUT_OF_MEMORY); + return NULL; + } + + //Validate device + device->Connected = ALC_TRUE; + device->IsCaptureDevice = AL_FALSE; + device->LastError = ALC_NO_ERROR; + + device->Bs2b = NULL; + device->szDeviceName = NULL; + + device->Contexts = NULL; + device->NumContexts = 0; + + InitUIntMap(&device->BufferMap); + InitUIntMap(&device->EffectMap); + InitUIntMap(&device->FilterMap); + InitUIntMap(&device->DatabufferMap); + + //Set output format + device->Frequency = GetConfigValueInt(NULL, "frequency", SWMIXER_OUTPUT_RATE); + if(device->Frequency < 8000) + device->Frequency = 8000; + + fmt = GetConfigValue(NULL, "format", "AL_FORMAT_STEREO16"); + if(DecomposeDevFormat(GetFormatFromString(fmt), + &device->FmtChans, &device->FmtType) == AL_FALSE) + { + /* Should never happen... */ + device->FmtChans = DevFmtStereo; + device->FmtType = DevFmtShort; + } + + device->NumUpdates = GetConfigValueInt(NULL, "periods", 4); + if(device->NumUpdates < 2) + device->NumUpdates = 4; + + device->UpdateSize = GetConfigValueInt(NULL, "period_size", 1024); + if(device->UpdateSize <= 0) + device->UpdateSize = 1024; + + device->MaxNoOfSources = GetConfigValueInt(NULL, "sources", 256); + if((ALint)device->MaxNoOfSources <= 0) + device->MaxNoOfSources = 256; + + device->AuxiliaryEffectSlotMax = GetConfigValueInt(NULL, "slots", 4); + if((ALint)device->AuxiliaryEffectSlotMax <= 0) + device->AuxiliaryEffectSlotMax = 4; + + device->NumStereoSources = 1; + device->NumMonoSources = device->MaxNoOfSources - device->NumStereoSources; + + device->NumAuxSends = GetConfigValueInt(NULL, "sends", 1); + if(device->NumAuxSends > MAX_SENDS) + device->NumAuxSends = MAX_SENDS; + + device->Bs2bLevel = GetConfigValueInt(NULL, "cf_level", 0); + + device->DuplicateStereo = GetConfigValueBool(NULL, "stereodup", 1); + + device->HeadDampen = int2ALfp(0); + + // Find a playback device to open + SuspendContext(NULL); + for(i = 0;BackendList[i].Init;i++) + { + device->Funcs = &BackendList[i].Funcs; + if(ALCdevice_OpenPlayback(device, deviceName)) + { + device->next = g_pDeviceList; + g_pDeviceList = device; + g_ulDeviceCount++; + + bDeviceFound = AL_TRUE; + break; + } + } + ProcessContext(NULL); + + if(!bDeviceFound) + { + // No suitable output device found + alcSetError(NULL, ALC_INVALID_VALUE); + free(device); + device = NULL; + } + + return device; +} + + +/* + alcCloseDevice + + Close the specified Device +*/ +ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *pDevice) +{ + ALCdevice **list; + + if(!IsDevice(pDevice) || pDevice->IsCaptureDevice) + { + alcSetError(pDevice, ALC_INVALID_DEVICE); + return ALC_FALSE; + } + + SuspendContext(NULL); + + list = &g_pDeviceList; + while(*list != pDevice) + list = &(*list)->next; + + *list = (*list)->next; + g_ulDeviceCount--; + + ProcessContext(NULL); + + if(pDevice->NumContexts > 0) + { +#ifdef _DEBUG + AL_PRINT("alcCloseDevice(): destroying %u Context(s)\n", pDevice->NumContexts); +#endif + while(pDevice->NumContexts > 0) + alcDestroyContext(pDevice->Contexts[0]); + } + ALCdevice_ClosePlayback(pDevice); + + if(pDevice->BufferMap.size > 0) + { +#ifdef _DEBUG + AL_PRINT("alcCloseDevice(): deleting %d Buffer(s)\n", pDevice->BufferMap.size); +#endif + ReleaseALBuffers(pDevice); + } + ResetUIntMap(&pDevice->BufferMap); + + if(pDevice->EffectMap.size > 0) + { +#ifdef _DEBUG + AL_PRINT("alcCloseDevice(): deleting %d Effect(s)\n", pDevice->EffectMap.size); +#endif + ReleaseALEffects(pDevice); + } + ResetUIntMap(&pDevice->EffectMap); + + if(pDevice->FilterMap.size > 0) + { +#ifdef _DEBUG + AL_PRINT("alcCloseDevice(): deleting %d Filter(s)\n", pDevice->FilterMap.size); +#endif + ReleaseALFilters(pDevice); + } + ResetUIntMap(&pDevice->FilterMap); + + if(pDevice->DatabufferMap.size > 0) + { +#ifdef _DEBUG + AL_PRINT("alcCloseDevice(): deleting %d Databuffer(s)\n", pDevice->DatabufferMap.size); +#endif + ReleaseALDatabuffers(pDevice); + } + ResetUIntMap(&pDevice->DatabufferMap); + + free(pDevice->Bs2b); + pDevice->Bs2b = NULL; + + free(pDevice->szDeviceName); + pDevice->szDeviceName = NULL; + + free(pDevice->Contexts); + pDevice->Contexts = NULL; + + //Release device structure + memset(pDevice, 0, sizeof(ALCdevice)); + free(pDevice); + + return ALC_TRUE; +} + + +static void ReleaseALC(void) +{ + free(alcDeviceList); alcDeviceList = NULL; + alcDeviceListSize = 0; + free(alcAllDeviceList); alcAllDeviceList = NULL; + alcAllDeviceListSize = 0; + free(alcCaptureDeviceList); alcCaptureDeviceList = NULL; + alcCaptureDeviceListSize = 0; + + free(alcDefaultDeviceSpecifier); + alcDefaultDeviceSpecifier = NULL; + free(alcDefaultAllDeviceSpecifier); + alcDefaultAllDeviceSpecifier = NULL; + free(alcCaptureDefaultDeviceSpecifier); + alcCaptureDefaultDeviceSpecifier = NULL; + +#ifdef _DEBUG + if(g_ulDeviceCount > 0) + AL_PRINT("exit(): closing %u Device%s\n", g_ulDeviceCount, (g_ulDeviceCount>1)?"s":""); +#endif + + while(g_pDeviceList) + { + if(g_pDeviceList->IsCaptureDevice) + alcCaptureCloseDevice(g_pDeviceList); + else + alcCloseDevice(g_pDeviceList); + } +} + +/////////////////////////////////////////////////////// diff --git a/jni/OpenAL/Alc/ALu.c b/jni/OpenAL/Alc/ALu.c new file mode 100644 index 0000000..1f01b65 --- /dev/null +++ b/jni/OpenAL/Alc/ALu.c @@ -0,0 +1,1092 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" +#include "alSource.h" +#include "alBuffer.h" +#include "alListener.h" +#include "alAuxEffectSlot.h" +#include "alu.h" +#include "bs2b.h" + +#ifdef MAX_SOURCES_LOW +// For throttling AlSource.c +int alc_max_sources = MAX_SOURCES_LOW; +int alc_active_sources = 0; +int alc_num_cores = 0; +#endif + +static __inline ALvoid aluCrossproduct(const ALfp *inVector1, const ALfp *inVector2, ALfp *outVector) +{ + outVector[0] = (ALfpMult(inVector1[1],inVector2[2]) - ALfpMult(inVector1[2],inVector2[1])); + outVector[1] = (ALfpMult(inVector1[2],inVector2[0]) - ALfpMult(inVector1[0],inVector2[2])); + outVector[2] = (ALfpMult(inVector1[0],inVector2[1]) - ALfpMult(inVector1[1],inVector2[0])); +} + +static __inline ALfp aluDotproduct(const ALfp *inVector1, const ALfp *inVector2) +{ + return (ALfpMult(inVector1[0],inVector2[0]) + ALfpMult(inVector1[1],inVector2[1]) + + ALfpMult(inVector1[2],inVector2[2])); +} + +static __inline ALvoid aluNormalize(ALfp *inVector) +{ + ALfp length, inverse_length; + + length = aluSqrt(aluDotproduct(inVector, inVector)); + if(length != int2ALfp(0)) + { + inverse_length = ALfpDiv(int2ALfp(1),length); + inVector[0] = ALfpMult(inVector[0], inverse_length); + inVector[1] = ALfpMult(inVector[1], inverse_length); + inVector[2] = ALfpMult(inVector[2], inverse_length); + } +} + +static __inline ALvoid aluMatrixVector(ALfp *vector,ALfp w,ALfp matrix[4][4]) +{ + ALfp temp[4] = { + vector[0], vector[1], vector[2], w + }; + + vector[0] = ALfpMult(temp[0],matrix[0][0]) + ALfpMult(temp[1],matrix[1][0]) + ALfpMult(temp[2],matrix[2][0]) + ALfpMult(temp[3],matrix[3][0]); + vector[1] = ALfpMult(temp[0],matrix[0][1]) + ALfpMult(temp[1],matrix[1][1]) + ALfpMult(temp[2],matrix[2][1]) + ALfpMult(temp[3],matrix[3][1]); + vector[2] = ALfpMult(temp[0],matrix[0][2]) + ALfpMult(temp[1],matrix[1][2]) + ALfpMult(temp[2],matrix[2][2]) + ALfpMult(temp[3],matrix[3][2]); +} + + +ALvoid CalcNonAttnSourceParams(ALsource *ALSource, const ALCcontext *ALContext) +{ + ALfp SourceVolume,ListenerGain,MinVolume,MaxVolume; + ALbufferlistitem *BufferListItem; + enum DevFmtChannels DevChans; + enum FmtChannels Channels; + ALfp DryGain, DryGainHF; + ALfp WetGain[MAX_SENDS]; + ALfp WetGainHF[MAX_SENDS]; + ALint NumSends, Frequency; + ALboolean DupStereo; + ALfp Pitch; + ALfp cw; + ALint i; + + /* Get device properties */ + DevChans = ALContext->Device->FmtChans; + DupStereo = ALContext->Device->DuplicateStereo; + NumSends = ALContext->Device->NumAuxSends; + Frequency = ALContext->Device->Frequency; + + /* Get listener properties */ + ListenerGain = ALContext->Listener.Gain; + + /* Get source properties */ + SourceVolume = ALSource->flGain; + MinVolume = ALSource->flMinGain; + MaxVolume = ALSource->flMaxGain; + Pitch = ALSource->flPitch; + + /* Calculate the stepping value */ + Channels = FmtMono; + BufferListItem = ALSource->queue; + while(BufferListItem != NULL) + { + ALbuffer *ALBuffer; + if((ALBuffer=BufferListItem->buffer) != NULL) + { + ALint maxstep = STACK_DATA_SIZE / FrameSizeFromFmt(ALBuffer->FmtChannels, + ALBuffer->FmtType); + maxstep -= ResamplerPadding[ALSource->Resampler] + + ResamplerPrePadding[ALSource->Resampler] + 1; + maxstep = min(maxstep, INT_MAX>>FRACTIONBITS); + + Pitch = ALfpDiv(ALfpMult(Pitch, int2ALfp(ALBuffer->Frequency)), int2ALfp(Frequency)); + if(Pitch > int2ALfp(maxstep)) + ALSource->Params.Step = maxstep<Params.Step = ALfp2int(ALfpMult(Pitch, int2ALfp(FRACTIONONE))); + if(ALSource->Params.Step == 0) + ALSource->Params.Step = 1; + } + + Channels = ALBuffer->FmtChannels; + break; + } + BufferListItem = BufferListItem->next; + } + + /* Calculate gains */ + DryGain = SourceVolume; + DryGain = __min(DryGain,MaxVolume); + DryGain = __max(DryGain,MinVolume); + DryGainHF = int2ALfp(1); + + switch(ALSource->DirectFilter.type) + { + case AL_FILTER_LOWPASS: + DryGain = ALfpMult(DryGain, ALSource->DirectFilter.Gain); + DryGainHF = ALfpMult(DryGainHF, ALSource->DirectFilter.GainHF); + break; + } + + for(i = 0;i < MAXCHANNELS;i++) + { + ALuint i2; + for(i2 = 0;i2 < MAXCHANNELS;i2++) + ALSource->Params.DryGains[i][i2] = int2ALfp(0); + } + + switch(Channels) + { + case FmtMono: + ALSource->Params.DryGains[0][FRONT_CENTER] = ALfpMult(DryGain, ListenerGain); + break; + case FmtStereo: + if(DupStereo == AL_FALSE) + { + ALSource->Params.DryGains[0][FRONT_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][FRONT_RIGHT] = ALfpMult(DryGain, ListenerGain); + } + else + { + switch(DevChans) + { + case DevFmtMono: + case DevFmtStereo: + ALSource->Params.DryGains[0][FRONT_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][FRONT_RIGHT] = ALfpMult(DryGain, ListenerGain); + break; + + case DevFmtQuad: + case DevFmtX51: + DryGain = ALfpMult(DryGain, aluSqrt(float2ALfp(2.0f/4.0f))); + ALSource->Params.DryGains[0][FRONT_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][FRONT_RIGHT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[0][BACK_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][BACK_RIGHT] = ALfpMult(DryGain, ListenerGain); + break; + + case DevFmtX61: + DryGain = ALfpMult(DryGain, aluSqrt(float2ALfp(2.0f/4.0f))); + ALSource->Params.DryGains[0][FRONT_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][FRONT_RIGHT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[0][SIDE_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][SIDE_RIGHT] = ALfpMult(DryGain, ListenerGain); + break; + + case DevFmtX71: + DryGain = ALfpMult(DryGain, aluSqrt(float2ALfp(2.0f/6.0f))); + ALSource->Params.DryGains[0][FRONT_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][FRONT_RIGHT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[0][BACK_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][BACK_RIGHT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[0][SIDE_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][SIDE_RIGHT] = ALfpMult(DryGain, ListenerGain); + break; + } + } + break; + + case FmtRear: + ALSource->Params.DryGains[0][BACK_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][BACK_RIGHT] = ALfpMult(DryGain, ListenerGain); + break; + + case FmtQuad: + ALSource->Params.DryGains[0][FRONT_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][FRONT_RIGHT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[2][BACK_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[3][BACK_RIGHT] = ALfpMult(DryGain, ListenerGain); + break; + + case FmtX51: + ALSource->Params.DryGains[0][FRONT_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][FRONT_RIGHT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[2][FRONT_CENTER] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[3][LFE] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[4][BACK_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[5][BACK_RIGHT] = ALfpMult(DryGain, ListenerGain); + break; + + case FmtX61: + ALSource->Params.DryGains[0][FRONT_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][FRONT_RIGHT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[2][FRONT_CENTER] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[3][LFE] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[4][BACK_CENTER] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[5][SIDE_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[6][SIDE_RIGHT] = ALfpMult(DryGain, ListenerGain); + break; + + case FmtX71: + ALSource->Params.DryGains[0][FRONT_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[1][FRONT_RIGHT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[2][FRONT_CENTER] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[3][LFE] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[4][BACK_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[5][BACK_RIGHT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[6][SIDE_LEFT] = ALfpMult(DryGain, ListenerGain); + ALSource->Params.DryGains[7][SIDE_RIGHT] = ALfpMult(DryGain, ListenerGain); + break; + } + + for(i = 0;i < NumSends;i++) + { + WetGain[i] = SourceVolume; + WetGain[i] = __min(WetGain[i],MaxVolume); + WetGain[i] = __max(WetGain[i],MinVolume); + WetGainHF[i] = int2ALfp(1); + + switch(ALSource->Send[i].WetFilter.type) + { + case AL_FILTER_LOWPASS: + WetGain[i] = ALfpMult(WetGain[i], ALSource->Send[i].WetFilter.Gain); + WetGainHF[i] = ALfpMult(WetGainHF[i], ALSource->Send[i].WetFilter.GainHF); + break; + } + + ALSource->Params.Send[i].WetGain = ALfpMult(WetGain[i], ListenerGain); + } + + /* Update filter coefficients. Calculations based on the I3DL2 + * spec. */ + cw = float2ALfp(cos(2.0*M_PI * LOWPASSFREQCUTOFF / Frequency)); + + /* We use two chained one-pole filters, so we need to take the + * square root of the squared gain, which is the same as the base + * gain. */ + ALSource->Params.iirFilter.coeff = lpCoeffCalc(DryGainHF, cw); + + for(i = 0;i < NumSends;i++) + { + /* We use a one-pole filter, so we need to take the squared gain */ + ALfp a = lpCoeffCalc(ALfpMult(WetGainHF[i],WetGainHF[i]), cw); + ALSource->Params.Send[i].iirFilter.coeff = a; + } +} + +ALvoid CalcSourceParams(ALsource *ALSource, const ALCcontext *ALContext) +{ + const ALCdevice *Device = ALContext->Device; + ALfp InnerAngle,OuterAngle,Angle,Distance,OrigDist; + ALfp Direction[3],Position[3],SourceToListener[3]; + ALfp Velocity[3],ListenerVel[3]; + ALfp MinVolume,MaxVolume,MinDist,MaxDist,Rolloff,OuterGainHF; + ALfp ConeVolume,ConeHF,SourceVolume,ListenerGain; + ALfp DopplerFactor, DopplerVelocity, SpeedOfSound; + ALfp AirAbsorptionFactor; + ALbufferlistitem *BufferListItem; + ALfp Attenuation, EffectiveDist; + ALfp RoomAttenuation[MAX_SENDS]; + ALfp MetersPerUnit; + ALfp RoomRolloff[MAX_SENDS]; + ALfp DryGain; + ALfp DryGainHF; + ALfp WetGain[MAX_SENDS]; + ALfp WetGainHF[MAX_SENDS]; + ALfp DirGain, AmbientGain; + const ALfp *SpeakerGain; + ALfp Pitch; + ALfp length; + ALuint Frequency; + ALint NumSends; + ALint pos, s, i; + ALfp cw; + + DryGainHF = int2ALfp(1); + for(i = 0;i < MAX_SENDS;i++) + WetGainHF[i] = int2ALfp(1); + + //Get context properties + DopplerFactor = ALfpMult(ALContext->DopplerFactor, ALSource->DopplerFactor); + DopplerVelocity = ALContext->DopplerVelocity; + SpeedOfSound = ALContext->flSpeedOfSound; + NumSends = Device->NumAuxSends; + Frequency = Device->Frequency; + + //Get listener properties + ListenerGain = ALContext->Listener.Gain; + MetersPerUnit = ALContext->Listener.MetersPerUnit; + memcpy(ListenerVel, ALContext->Listener.Velocity, sizeof(ALContext->Listener.Velocity)); + + //Get source properties + SourceVolume = ALSource->flGain; + memcpy(Position, ALSource->vPosition, sizeof(ALSource->vPosition)); + memcpy(Direction, ALSource->vOrientation, sizeof(ALSource->vOrientation)); + memcpy(Velocity, ALSource->vVelocity, sizeof(ALSource->vVelocity)); + MinVolume = ALSource->flMinGain; + MaxVolume = ALSource->flMaxGain; + MinDist = ALSource->flRefDistance; + MaxDist = ALSource->flMaxDistance; + Rolloff = ALSource->flRollOffFactor; + InnerAngle = ALSource->flInnerAngle; + OuterAngle = ALSource->flOuterAngle; + OuterGainHF = ALSource->OuterGainHF; + AirAbsorptionFactor = ALSource->AirAbsorptionFactor; + + //1. Translate Listener to origin (convert to head relative) + if(ALSource->bHeadRelative == AL_FALSE) + { + ALfp U[3],V[3],N[3]; + ALfp Matrix[4][4]; + + // Build transform matrix + memcpy(N, ALContext->Listener.Forward, sizeof(N)); // At-vector + aluNormalize(N); // Normalized At-vector + memcpy(V, ALContext->Listener.Up, sizeof(V)); // Up-vector + aluNormalize(V); // Normalized Up-vector + aluCrossproduct(N, V, U); // Right-vector + aluNormalize(U); // Normalized Right-vector + Matrix[0][0] = U[0]; Matrix[0][1] = V[0]; Matrix[0][2] = -1*N[0]; Matrix[0][3] = int2ALfp(0); + Matrix[1][0] = U[1]; Matrix[1][1] = V[1]; Matrix[1][2] = -1*N[1]; Matrix[1][3] = int2ALfp(0); + Matrix[2][0] = U[2]; Matrix[2][1] = V[2]; Matrix[2][2] = -1*N[2]; Matrix[2][3] = int2ALfp(0); + Matrix[3][0] = int2ALfp(0); Matrix[3][1] = int2ALfp(0); Matrix[3][2] = int2ALfp(0); Matrix[3][3] = int2ALfp(1); + + // Translate position + Position[0] -= ALContext->Listener.Position[0]; + Position[1] -= ALContext->Listener.Position[1]; + Position[2] -= ALContext->Listener.Position[2]; + + // Transform source position and direction into listener space + aluMatrixVector(Position, int2ALfp(1), Matrix); + aluMatrixVector(Direction, int2ALfp(0), Matrix); + // Transform source and listener velocity into listener space + aluMatrixVector(Velocity, int2ALfp(0), Matrix); + aluMatrixVector(ListenerVel, int2ALfp(0), Matrix); + } + else + ListenerVel[0] = ListenerVel[1] = ListenerVel[2] = int2ALfp(0); + + SourceToListener[0] = -1*Position[0]; + SourceToListener[1] = -1*Position[1]; + SourceToListener[2] = -1*Position[2]; + aluNormalize(SourceToListener); + aluNormalize(Direction); + + //2. Calculate distance attenuation + Distance = aluSqrt(aluDotproduct(Position, Position)); + OrigDist = Distance; + + Attenuation = int2ALfp(1); + for(i = 0;i < NumSends;i++) + { + RoomAttenuation[i] = int2ALfp(1); + + RoomRolloff[i] = ALSource->RoomRolloffFactor; + if(ALSource->Send[i].Slot && + (ALSource->Send[i].Slot->effect.type == AL_EFFECT_REVERB || + ALSource->Send[i].Slot->effect.type == AL_EFFECT_EAXREVERB)) + RoomRolloff[i] += ALSource->Send[i].Slot->effect.Reverb.RoomRolloffFactor; + } + + switch(ALContext->SourceDistanceModel ? ALSource->DistanceModel : + ALContext->DistanceModel) + { + case AL_INVERSE_DISTANCE_CLAMPED: + Distance=__max(Distance,MinDist); + Distance=__min(Distance,MaxDist); + if(MaxDist < MinDist) + break; + //fall-through + case AL_INVERSE_DISTANCE: + if(MinDist > int2ALfp(0)) + { + if((MinDist + ALfpMult(Rolloff, (Distance - MinDist))) > int2ALfp(0)) + Attenuation = ALfpDiv(MinDist, (MinDist + ALfpMult(Rolloff, (Distance - MinDist)))); + for(i = 0;i < NumSends;i++) + { + if((MinDist + ALfpMult(RoomRolloff[i], (Distance - MinDist))) > int2ALfp(0)) + RoomAttenuation[i] = ALfpDiv(MinDist, (MinDist + ALfpMult(RoomRolloff[i], (Distance - MinDist)))); + } + } + break; + + case AL_LINEAR_DISTANCE_CLAMPED: + Distance=__max(Distance,MinDist); + Distance=__min(Distance,MaxDist); + if(MaxDist < MinDist) + break; + //fall-through + case AL_LINEAR_DISTANCE: + if(MaxDist != MinDist) + { + Attenuation = int2ALfp(1) - ALfpDiv(ALfpMult(Rolloff,(Distance-MinDist)), (MaxDist - MinDist)); + Attenuation = __max(Attenuation, int2ALfp(0)); + for(i = 0;i < NumSends;i++) + { + RoomAttenuation[i] = int2ALfp(1) - ALfpDiv(ALfpMult(RoomRolloff[i],(Distance-MinDist)),(MaxDist - MinDist)); + RoomAttenuation[i] = __max(RoomAttenuation[i], int2ALfp(0)); + } + } + break; + + case AL_EXPONENT_DISTANCE_CLAMPED: + Distance=__max(Distance,MinDist); + Distance=__min(Distance,MaxDist); + if(MaxDist < MinDist) + break; + //fall-through + case AL_EXPONENT_DISTANCE: + if(Distance > int2ALfp(0) && MinDist > int2ALfp(0)) + { + Attenuation = aluPow(ALfpDiv(Distance,MinDist), (-1*Rolloff)); + for(i = 0;i < NumSends;i++) + RoomAttenuation[i] = aluPow(ALfpDiv(Distance,MinDist), (-1*RoomRolloff[i])); + } + break; + + case AL_NONE: + break; + } + + // Source Gain + Attenuation + DryGain = ALfpMult(SourceVolume, Attenuation); + for(i = 0;i < NumSends;i++) + WetGain[i] = ALfpMult(SourceVolume, RoomAttenuation[i]); + + EffectiveDist = int2ALfp(0); + if(MinDist > int2ALfp(0) && Attenuation < int2ALfp(1)) + EffectiveDist = ALfpMult((ALfpDiv(MinDist,Attenuation) - MinDist),MetersPerUnit); + + // Distance-based air absorption + if(AirAbsorptionFactor > int2ALfp(0) && EffectiveDist > int2ALfp(0)) + { + ALfp absorb; + + // Absorption calculation is done in dB + absorb = ALfpMult(ALfpMult(AirAbsorptionFactor,float2ALfp(AIRABSORBGAINDBHF)), + EffectiveDist); + // Convert dB to linear gain before applying + absorb = aluPow(int2ALfp(10), ALfpDiv(absorb,int2ALfp(20))); + + DryGainHF = ALfpMult(DryGainHF,absorb); + } + + //3. Apply directional soundcones + Angle = ALfpMult(aluAcos(aluDotproduct(Direction,SourceToListener)), float2ALfp(180.0f/M_PI)); + if(Angle >= InnerAngle && Angle <= OuterAngle) + { + ALfp scale; scale = ALfpDiv((Angle-InnerAngle), (OuterAngle-InnerAngle)); + ConeVolume = int2ALfp(1) + ALfpMult((ALSource->flOuterGain - int2ALfp(1)),scale); + ConeHF = (int2ALfp(1)+ALfpMult((OuterGainHF-int2ALfp(1)),scale)); + } + else if(Angle > OuterAngle) + { + ConeVolume = (int2ALfp(1)+(ALSource->flOuterGain-int2ALfp(1))); + ConeHF = (int2ALfp(1)+(OuterGainHF-int2ALfp(1))); + } + else + { + ConeVolume = int2ALfp(1); + ConeHF = int2ALfp(1); + } + + // Apply some high-frequency attenuation for sources behind the listener + // NOTE: This should be aluDotproduct({0,0,-1}, ListenerToSource), however + // that is equivalent to aluDotproduct({0,0,1}, SourceToListener), which is + // the same as SourceToListener[2] + Angle = ALfpMult(aluAcos(SourceToListener[2]), float2ALfp(180.0f/M_PI)); + // Sources within the minimum distance attenuate less + if(OrigDist < MinDist) + Angle = ALfpMult(Angle, ALfpDiv(OrigDist,MinDist)); + if(Angle > int2ALfp(90)) + { + ALfp scale; scale = ALfpDiv((Angle-int2ALfp(90)), float2ALfp(180.1f-90.0f)); // .1 to account for fp errors + ConeHF = ALfpMult(ConeHF, (int2ALfp(1) - ALfpMult(Device->HeadDampen,scale))); + } + + DryGain = ALfpMult(DryGain, ConeVolume); + if(ALSource->DryGainHFAuto) + DryGainHF = ALfpMult(DryGainHF, ConeHF); + + // Clamp to Min/Max Gain + DryGain = __min(DryGain,MaxVolume); + DryGain = __max(DryGain,MinVolume); + + for(i = 0;i < NumSends;i++) + { + ALeffectslot *Slot = ALSource->Send[i].Slot; + + if(!Slot || Slot->effect.type == AL_EFFECT_NULL) + { + ALSource->Params.Send[i].WetGain = int2ALfp(0); + WetGainHF[i] = int2ALfp(1); + continue; + } + + if(Slot->AuxSendAuto) + { + if(ALSource->WetGainAuto) + WetGain[i] = ALfpMult(WetGain[i], ConeVolume); + if(ALSource->WetGainHFAuto) + WetGainHF[i] = ALfpMult(WetGainHF[i], ConeHF); + + // Clamp to Min/Max Gain + WetGain[i] = __min(WetGain[i],MaxVolume); + WetGain[i] = __max(WetGain[i],MinVolume); + + if(Slot->effect.type == AL_EFFECT_REVERB || + Slot->effect.type == AL_EFFECT_EAXREVERB) + { + /* Apply a decay-time transformation to the wet path, based on + * the attenuation of the dry path. + * + * Using the approximate (effective) source to listener + * distance, the initial decay of the reverb effect is + * calculated and applied to the wet path. + */ + WetGain[i] = ALfpMult(WetGain[i], + aluPow(int2ALfp(10), + ALfpDiv(ALfpMult(ALfpDiv(EffectiveDist, + ALfpMult(float2ALfp(SPEEDOFSOUNDMETRESPERSEC), Slot->effect.Reverb.DecayTime)), + int2ALfp(-60)), + int2ALfp(20)))); + + WetGainHF[i] = ALfpMult(WetGainHF[i], + aluPow(Slot->effect.Reverb.AirAbsorptionGainHF, + ALfpMult(AirAbsorptionFactor, EffectiveDist))); + } + } + else + { + /* If the slot's auxiliary send auto is off, the data sent to the + * effect slot is the same as the dry path, sans filter effects */ + WetGain[i] = DryGain; + WetGainHF[i] = DryGainHF; + } + + switch(ALSource->Send[i].WetFilter.type) + { + case AL_FILTER_LOWPASS: + WetGain[i] = ALfpMult(WetGain[i], ALSource->Send[i].WetFilter.Gain); + WetGainHF[i] = ALfpMult(WetGainHF[i], ALSource->Send[i].WetFilter.GainHF); + break; + } + ALSource->Params.Send[i].WetGain = ALfpMult(WetGain[i], ListenerGain); + } + + // Apply filter gains and filters + switch(ALSource->DirectFilter.type) + { + case AL_FILTER_LOWPASS: + DryGain = ALfpMult(DryGain, ALSource->DirectFilter.Gain); + DryGainHF = ALfpMult(DryGainHF, ALSource->DirectFilter.GainHF); + break; + } + DryGain = ALfpMult(DryGain, ListenerGain); + + // Calculate Velocity + Pitch = ALSource->flPitch; + if(DopplerFactor != int2ALfp(0)) + { + ALfp VSS, VLS; + ALfp MaxVelocity; MaxVelocity = ALfpDiv(ALfpMult(SpeedOfSound,DopplerVelocity), + DopplerFactor); + + VSS = aluDotproduct(Velocity, SourceToListener); + if(VSS >= MaxVelocity) + VSS = (MaxVelocity - int2ALfp(1)); + else if(VSS <= -MaxVelocity) + VSS = (-MaxVelocity + int2ALfp(1)); + + VLS = aluDotproduct(ListenerVel, SourceToListener); + if(VLS >= MaxVelocity) + VLS = (MaxVelocity - int2ALfp(1)); + else if(VLS <= -MaxVelocity) + VLS = -MaxVelocity + int2ALfp(1); + + Pitch = ALfpMult(Pitch, + ALfpDiv((ALfpMult(SpeedOfSound,DopplerVelocity) - ALfpMult(DopplerFactor,VLS)), + (ALfpMult(SpeedOfSound,DopplerVelocity) - ALfpMult(DopplerFactor,VSS)))); + } + + BufferListItem = ALSource->queue; + while(BufferListItem != NULL) + { + ALbuffer *ALBuffer; + if((ALBuffer=BufferListItem->buffer) != NULL) + { + ALint maxstep = STACK_DATA_SIZE / FrameSizeFromFmt(ALBuffer->FmtChannels, + ALBuffer->FmtType); + maxstep -= ResamplerPadding[ALSource->Resampler] + + ResamplerPrePadding[ALSource->Resampler] + 1; + maxstep = min(maxstep, INT_MAX>>FRACTIONBITS); + + Pitch = ALfpDiv(ALfpMult(Pitch, int2ALfp(ALBuffer->Frequency)), int2ALfp(Frequency)); + if(Pitch > int2ALfp(maxstep)) + ALSource->Params.Step = maxstep<Params.Step = ALfp2int(ALfpMult(Pitch,float2ALfp(FRACTIONONE))); + if(ALSource->Params.Step == 0) + ALSource->Params.Step = 1; + } + break; + } + BufferListItem = BufferListItem->next; + } + + // Use energy-preserving panning algorithm for multi-speaker playback + length = __max(OrigDist, MinDist); + if(length > int2ALfp(0)) + { + ALfp invlen = ALfpDiv(int2ALfp(1), length); + Position[0] = ALfpMult(Position[0],invlen); + Position[1] = ALfpMult(Position[1],invlen); + Position[2] = ALfpMult(Position[2],invlen); + } + + pos = aluCart2LUTpos((-1*Position[2]), Position[0]); + SpeakerGain = &Device->PanningLUT[MAXCHANNELS * pos]; + + DirGain = aluSqrt((ALfpMult(Position[0],Position[0]) + ALfpMult(Position[2],Position[2]))); + // elevation adjustment for directional gain. this sucks, but + // has low complexity + AmbientGain = aluSqrt(float2ALfp(1.0f/Device->NumChan)); + for(s = 0;s < MAXCHANNELS;s++) + { + ALuint s2; + for(s2 = 0;s2 < MAXCHANNELS;s2++) + ALSource->Params.DryGains[s][s2] = int2ALfp(0); + } + for(s = 0;s < (ALsizei)Device->NumChan;s++) + { + Channel chan = Device->Speaker2Chan[s]; + ALfp gain; gain = AmbientGain + ALfpMult((SpeakerGain[chan]-AmbientGain),DirGain); + ALSource->Params.DryGains[0][chan] = ALfpMult(DryGain, gain); + } + + /* Update filter coefficients. */ + cw = __cos(ALfpDiv(float2ALfp(2.0*M_PI*LOWPASSFREQCUTOFF), int2ALfp(Frequency))); + + /* Spatialized sources use four chained one-pole filters, so we need to + * take the fourth root of the squared gain, which is the same as the + * square root of the base gain. */ + ALSource->Params.iirFilter.coeff = lpCoeffCalc(aluSqrt(DryGainHF), cw); + + for(i = 0;i < NumSends;i++) + { + /* The wet path uses two chained one-pole filters, so take the + * base gain (square root of the squared gain) */ + ALSource->Params.Send[i].iirFilter.coeff = lpCoeffCalc(WetGainHF[i], cw); + } +} + + +static __inline ALfloat aluF2F(ALfp val) +{ + return ALfp2float(val); +} +static __inline ALushort aluF2US(ALfp val) +{ + if(val > int2ALfp(1)) return 65535; + if(val < int2ALfp(-1)) return 0; + return (ALushort)(ALfp2int(ALfpMult(val,int2ALfp(32767))) + 32768); +} +static __inline ALshort aluF2S(ALfp val) +{ + if(val > int2ALfp(1)) return 32767; + if(val < int2ALfp(-1)) return -32768; + return (ALshort)(ALfp2int(ALfpMult(val,int2ALfp(32767)))); +} +static __inline ALubyte aluF2UB(ALfp val) +{ + ALushort i = aluF2US(val); + return i>>8; +} +static __inline ALbyte aluF2B(ALfp val) +{ + ALshort i = aluF2S(val); + return i>>8; +} + +static const Channel MonoChans[] = { FRONT_CENTER }; +static const Channel StereoChans[] = { FRONT_LEFT, FRONT_RIGHT }; +static const Channel QuadChans[] = { FRONT_LEFT, FRONT_RIGHT, + BACK_LEFT, BACK_RIGHT }; +static const Channel X51Chans[] = { FRONT_LEFT, FRONT_RIGHT, + FRONT_CENTER, LFE, + BACK_LEFT, BACK_RIGHT }; +static const Channel X61Chans[] = { FRONT_LEFT, FRONT_LEFT, + FRONT_CENTER, LFE, BACK_CENTER, + SIDE_LEFT, SIDE_RIGHT }; +static const Channel X71Chans[] = { FRONT_LEFT, FRONT_RIGHT, + FRONT_CENTER, LFE, + BACK_LEFT, BACK_RIGHT, + SIDE_LEFT, SIDE_RIGHT }; + +#define DECL_TEMPLATE(T, chans,N, func) \ +static void Write_##T##_##chans(ALCdevice *device, T *buffer, ALuint SamplesToDo)\ +{ \ + ALfp (*DryBuffer)[MAXCHANNELS] = device->DryBuffer; \ + ALfp (*Matrix)[MAXCHANNELS] = device->ChannelMatrix; \ + const ALuint *ChanMap = device->DevChannels; \ + ALuint i, j, c; \ + \ + for(i = 0;i < SamplesToDo;i++) \ + { \ + for(j = 0;j < N;j++) \ + { \ + ALfp samp; samp = int2ALfp(0); \ + for(c = 0;c < MAXCHANNELS;c++) { \ + ALfp m = Matrix[c][chans[j]]; \ + if (m != 0) \ + samp += ALfpMult(DryBuffer[i][c], m); \ + } \ + ((T*)buffer)[ChanMap[chans[j]]] = func(samp); \ + } \ + buffer = ((T*)buffer) + N; \ + } \ +} + +DECL_TEMPLATE(ALfloat, MonoChans,1, aluF2F) +DECL_TEMPLATE(ALfloat, QuadChans,4, aluF2F) +DECL_TEMPLATE(ALfloat, X51Chans,6, aluF2F) +DECL_TEMPLATE(ALfloat, X61Chans,7, aluF2F) +DECL_TEMPLATE(ALfloat, X71Chans,8, aluF2F) + +DECL_TEMPLATE(ALushort, MonoChans,1, aluF2US) +DECL_TEMPLATE(ALushort, QuadChans,4, aluF2US) +DECL_TEMPLATE(ALushort, X51Chans,6, aluF2US) +DECL_TEMPLATE(ALushort, X61Chans,7, aluF2US) +DECL_TEMPLATE(ALushort, X71Chans,8, aluF2US) + +DECL_TEMPLATE(ALshort, MonoChans,1, aluF2S) +DECL_TEMPLATE(ALshort, QuadChans,4, aluF2S) +DECL_TEMPLATE(ALshort, X51Chans,6, aluF2S) +DECL_TEMPLATE(ALshort, X61Chans,7, aluF2S) +DECL_TEMPLATE(ALshort, X71Chans,8, aluF2S) + +DECL_TEMPLATE(ALubyte, MonoChans,1, aluF2UB) +DECL_TEMPLATE(ALubyte, QuadChans,4, aluF2UB) +DECL_TEMPLATE(ALubyte, X51Chans,6, aluF2UB) +DECL_TEMPLATE(ALubyte, X61Chans,7, aluF2UB) +DECL_TEMPLATE(ALubyte, X71Chans,8, aluF2UB) + +DECL_TEMPLATE(ALbyte, MonoChans,1, aluF2B) +DECL_TEMPLATE(ALbyte, QuadChans,4, aluF2B) +DECL_TEMPLATE(ALbyte, X51Chans,6, aluF2B) +DECL_TEMPLATE(ALbyte, X61Chans,7, aluF2B) +DECL_TEMPLATE(ALbyte, X71Chans,8, aluF2B) + +#undef DECL_TEMPLATE + +#define DECL_TEMPLATE(T, chans,N, func) \ +static void Write_##T##_##chans(ALCdevice *device, T *buffer, ALuint SamplesToDo)\ +{ \ + ALfp (*DryBuffer)[MAXCHANNELS] = device->DryBuffer; \ + ALfp (*Matrix)[MAXCHANNELS] = device->ChannelMatrix; \ + const ALuint *ChanMap = device->DevChannels; \ + ALuint i, j, c; \ + \ + if(device->Bs2b) \ + { \ + for(i = 0;i < SamplesToDo;i++) \ + { \ + ALfp samples[2] = { int2ALfp(0), int2ALfp(0) }; \ + for(c = 0;c < MAXCHANNELS;c++) \ + { \ + samples[0] += ALfpMult(DryBuffer[i][c],Matrix[c][FRONT_LEFT]); \ + samples[1] += ALfpMult(DryBuffer[i][c],Matrix[c][FRONT_RIGHT]); \ + } \ + bs2b_cross_feed(device->Bs2b, samples); \ + ((T*)buffer)[ChanMap[FRONT_LEFT]] = func(samples[0]); \ + ((T*)buffer)[ChanMap[FRONT_RIGHT]] = func(samples[1]); \ + buffer = ((T*)buffer) + 2; \ + } \ + } \ + else \ + { \ + for(i = 0;i < SamplesToDo;i++) \ + { \ + for(j = 0;j < N;j++) \ + { \ + ALfp samp = int2ALfp(0); \ + for(c = 0;c < MAXCHANNELS;c++) \ + samp += ALfpMult(DryBuffer[i][c], Matrix[c][chans[j]]); \ + ((T*)buffer)[ChanMap[chans[j]]] = func(samp); \ + } \ + buffer = ((T*)buffer) + N; \ + } \ + } \ +} + +DECL_TEMPLATE(ALfloat, StereoChans,2, aluF2F) +DECL_TEMPLATE(ALushort, StereoChans,2, aluF2US) +DECL_TEMPLATE(ALshort, StereoChans,2, aluF2S) +DECL_TEMPLATE(ALubyte, StereoChans,2, aluF2UB) +DECL_TEMPLATE(ALbyte, StereoChans,2, aluF2B) + +#undef DECL_TEMPLATE + +#define DECL_TEMPLATE(T, func) \ +static void Write_##T(ALCdevice *device, T *buffer, ALuint SamplesToDo) \ +{ \ + switch(device->FmtChans) \ + { \ + case DevFmtMono: \ + Write_##T##_MonoChans(device, buffer, SamplesToDo); \ + break; \ + case DevFmtStereo: \ + Write_##T##_StereoChans(device, buffer, SamplesToDo); \ + break; \ + case DevFmtQuad: \ + Write_##T##_QuadChans(device, buffer, SamplesToDo); \ + break; \ + case DevFmtX51: \ + Write_##T##_X51Chans(device, buffer, SamplesToDo); \ + break; \ + case DevFmtX61: \ + Write_##T##_X61Chans(device, buffer, SamplesToDo); \ + break; \ + case DevFmtX71: \ + Write_##T##_X71Chans(device, buffer, SamplesToDo); \ + break; \ + } \ +} + +DECL_TEMPLATE(ALfloat, aluF2F) +DECL_TEMPLATE(ALushort, aluF2US) +DECL_TEMPLATE(ALshort, aluF2S) +DECL_TEMPLATE(ALubyte, aluF2UB) +DECL_TEMPLATE(ALbyte, aluF2B) + +#undef DECL_TEMPLATE + +static __inline ALvoid aluMixDataPrivate(ALCdevice *device, ALvoid *buffer, ALsizei size) +{ + ALuint SamplesToDo; + ALeffectslot *ALEffectSlot; + ALCcontext **ctx, **ctx_end; + ALsource **src, **src_end; + int fpuState; + ALuint i, c; + ALsizei e; + +#if defined(HAVE_FESETROUND) + fpuState = fegetround(); + fesetround(FE_TOWARDZERO); +#elif defined(HAVE__CONTROLFP) + fpuState = _controlfp(_RC_CHOP, _MCW_RC); +#else + (void)fpuState; +#endif + + while(size > 0) + { + /* Setup variables */ + SamplesToDo = min(size, BUFFERSIZE); + + /* Clear mixing buffer */ + memset(device->DryBuffer, 0, SamplesToDo*MAXCHANNELS*sizeof(ALfp)); + + SuspendContext(NULL); + ctx = device->Contexts; + ctx_end = ctx + device->NumContexts; + while(ctx != ctx_end) + { + SuspendContext(*ctx); + + src = (*ctx)->ActiveSources; + src_end = src + (*ctx)->ActiveSourceCount; + while(src != src_end) + { + if((*src)->state != AL_PLAYING) + { + --((*ctx)->ActiveSourceCount); + *src = *(--src_end); + continue; + } + + if((*src)->NeedsUpdate) + { + ALsource_Update(*src, *ctx); + (*src)->NeedsUpdate = AL_FALSE; + } + + MixSource(*src, device, SamplesToDo); + src++; + } + + /* effect slot processing */ + for(e = 0;e < (*ctx)->EffectSlotMap.size;e++) + { + ALEffectSlot = (*ctx)->EffectSlotMap.array[e].value; + + for(i = 0;i < SamplesToDo;i++) + { + ALEffectSlot->ClickRemoval[0] -= ALfpDiv(ALEffectSlot->ClickRemoval[0], int2ALfp(256)); + ALEffectSlot->WetBuffer[i] += ALEffectSlot->ClickRemoval[0]; + } + for(i = 0;i < 1;i++) + { + ALEffectSlot->ClickRemoval[i] += ALEffectSlot->PendingClicks[i]; + ALEffectSlot->PendingClicks[i] = int2ALfp(0); + } + + ALEffect_Process(ALEffectSlot->EffectState, ALEffectSlot, + SamplesToDo, ALEffectSlot->WetBuffer, + device->DryBuffer); + + for(i = 0;i < SamplesToDo;i++) + ALEffectSlot->WetBuffer[i] = int2ALfp(0); + } + + ProcessContext(*ctx); + ctx++; + } + ProcessContext(NULL); + + //Post processing loop + for(i = 0;i < SamplesToDo;i++) + { + for(c = 0;c < MAXCHANNELS;c++) + { + device->ClickRemoval[c] -= ALfpDiv(device->ClickRemoval[c], int2ALfp(256)); + device->DryBuffer[i][c] += device->ClickRemoval[c]; + } + } + for(i = 0;i < MAXCHANNELS;i++) + { + device->ClickRemoval[i] += device->PendingClicks[i]; + device->PendingClicks[i] = int2ALfp(0); + } + + switch(device->FmtType) + { + case DevFmtByte: + Write_ALbyte(device, buffer, SamplesToDo); + break; + case DevFmtUByte: + Write_ALubyte(device, buffer, SamplesToDo); + break; + case DevFmtShort: + Write_ALshort(device, buffer, SamplesToDo); + break; + case DevFmtUShort: + Write_ALushort(device, buffer, SamplesToDo); + break; + case DevFmtFloat: + Write_ALfloat(device, buffer, SamplesToDo); + break; + } + + size -= SamplesToDo; + } + +#if defined(HAVE_FESETROUND) + fesetround(fpuState); +#elif defined(HAVE__CONTROLFP) + _controlfp(fpuState, _MCW_RC); +#endif +} + +ALvoid aluMixData(ALCdevice *device, ALvoid *buffer, ALsizei size) +{ +#ifdef MAX_SOURCES_LOW + // Profile aluMixDataPrivate to set admission control parameters + clock_t ts_start; + clock_t ts_end; + clock_t ts_diff; + int time_per_source; + int max_sources_within_deadline; + int mix_deadline_usec; + int max; + + if (alc_num_cores == 0) { + // FIXME(Apportable) this is Linux specific + alc_num_cores = sysconf( _SC_NPROCESSORS_ONLN ); + LOGI("_SC_NPROCESSORS_ONLN=%d", alc_num_cores); + } + + if (alc_num_cores > 1) { + // Allow OpenAL to monopolize one core + mix_deadline_usec = ((size*1000000) / device->Frequency) - 2000; + } else { + // Try to cap mixing at 20% CPU + mix_deadline_usec = ((size*1000000) / device->Frequency) / 5; + } + + ts_start = clock(); + aluMixDataPrivate(device, buffer, size); + ts_end = clock(); + + // Time in micro-seconds that aluMixData has taken to run + ts_diff = ((ts_end-ts_start)*1000) / (CLOCKS_PER_SEC/1000); + + // Try to adjust the max sources limit adaptively, within a range + if (alc_active_sources > 0) { + time_per_source = max(1, ts_diff / alc_active_sources); + max_sources_within_deadline = mix_deadline_usec / time_per_source; + max = min(max(max_sources_within_deadline, MAX_SOURCES_LOW), MAX_SOURCES_HIGH); + if (max > alc_max_sources) { + alc_max_sources++; + } else if (max < alc_max_sources) { + alc_max_sources--; + } + } else { + alc_max_sources = MAX_SOURCES_LOW; + } +#else + aluMixDataPrivate(device, buffer, size); +#endif +} + +ALvoid aluHandleDisconnect(ALCdevice *device) +{ + ALuint i; + + SuspendContext(NULL); + for(i = 0;i < device->NumContexts;i++) + { + ALCcontext *Context = device->Contexts[i]; + ALsource *source; + ALsizei pos; + + SuspendContext(Context); + + for(pos = 0;pos < Context->SourceMap.size;pos++) + { + source = Context->SourceMap.array[pos].value; + if(source->state == AL_PLAYING) + { + source->state = AL_STOPPED; + source->BuffersPlayed = source->BuffersInQueue; + source->position = 0; + source->position_fraction = 0; + } + } + ProcessContext(Context); + } + + device->Connected = ALC_FALSE; + ProcessContext(NULL); +} diff --git a/jni/OpenAL/Alc/alcConfig.c b/jni/OpenAL/Alc/alcConfig.c new file mode 100644 index 0000000..847e5d1 --- /dev/null +++ b/jni/OpenAL/Alc/alcConfig.c @@ -0,0 +1,338 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#ifdef _WIN32 +#ifdef __MINGW64__ +#define _WIN32_IE 0x501 +#else +#define _WIN32_IE 0x400 +#endif +#endif + +#include "config.h" + +#include +#include +#include +#include + +#include "alMain.h" + +#ifdef _WIN32_IE +#include +#endif + +typedef struct ConfigEntry { + char *key; + char *value; +} ConfigEntry; + +typedef struct ConfigBlock { + char *name; + ConfigEntry *entries; + size_t entryCount; +} ConfigBlock; + +static ConfigBlock *cfgBlocks; +static size_t cfgCount; + +static char buffer[1024]; + +static void LoadConfigFromFile(FILE *f) +{ + ConfigBlock *curBlock = cfgBlocks; + ConfigEntry *ent; + + while(fgets(buffer, sizeof(buffer), f)) + { + size_t i = 0; + + while(isspace(buffer[i])) + i++; + if(!buffer[i] || buffer[i] == '#') + continue; + + memmove(buffer, buffer+i, strlen(buffer+i)+1); + + if(buffer[0] == '[') + { + ConfigBlock *nextBlock; + + i = 1; + while(buffer[i] && buffer[i] != ']') + i++; + + if(!buffer[i]) + { + AL_PRINT("config parse error: bad line \"%s\"\n", buffer); + continue; + } + buffer[i] = 0; + + do { + i++; + if(buffer[i] && !isspace(buffer[i])) + { + if(buffer[i] != '#') + AL_PRINT("config warning: extra data after block: \"%s\"\n", buffer+i); + break; + } + } while(buffer[i]); + + nextBlock = NULL; + for(i = 0;i < cfgCount;i++) + { + if(strcasecmp(cfgBlocks[i].name, buffer+1) == 0) + { + nextBlock = cfgBlocks+i; +// AL_PRINT("found block '%s'\n", nextBlock->name); + break; + } + } + + if(!nextBlock) + { + nextBlock = realloc(cfgBlocks, (cfgCount+1)*sizeof(ConfigBlock)); + if(!nextBlock) + { + AL_PRINT("config parse error: error reallocating config blocks\n"); + continue; + } + cfgBlocks = nextBlock; + nextBlock = cfgBlocks+cfgCount; + cfgCount++; + + nextBlock->name = strdup(buffer+1); + nextBlock->entries = NULL; + nextBlock->entryCount = 0; + +// AL_PRINT("found new block '%s'\n", nextBlock->name); + } + curBlock = nextBlock; + continue; + } + + /* Look for the option name */ + i = 0; + while(buffer[i] && buffer[i] != '#' && buffer[i] != '=' && + !isspace(buffer[i])) + i++; + + if(!buffer[i] || buffer[i] == '#' || i == 0) + { + AL_PRINT("config parse error: malformed option line: \"%s\"\n", buffer); + continue; + } + + /* Seperate the option */ + if(buffer[i] != '=') + { + buffer[i++] = 0; + + while(isspace(buffer[i])) + i++; + if(buffer[i] != '=') + { + AL_PRINT("config parse error: option without a value: \"%s\"\n", buffer); + continue; + } + } + /* Find the start of the value */ + buffer[i++] = 0; + while(isspace(buffer[i])) + i++; + + /* Check if we already have this option set */ + ent = curBlock->entries; + while((size_t)(ent-curBlock->entries) < curBlock->entryCount) + { + if(strcasecmp(ent->key, buffer) == 0) + break; + ent++; + } + + if((size_t)(ent-curBlock->entries) >= curBlock->entryCount) + { + /* Allocate a new option entry */ + ent = realloc(curBlock->entries, (curBlock->entryCount+1)*sizeof(ConfigEntry)); + if(!ent) + { + AL_PRINT("config parse error: error reallocating config entries\n"); + continue; + } + curBlock->entries = ent; + ent = curBlock->entries + curBlock->entryCount; + curBlock->entryCount++; + + ent->key = strdup(buffer); + ent->value = NULL; + } + + /* Look for the end of the line (Null term, new-line, or #-symbol) and + eat up the trailing whitespace */ + memmove(buffer, buffer+i, strlen(buffer+i)+1); + + i = 0; + while(buffer[i] && buffer[i] != '#' && buffer[i] != '\n') + i++; + do { + i--; + } while(isspace(buffer[i])); + buffer[++i] = 0; + + free(ent->value); + ent->value = strdup(buffer); + +// AL_PRINT("found '%s' = '%s'\n", ent->key, ent->value); + } +} + +void ReadALConfig(void) +{ + FILE *f; + + cfgBlocks = calloc(1, sizeof(ConfigBlock)); + cfgBlocks->name = strdup("general"); + cfgCount = 1; + +#ifdef _WIN32 + if(SHGetSpecialFolderPathA(NULL, buffer, CSIDL_APPDATA, FALSE) != FALSE) + { + size_t p = strlen(buffer); + snprintf(buffer+p, sizeof(buffer)-p, "\\alsoft.ini"); + f = fopen(buffer, "rt"); + if(f) + { + LoadConfigFromFile(f); + fclose(f); + } + } +#else + f = fopen("/etc/openal/alsoft.conf", "r"); + if(f) + { + LoadConfigFromFile(f); + fclose(f); + } + if(getenv("HOME") && *(getenv("HOME"))) + { + snprintf(buffer, sizeof(buffer), "%s/.alsoftrc", getenv("HOME")); + f = fopen(buffer, "r"); + if(f) + { + LoadConfigFromFile(f); + fclose(f); + } + } +#endif + if(getenv("ALSOFT_CONF")) + { + f = fopen(getenv("ALSOFT_CONF"), "r"); + if(f) + { + LoadConfigFromFile(f); + fclose(f); + } + } +} + +void FreeALConfig(void) +{ + size_t i; + + for(i = 0;i < cfgCount;i++) + { + size_t j; + for(j = 0;j < cfgBlocks[i].entryCount;j++) + { + free(cfgBlocks[i].entries[j].key); + free(cfgBlocks[i].entries[j].value); + } + free(cfgBlocks[i].entries); + free(cfgBlocks[i].name); + } + free(cfgBlocks); + cfgBlocks = NULL; + cfgCount = 0; +} + +const char *GetConfigValue(const char *blockName, const char *keyName, const char *def) +{ + size_t i, j; + + if(!keyName) + return def; + + if(!blockName) + blockName = "general"; + + for(i = 0;i < cfgCount;i++) + { + if(strcasecmp(cfgBlocks[i].name, blockName) != 0) + continue; + + for(j = 0;j < cfgBlocks[i].entryCount;j++) + { + if(strcasecmp(cfgBlocks[i].entries[j].key, keyName) == 0) + { + if(cfgBlocks[i].entries[j].value[0]) + return cfgBlocks[i].entries[j].value; + return def; + } + } + } + + return def; +} + +int ConfigValueExists(const char *blockName, const char *keyName) +{ + const char *val = GetConfigValue(blockName, keyName, ""); + return !!val[0]; +} + +int GetConfigValueInt(const char *blockName, const char *keyName, int def) +{ + const char *val = GetConfigValue(blockName, keyName, ""); + + if(!val[0]) return def; + return strtol(val, NULL, 0); +} + +float GetConfigValueFloat(const char *blockName, const char *keyName, float def) +{ + const char *val = GetConfigValue(blockName, keyName, ""); + + if(!val[0]) return def; +#ifdef HAVE_STRTOF + return strtof(val, NULL); +#else + return (float)strtod(val, NULL); +#endif +} + +int GetConfigValueBool(const char *blockName, const char *keyName, int def) +{ + const char *val = GetConfigValue(blockName, keyName, ""); + + if(!val[0]) return !!def; + return (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 || + strcasecmp(val, "on") == 0 || atoi(val) != 0); +} diff --git a/jni/OpenAL/Alc/alcEcho.c b/jni/OpenAL/Alc/alcEcho.c new file mode 100644 index 0000000..1abf0b2 --- /dev/null +++ b/jni/OpenAL/Alc/alcEcho.c @@ -0,0 +1,203 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include + +#include "alMain.h" +#include "alFilter.h" +#include "alAuxEffectSlot.h" +#include "alError.h" +#include "alu.h" + + +typedef struct ALechoState { + // Must be first in all effects! + ALeffectState state; + + ALfp *SampleBuffer; + ALuint BufferLength; + + // The echo is two tap. The delay is the number of samples from before the + // current offset + struct { + ALuint delay; + } Tap[2]; + ALuint Offset; + // The LR gains for the first tap. The second tap uses the reverse + ALfp GainL; + ALfp GainR; + + ALfp FeedGain; + + ALfp Gain[MAXCHANNELS]; + + FILTER iirFilter; + ALfp history[2]; +} ALechoState; + +static ALvoid EchoDestroy(ALeffectState *effect) +{ + ALechoState *state = (ALechoState*)effect; + if(state) + { + free(state->SampleBuffer); + state->SampleBuffer = NULL; + free(state); + } +} + +static ALboolean EchoDeviceUpdate(ALeffectState *effect, ALCdevice *Device) +{ + ALechoState *state = (ALechoState*)effect; + ALuint maxlen, i; + + // 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 = (ALuint)(AL_ECHO_MAX_DELAY * Device->Frequency) + 1; + maxlen += (ALuint)(AL_ECHO_MAX_LRDELAY * Device->Frequency) + 1; + maxlen = NextPowerOf2(maxlen); + + if(maxlen != state->BufferLength) + { + void *temp; + + temp = realloc(state->SampleBuffer, maxlen * sizeof(ALfp)); + if(!temp) + return AL_FALSE; + state->SampleBuffer = temp; + state->BufferLength = maxlen; + } + for(i = 0;i < state->BufferLength;i++) + state->SampleBuffer[i] = int2ALfp(0); + + for(i = 0;i < MAXCHANNELS;i++) + state->Gain[i] = int2ALfp(0); + for(i = 0;i < Device->NumChan;i++) + { + Channel chan = Device->Speaker2Chan[i]; + state->Gain[chan] = int2ALfp(1); + } + + return AL_TRUE; +} + +static ALvoid EchoUpdate(ALeffectState *effect, ALCcontext *Context, const ALeffect *Effect) +{ + ALechoState *state = (ALechoState*)effect; + ALuint frequency = Context->Device->Frequency; + ALfp lrpan, cw, a, g; + + state->Tap[0].delay = (ALuint)ALfp2int((ALfpMult(Effect->Echo.Delay, int2ALfp(frequency)) + int2ALfp(1))); + state->Tap[1].delay = (ALuint)ALfp2int(ALfpMult(Effect->Echo.LRDelay, int2ALfp(frequency))); + state->Tap[1].delay += state->Tap[0].delay; + + lrpan = (ALfpMult(Effect->Echo.Spread, float2ALfp(0.5f)) + float2ALfp(0.5f)); + state->GainL = aluSqrt( lrpan); + state->GainR = aluSqrt((int2ALfp(1)-lrpan)); + + state->FeedGain = Effect->Echo.Feedback; + + cw = __cos(ALfpDiv(float2ALfp(2.0*M_PI * LOWPASSFREQCUTOFF), int2ALfp(frequency))); + g = (int2ALfp(1) - Effect->Echo.Damping); + a = int2ALfp(0); + if(g < float2ALfp(0.9999f)) /* 1-epsilon */ { + // a = (1 - g*cw - aluSqrt(2*g*(1-cw) - g*g*(1 - cw*cw))) / (1 - g); + a = ALfpDiv((int2ALfp(1) - ALfpMult(g,cw) - aluSqrt((ALfpMult(ALfpMult(int2ALfp(2),g),(int2ALfp(1)-cw)) - + ALfpMult(ALfpMult(g,g),(int2ALfp(1) - ALfpMult(cw,cw)))))), + (int2ALfp(1) - g)); + } + state->iirFilter.coeff = a; +} + +static ALvoid EchoProcess(ALeffectState *effect, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfp *SamplesIn, ALfp (*SamplesOut)[MAXCHANNELS]) +{ + ALechoState *state = (ALechoState*)effect; + const ALuint mask = state->BufferLength-1; + const ALuint tap1 = state->Tap[0].delay; + const ALuint tap2 = state->Tap[1].delay; + ALuint offset = state->Offset; + const ALfp gain = Slot->Gain; + ALfp samp[2], smp; + ALuint i; + + for(i = 0;i < SamplesToDo;i++,offset++) + { + // Sample first tap + smp = state->SampleBuffer[(offset-tap1) & mask]; + samp[0] = ALfpMult(smp, state->GainL); + samp[1] = ALfpMult(smp, state->GainR); + // Sample second tap. Reverse LR panning + smp = state->SampleBuffer[(offset-tap2) & mask]; + samp[0] += ALfpMult(smp, state->GainR); + samp[1] += ALfpMult(smp, state->GainL); + + // Apply damping and feedback gain to the second tap, and mix in the + // new sample + smp = lpFilter2P(&state->iirFilter, 0, (smp+SamplesIn[i])); + state->SampleBuffer[offset&mask] = ALfpMult(smp, state->FeedGain); + + // Apply slot gain + samp[0] = ALfpMult(samp[0], gain); + samp[1] = ALfpMult(samp[1], gain); + + SamplesOut[i][FRONT_LEFT] += ALfpMult(state->Gain[FRONT_LEFT], samp[0]); + SamplesOut[i][FRONT_RIGHT] += ALfpMult(state->Gain[FRONT_RIGHT], samp[1]); +#ifdef APPORTABLE_OPTIMIZED_OUT + SamplesOut[i][SIDE_LEFT] += ALfpMult(state->Gain[SIDE_LEFT], samp[0]); + SamplesOut[i][SIDE_RIGHT] += ALfpMult(state->Gain[SIDE_RIGHT], samp[1]); + SamplesOut[i][BACK_LEFT] += ALfpMult(state->Gain[BACK_LEFT], samp[0]); + SamplesOut[i][BACK_RIGHT] += ALfpMult(state->Gain[BACK_RIGHT], samp[1]); +#endif + + } + state->Offset = offset; +} + +ALeffectState *EchoCreate(void) +{ + ALechoState *state; + + state = malloc(sizeof(*state)); + if(!state) + return NULL; + + state->state.Destroy = EchoDestroy; + state->state.DeviceUpdate = EchoDeviceUpdate; + state->state.Update = EchoUpdate; + state->state.Process = EchoProcess; + + state->BufferLength = 0; + state->SampleBuffer = NULL; + + state->Tap[0].delay = 0; + state->Tap[1].delay = 0; + state->Offset = 0; + state->GainL = int2ALfp(0); + state->GainR = int2ALfp(0); + + state->iirFilter.coeff = int2ALfp(0); + state->iirFilter.history[0] = int2ALfp(0); + state->iirFilter.history[1] = int2ALfp(0); + + return &state->state; +} diff --git a/jni/OpenAL/Alc/alcModulator.c b/jni/OpenAL/Alc/alcModulator.c new file mode 100644 index 0000000..445bf13 --- /dev/null +++ b/jni/OpenAL/Alc/alcModulator.c @@ -0,0 +1,229 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include + +#include "alMain.h" +#include "alFilter.h" +#include "alAuxEffectSlot.h" +#include "alError.h" +#include "alu.h" + + +typedef struct ALmodulatorState { + // Must be first in all effects! + ALeffectState state; + + enum { + SINUSOID, + SAWTOOTH, + SQUARE + } Waveform; + + ALuint index; + ALuint step; + + ALfp Gain[MAXCHANNELS]; + + FILTER iirFilter; + ALfp history[1]; +} ALmodulatorState; + +#define WAVEFORM_FRACBITS 16 +#define WAVEFORM_FRACMASK ((1<>(WAVEFORM_FRACBITS-1))&1) ? -1 : 1); +} + + +static __inline ALfp hpFilter1P(FILTER *iir, ALuint offset, ALfp input) +{ + ALfp *history = &iir->history[offset]; + ALfp a = iir->coeff; + ALfp output = input; + + output = (output + ALfpMult((history[0]-output),a)); + history[0] = output; + + return (input - output); +} + + +static ALvoid ModulatorDestroy(ALeffectState *effect) +{ + ALmodulatorState *state = (ALmodulatorState*)effect; + free(state); +} + +static ALboolean ModulatorDeviceUpdate(ALeffectState *effect, ALCdevice *Device) +{ + ALmodulatorState *state = (ALmodulatorState*)effect; + ALuint index; + + for(index = 0;index < MAXCHANNELS;index++) + state->Gain[index] = int2ALfp(0); + for(index = 0;index < Device->NumChan;index++) + { + Channel chan = Device->Speaker2Chan[index]; + state->Gain[chan] = int2ALfp(1); + } + + return AL_TRUE; +} + +static ALvoid ModulatorUpdate(ALeffectState *effect, ALCcontext *Context, const ALeffect *Effect) +{ + ALmodulatorState *state = (ALmodulatorState*)effect; + ALfp cw, a; + a = int2ALfp(0); + + if(Effect->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID) + state->Waveform = SINUSOID; + else if(Effect->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH) + state->Waveform = SAWTOOTH; + else if(Effect->Modulator.Waveform == AL_RING_MODULATOR_SQUARE) + state->Waveform = SQUARE; + + state->step = ALfp2int(ALfpDiv(ALfpMult(Effect->Modulator.Frequency, + int2ALfp(1<Device->Frequency))); + if(!state->step) + state->step = 1; + + cw = __cos(ALfpDiv(ALfpMult(float2ALfp(2.0*M_PI), + Effect->Modulator.HighPassCutoff), + int2ALfp(Context->Device->Frequency))); + a = ((int2ALfp(2)-cw) - + aluSqrt((aluPow((int2ALfp(2)-cw), int2ALfp(2)) - int2ALfp(1)))); + state->iirFilter.coeff = a; +} + +static ALvoid ModulatorProcess(ALeffectState *effect, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfp *SamplesIn, ALfp (*SamplesOut)[MAXCHANNELS]) +{ + ALmodulatorState *state = (ALmodulatorState*)effect; + const ALfp gain = Slot->Gain; + const ALuint step = state->step; + ALuint index = state->index; + ALfp samp; + ALuint i; + + switch(state->Waveform) + { + case SINUSOID: + for(i = 0;i < SamplesToDo;i++) + { +#ifdef APPORTABLE_OPTIMIZED_OUT +#define FILTER_OUT(func) do { \ + samp = SamplesIn[i]; \ + \ + index += step; \ + index &= WAVEFORM_FRACMASK; \ + samp *= func(index); \ + \ + samp = hpFilter1P(&state->iirFilter, 0, samp); \ + \ + /* Apply slot gain */ \ + samp *= gain; \ + \ + SamplesOut[i][FRONT_LEFT] += state->Gain[FRONT_LEFT] * samp; \ + SamplesOut[i][FRONT_RIGHT] += state->Gain[FRONT_RIGHT] * samp; \ + SamplesOut[i][FRONT_CENTER] += state->Gain[FRONT_CENTER] * samp; \ + SamplesOut[i][SIDE_LEFT] += state->Gain[SIDE_LEFT] * samp; \ + SamplesOut[i][SIDE_RIGHT] += state->Gain[SIDE_RIGHT] * samp; \ + SamplesOut[i][BACK_LEFT] += state->Gain[BACK_LEFT] * samp; \ + SamplesOut[i][BACK_RIGHT] += state->Gain[BACK_RIGHT] * samp; \ + SamplesOut[i][BACK_CENTER] += state->Gain[BACK_CENTER] * samp; \ +} while(0) +#else +//Apportable optimized version +#define FILTER_OUT(func) do { \ + samp = SamplesIn[i]; \ + \ + index += step; \ + index &= WAVEFORM_FRACMASK; \ + samp = ALfpMult(samp, func(index)); \ + \ + samp = hpFilter1P(&state->iirFilter, 0, samp); \ + \ + /* Apply slot gain */ \ + samp = ALfpMult(samp, gain); \ + \ + SamplesOut[i][FRONT_LEFT] += ALfpMult(state->Gain[FRONT_LEFT], samp); \ + SamplesOut[i][FRONT_RIGHT] += ALfpMult(state->Gain[FRONT_RIGHT], samp); \ +} while(0) +#endif + FILTER_OUT(sin_func); + } + break; + + case SAWTOOTH: + for(i = 0;i < SamplesToDo;i++) + { + FILTER_OUT(saw_func); + } + break; + + case SQUARE: + for(i = 0;i < SamplesToDo;i++) + { + FILTER_OUT(square_func); +#undef FILTER_OUT + } + break; + } + state->index = index; +} + +ALeffectState *ModulatorCreate(void) +{ + ALmodulatorState *state; + + state = malloc(sizeof(*state)); + if(!state) + return NULL; + + state->state.Destroy = ModulatorDestroy; + state->state.DeviceUpdate = ModulatorDeviceUpdate; + state->state.Update = ModulatorUpdate; + state->state.Process = ModulatorProcess; + + state->index = 0; + state->step = 1; + + state->iirFilter.coeff = int2ALfp(0); + state->iirFilter.history[0] = int2ALfp(0); + + return &state->state; +} diff --git a/jni/OpenAL/Alc/alcReverb.c b/jni/OpenAL/Alc/alcReverb.c new file mode 100644 index 0000000..496a15d --- /dev/null +++ b/jni/OpenAL/Alc/alcReverb.c @@ -0,0 +1,1371 @@ +/** + * Reverb for the OpenAL cross platform audio library + * Copyright (C) 2008-2009 by Christopher Fitzgerald. + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "alMain.h" +#include "alAuxEffectSlot.h" +#include "alEffect.h" +#include "alError.h" +#include "alu.h" + +typedef struct DelayLine +{ + // The delay lines use sample lengths that are powers of 2 to allow the + // use of bit-masking instead of a modulus for wrapping. + ALuint Mask; + ALfp *Line; +} DelayLine; + +typedef struct ALverbState { + // Must be first in all effects! + ALeffectState state; + + // All delay lines are allocated as a single buffer to reduce memory + // fragmentation and management code. + ALfp *SampleBuffer; + ALuint TotalSamples; + // Master effect low-pass filter (2 chained 1-pole filters). + FILTER LpFilter; + ALfp LpHistory[2]; + struct { + // Modulator delay line. + DelayLine Delay; + // The vibrato time is tracked with an index over a modulus-wrapped + // range (in samples). + ALuint Index; + ALuint Range; + // The depth of frequency change (also in samples) and its filter. + ALfp Depth; + ALfp Coeff; + ALfp Filter; + } Mod; + // Initial effect delay. + DelayLine Delay; + // The tap points for the initial delay. First tap goes to early + // reflections, the last to late reverb. + ALuint DelayTap[2]; + struct { + // Output gain for early reflections. + ALfp Gain; + // Early reflections are done with 4 delay lines. + ALfp Coeff[4]; + DelayLine Delay[4]; + ALuint Offset[4]; + // The gain for each output channel based on 3D panning (only for the + // EAX path). + ALfp PanGain[MAXCHANNELS]; + } Early; + // Decorrelator delay line. + DelayLine Decorrelator; + // There are actually 4 decorrelator taps, but the first occurs at the + // initial sample. + ALuint DecoTap[3]; + struct { + // Output gain for late reverb. + ALfp Gain; + // Attenuation to compensate for the modal density and decay rate of + // the late lines. + ALfp DensityGain; + // The feed-back and feed-forward all-pass coefficient. + ALfp ApFeedCoeff; + // Mixing matrix coefficient. + ALfp MixCoeff; + // Late reverb has 4 parallel all-pass filters. + ALfp ApCoeff[4]; + DelayLine ApDelay[4]; + ALuint ApOffset[4]; + // In addition to 4 cyclical delay lines. + ALfp Coeff[4]; + DelayLine Delay[4]; + ALuint Offset[4]; + // The cyclical delay lines are 1-pole low-pass filtered. + ALfp LpCoeff[4]; + ALfp LpSample[4]; + // The gain for each output channel based on 3D panning (only for the + // EAX path). + ALfp PanGain[MAXCHANNELS]; + } Late; + struct { + // Attenuation to compensate for the modal density and decay rate of + // the echo line. + ALfp DensityGain; + // Echo delay and all-pass lines. + DelayLine Delay; + DelayLine ApDelay; + ALfp Coeff; + ALfp ApFeedCoeff; + ALfp ApCoeff; + ALuint Offset; + ALuint ApOffset; + // The echo line is 1-pole low-pass filtered. + ALfp LpCoeff; + ALfp LpSample; + // Echo mixing coefficients. + ALfp MixCoeff[2]; + } Echo; + // The current read offset for all delay lines. + ALuint Offset; + + // The gain for each output channel (non-EAX path only; aliased from + // Late.PanGain) + ALfp *Gain; +} ALverbState; + +/* This coefficient is used to define the maximum frequency range controlled + * by the modulation depth. The current value of 0.1 will allow it to swing + * from 0.9x to 1.1x. This value must be below 1. At 1 it will cause the + * sampler to stall on the downswing, and above 1 it will cause it to sample + * backwards. + */ +static const ALfp MODULATION_DEPTH_COEFF = toALfpConst(0.1f); + +/* A filter is used to avoid the terrible distortion caused by changing + * modulation time and/or depth. To be consistent across different sample + * rates, the coefficient must be raised to a constant divided by the sample + * rate: coeff^(constant / rate). + */ +static const ALfp MODULATION_FILTER_COEFF = toALfpConst(0.048f); +static const ALfp MODULATION_FILTER_CONST = toALfpConst(100000.0f); + +// When diffusion is above 0, an all-pass filter is used to take the edge off +// the echo effect. It uses the following line length (in seconds). +static const ALfp ECHO_ALLPASS_LENGTH = toALfpConst(0.0133f); + +// Input into the late reverb is decorrelated between four channels. Their +// timings are dependent on a fraction and multiplier. See the +// UpdateDecorrelator() routine for the calculations involved. +static const ALfp DECO_FRACTION = toALfpConst(0.15f); +static const ALfp DECO_MULTIPLIER = toALfpConst(2.0f); + +// All delay line lengths are specified in seconds. + +// The lengths of the early delay lines. +static const ALfp EARLY_LINE_LENGTH[4] = +{ + toALfpConst(0.0015f), toALfpConst(0.0045f), toALfpConst(0.0135f), toALfpConst(0.0405f) +}; + +// The lengths of the late all-pass delay lines. +static const ALfp ALLPASS_LINE_LENGTH[4] = +{ + toALfpConst(0.0151f), toALfpConst(0.0167f), toALfpConst(0.0183f), toALfpConst(0.0200f), +}; + +// The lengths of the late cyclical delay lines. +static const ALfp LATE_LINE_LENGTH[4] = +{ + toALfpConst(0.0211f), toALfpConst(0.0311f), toALfpConst(0.0461f), toALfpConst(0.0680f) +}; + +// The late cyclical delay lines have a variable length dependent on the +// effect's density parameter (inverted for some reason) and this multiplier. +static const ALfp LATE_LINE_MULTIPLIER = toALfpConst(4.0f); + +// Calculate the length of a delay line and store its mask and offset. +static ALuint CalcLineLength(ALfp length, ALintptrEXT offset, ALuint frequency, DelayLine *Delay) +{ + ALuint samples; + + // All line lengths are powers of 2, calculated from their lengths, with + // an additional sample in case of rounding errors. + samples = NextPowerOf2((ALuint)(ALfp2int(ALfpMult(length, int2ALfp(frequency)))) + 1); + // All lines share a single sample buffer. + Delay->Mask = samples - 1; + Delay->Line = (ALfp*)offset; + // Return the sample count for accumulation. + return samples; +} + +// Given the allocated sample buffer, this function updates each delay line +// offset. +static __inline ALvoid RealizeLineOffset(ALfp * sampleBuffer, DelayLine *Delay) +{ + Delay->Line = &sampleBuffer[(ALintptrEXT)Delay->Line]; +} + +/* Calculates the delay line metrics and allocates the shared sample buffer + * for all lines given a flag indicating whether or not to allocate the EAX- + * related delays (eaxFlag) and the sample rate (frequency). If an + * allocation failure occurs, it returns AL_FALSE. + */ +static ALboolean AllocLines(ALboolean eaxFlag, ALuint frequency, ALverbState *State) +{ + ALuint totalSamples, index; + ALfp length; + ALfp *newBuffer = NULL; + + // All delay line lengths are calculated to accomodate the full range of + // lengths given their respective paramters. + totalSamples = 0; + if(eaxFlag) + { + /* The modulator's line length is calculated from the maximum + * modulation time and depth coefficient, and halfed for the low-to- + * high frequency swing. An additional sample is added to keep it + * stable when there is no modulation. + */ + length = ((ALfpDiv(ALfpMult(float2ALfp(AL_EAXREVERB_MAX_MODULATION_TIME), MODULATION_DEPTH_COEFF), + int2ALfp(2))) + + ALfpDiv(int2ALfp(1), int2ALfp(frequency))); + totalSamples += CalcLineLength(length, totalSamples, frequency, + &State->Mod.Delay); + } + + // The initial delay is the sum of the reflections and late reverb + // delays. + if(eaxFlag) + length = float2ALfp(AL_EAXREVERB_MAX_REFLECTIONS_DELAY + + AL_EAXREVERB_MAX_LATE_REVERB_DELAY); + else + length = float2ALfp(AL_REVERB_MAX_REFLECTIONS_DELAY + + AL_REVERB_MAX_LATE_REVERB_DELAY); + totalSamples += CalcLineLength(length, totalSamples, frequency, + &State->Delay); + + // The early reflection lines. + for(index = 0;index < 4;index++) + totalSamples += CalcLineLength(EARLY_LINE_LENGTH[index], totalSamples, + frequency, &State->Early.Delay[index]); + + // The decorrelator line is calculated from the lowest reverb density (a + // parameter value of 1). + length = ALfpMult(ALfpMult(ALfpMult(ALfpMult(DECO_FRACTION, + DECO_MULTIPLIER), + DECO_MULTIPLIER), + LATE_LINE_LENGTH[0]), + (int2ALfp(1) + LATE_LINE_MULTIPLIER)); + totalSamples += CalcLineLength(length, totalSamples, frequency, + &State->Decorrelator); + + // The late all-pass lines. + for(index = 0;index < 4;index++) + totalSamples += CalcLineLength(ALLPASS_LINE_LENGTH[index], totalSamples, + frequency, &State->Late.ApDelay[index]); + + // The late delay lines are calculated from the lowest reverb density. + for(index = 0;index < 4;index++) + { + length = ALfpMult(LATE_LINE_LENGTH[index], (int2ALfp(1) + LATE_LINE_MULTIPLIER)); + totalSamples += CalcLineLength(length, totalSamples, frequency, + &State->Late.Delay[index]); + } + + if(eaxFlag) + { + // The echo all-pass and delay lines. + totalSamples += CalcLineLength(ECHO_ALLPASS_LENGTH, totalSamples, + frequency, &State->Echo.ApDelay); + totalSamples += CalcLineLength(float2ALfp(AL_EAXREVERB_MAX_ECHO_TIME), totalSamples, + frequency, &State->Echo.Delay); + } + + if(totalSamples != State->TotalSamples) + { + newBuffer = realloc(State->SampleBuffer, sizeof(ALfp) * totalSamples); + if(newBuffer == NULL) + return AL_FALSE; + State->SampleBuffer = newBuffer; + State->TotalSamples = totalSamples; + } + + // Update all delays to reflect the new sample buffer. + RealizeLineOffset(State->SampleBuffer, &State->Delay); + RealizeLineOffset(State->SampleBuffer, &State->Decorrelator); + for(index = 0;index < 4;index++) + { + RealizeLineOffset(State->SampleBuffer, &State->Early.Delay[index]); + RealizeLineOffset(State->SampleBuffer, &State->Late.ApDelay[index]); + RealizeLineOffset(State->SampleBuffer, &State->Late.Delay[index]); + } + if(eaxFlag) + { + RealizeLineOffset(State->SampleBuffer, &State->Mod.Delay); + RealizeLineOffset(State->SampleBuffer, &State->Echo.ApDelay); + RealizeLineOffset(State->SampleBuffer, &State->Echo.Delay); + } + + // Clear the sample buffer. + for(index = 0;index < State->TotalSamples;index++) + State->SampleBuffer[index] = int2ALfp(0); + + return AL_TRUE; +} + +// Calculate a decay coefficient given the length of each cycle and the time +// until the decay reaches -60 dB. +static __inline ALfp CalcDecayCoeff(ALfp length, ALfp decayTime) +{ + return aluPow(int2ALfp(10), ALfpDiv(length, + ALfpDiv(ALfpMult(decayTime, + int2ALfp(-60)), + int2ALfp(20)))); +} + +// Calculate a decay length from a coefficient and the time until the decay +// reaches -60 dB. +static __inline ALfp CalcDecayLength(ALfp coeff, ALfp decayTime) +{ + return ALfpMult(ALfpMult(ALfpDiv(__log10(coeff), int2ALfp(-60)), int2ALfp(20)), decayTime); +} + +// Calculate the high frequency parameter for the I3DL2 coefficient +// calculation. +static __inline ALfp CalcI3DL2HFreq(ALfp hfRef, ALuint frequency) +{ + return __cos(ALfpDiv(ALfpMult(float2ALfp(2.0f * M_PI), hfRef), int2ALfp(frequency))); +} + +// Calculate an attenuation to be applied to the input of any echo models to +// compensate for modal density and decay time. +static __inline ALfp CalcDensityGain(ALfp a) +{ + /* The energy of a signal can be obtained by finding the area under the + * squared signal. This takes the form of Sum(x_n^2), where x is the + * amplitude for the sample n. + * + * Decaying feedback matches exponential decay of the form Sum(a^n), + * where a is the attenuation coefficient, and n is the sample. The area + * under this decay curve can be calculated as: 1 / (1 - a). + * + * Modifying the above equation to find the squared area under the curve + * (for energy) yields: 1 / (1 - a^2). Input attenuation can then be + * calculated by inverting the square root of this approximation, + * yielding: 1 / sqrt(1 / (1 - a^2)), simplified to: sqrt(1 - a^2). + */ + return aluSqrt((int2ALfp(1) - ALfpMult(a, a))); +} + +// Calculate the mixing matrix coefficients given a diffusion factor. +static __inline ALvoid CalcMatrixCoeffs(ALfp diffusion, ALfp *x, ALfp *y) +{ + ALfp n, t; + + // The matrix is of order 4, so n is sqrt (4 - 1). + n = aluSqrt(int2ALfp(3)); + t = ALfpMult(diffusion, __atan(n)); + + // Calculate the first mixing matrix coefficient. + *x = __cos(t); + // Calculate the second mixing matrix coefficient. + *y = ALfpDiv(__sin(t), n); +} + +// Calculate the limited HF ratio for use with the late reverb low-pass +// filters. +static ALfp CalcLimitedHfRatio(ALfp hfRatio, ALfp airAbsorptionGainHF, ALfp decayTime) +{ + ALfp limitRatio; + + /* Find the attenuation due to air absorption in dB (converting delay + * time to meters using the speed of sound). Then reversing the decay + * equation, solve for HF ratio. The delay length is cancelled out of + * the equation, so it can be calculated once for all lines. + */ + limitRatio = ALfpDiv(int2ALfp(1), + ALfpMult(CalcDecayLength(airAbsorptionGainHF, decayTime), + float2ALfp(SPEEDOFSOUNDMETRESPERSEC))); + // Need to limit the result to a minimum of 0.1, just like the HF ratio + // parameter. + limitRatio = __max(limitRatio, float2ALfp(0.1f)); + + // Using the limit calculated above, apply the upper bound to the HF + // ratio. + return __min(hfRatio, limitRatio); +} + +// Calculate the coefficient for a HF (and eventually LF) decay damping +// filter. +static __inline ALfp CalcDampingCoeff(ALfp hfRatio, ALfp length, ALfp decayTime, ALfp decayCoeff, ALfp cw) +{ + ALfp coeff, g; + + // Eventually this should boost the high frequencies when the ratio + // exceeds 1. + coeff = int2ALfp(0); + if (hfRatio < int2ALfp(1)) + { + // Calculate the low-pass coefficient by dividing the HF decay + // coefficient by the full decay coefficient. + g = ALfpDiv(CalcDecayCoeff(length, ALfpMult(decayTime, hfRatio)), decayCoeff); + + // Damping is done with a 1-pole filter, so g needs to be squared. + g = ALfpMult(g, g); + coeff = lpCoeffCalc(g, cw); + + // Very low decay times will produce minimal output, so apply an + // upper bound to the coefficient. + coeff = __min(coeff, float2ALfp(0.98f)); + } + return coeff; +} + +// Update the EAX modulation index, range, and depth. Keep in mind that this +// kind of vibrato is additive and not multiplicative as one may expect. The +// downswing will sound stronger than the upswing. +static ALvoid UpdateModulator(ALfp modTime, ALfp modDepth, ALuint frequency, ALverbState *State) +{ + ALfp length; + + /* Modulation is calculated in two parts. + * + * The modulation time effects the sinus applied to the change in + * frequency. An index out of the current time range (both in samples) + * is incremented each sample. The range is bound to a reasonable + * minimum (1 sample) and when the timing changes, the index is rescaled + * to the new range (to keep the sinus consistent). + */ + length = ALfpMult(modTime, int2ALfp(frequency)); + if (length >= int2ALfp(1)) { + State->Mod.Index = (ALuint)(ALfp2int(ALfpDiv(ALfpMult(int2ALfp(State->Mod.Index), + length), + int2ALfp(State->Mod.Range)))); + State->Mod.Range = (ALuint)ALfp2int(length); + } else { + State->Mod.Index = 0; + State->Mod.Range = 1; + } + + /* The modulation depth effects the amount of frequency change over the + * range of the sinus. It needs to be scaled by the modulation time so + * that a given depth produces a consistent change in frequency over all + * ranges of time. Since the depth is applied to a sinus value, it needs + * to be halfed once for the sinus range and again for the sinus swing + * in time (half of it is spent decreasing the frequency, half is spent + * increasing it). + */ + State->Mod.Depth = ALfpMult(ALfpDiv(ALfpDiv(ALfpMult(ALfpMult(modDepth, + MODULATION_DEPTH_COEFF), + modTime), + int2ALfp(2)), + int2ALfp(2)), + int2ALfp(frequency)); +} + +// Update the offsets for the initial effect delay line. +static ALvoid UpdateDelayLine(ALfp earlyDelay, ALfp lateDelay, ALuint frequency, ALverbState *State) +{ + // Calculate the initial delay taps. + State->DelayTap[0] = (ALuint)(ALfp2int(ALfpMult(earlyDelay, int2ALfp(frequency)))); + State->DelayTap[1] = (ALuint)(ALfp2int(ALfpMult((earlyDelay + lateDelay), int2ALfp(frequency)))); +} + +// Update the early reflections gain and line coefficients. +static ALvoid UpdateEarlyLines(ALfp reverbGain, ALfp earlyGain, ALfp lateDelay, ALverbState *State) +{ + ALuint index; + + // Calculate the early reflections gain (from the master effect gain, and + // reflections gain parameters) with a constant attenuation of 0.5. + State->Early.Gain = ALfpMult(ALfpMult(float2ALfp(0.5f), reverbGain), earlyGain); + + // Calculate the gain (coefficient) for each early delay line using the + // late delay time. This expands the early reflections to the start of + // the late reverb. + for(index = 0;index < 4;index++) + State->Early.Coeff[index] = CalcDecayCoeff(EARLY_LINE_LENGTH[index], + lateDelay); +} + +// Update the offsets for the decorrelator line. +static ALvoid UpdateDecorrelator(ALfp density, ALuint frequency, ALverbState *State) +{ + ALuint index; + ALfp length; + + /* The late reverb inputs are decorrelated to smooth the reverb tail and + * reduce harsh echos. The first tap occurs immediately, while the + * remaining taps are delayed by multiples of a fraction of the smallest + * cyclical delay time. + * + * offset[index] = (FRACTION (MULTIPLIER^index)) smallest_delay + */ + for(index = 0;index < 3;index++) + { + length = ALfpMult(ALfpMult(ALfpMult(DECO_FRACTION, + aluPow(DECO_MULTIPLIER, int2ALfp(index))), + LATE_LINE_LENGTH[0]), + (int2ALfp(1) + ALfpMult(density, LATE_LINE_MULTIPLIER))); + State->DecoTap[index] = (ALuint)ALfp2int(ALfpMult(length, int2ALfp(frequency))); + } +} + +// Update the late reverb gains, line lengths, and line coefficients. +static ALvoid UpdateLateLines(ALfp reverbGain, ALfp lateGain, ALfp xMix, ALfp density, ALfp decayTime, ALfp diffusion, ALfp hfRatio, ALfp cw, ALuint frequency, ALverbState *State) +{ + ALfp length; + ALuint index; + + /* Calculate the late reverb gain (from the master effect gain, and late + * reverb gain parameters). Since the output is tapped prior to the + * application of the next delay line coefficients, this gain needs to be + * attenuated by the 'x' mixing matrix coefficient as well. + */ + State->Late.Gain = ALfpMult(ALfpMult(reverbGain, lateGain), xMix); + + /* To compensate for changes in modal density and decay time of the late + * reverb signal, the input is attenuated based on the maximal energy of + * the outgoing signal. This approximation is used to keep the apparent + * energy of the signal equal for all ranges of density and decay time. + * + * The average length of the cyclcical delay lines is used to calculate + * the attenuation coefficient. + */ + length = ALfpDiv((LATE_LINE_LENGTH[0] + LATE_LINE_LENGTH[1] + + LATE_LINE_LENGTH[2] + LATE_LINE_LENGTH[3]), + int2ALfp(4)); + length = ALfpMult(length, (int2ALfp(1) + ALfpMult(density, LATE_LINE_MULTIPLIER))); + State->Late.DensityGain = CalcDensityGain(CalcDecayCoeff(length, + decayTime)); + + // Calculate the all-pass feed-back and feed-forward coefficient. + State->Late.ApFeedCoeff = ALfpMult(float2ALfp(0.5f), aluPow(diffusion, int2ALfp(2))); + + for(index = 0;index < 4;index++) + { + // Calculate the gain (coefficient) for each all-pass line. + State->Late.ApCoeff[index] = CalcDecayCoeff(ALLPASS_LINE_LENGTH[index], + decayTime); + + // Calculate the length (in seconds) of each cyclical delay line. + length = ALfpMult(LATE_LINE_LENGTH[index], + (int2ALfp(1) + ALfpMult(density, LATE_LINE_MULTIPLIER))); + + // Calculate the delay offset for each cyclical delay line. + State->Late.Offset[index] = (ALuint)(ALfp2int(ALfpMult(length, int2ALfp(frequency)))); + + // Calculate the gain (coefficient) for each cyclical line. + State->Late.Coeff[index] = CalcDecayCoeff(length, decayTime); + + // Calculate the damping coefficient for each low-pass filter. + State->Late.LpCoeff[index] = + CalcDampingCoeff(hfRatio, length, decayTime, + State->Late.Coeff[index], cw); + + // Attenuate the cyclical line coefficients by the mixing coefficient + // (x). + State->Late.Coeff[index] = ALfpMult(State->Late.Coeff[index], xMix); + } +} + +// Update the echo gain, line offset, line coefficients, and mixing +// coefficients. +static ALvoid UpdateEchoLine(ALfp reverbGain, ALfp lateGain, ALfp echoTime, ALfp decayTime, ALfp diffusion, ALfp echoDepth, ALfp hfRatio, ALfp cw, ALuint frequency, ALverbState *State) +{ + // Update the offset and coefficient for the echo delay line. + State->Echo.Offset = (ALuint)(ALfp2int(ALfpMult(echoTime, int2ALfp(frequency)))); + + // Calculate the decay coefficient for the echo line. + State->Echo.Coeff = CalcDecayCoeff(echoTime, decayTime); + + // Calculate the energy-based attenuation coefficient for the echo delay + // line. + State->Echo.DensityGain = CalcDensityGain(State->Echo.Coeff); + + // Calculate the echo all-pass feed coefficient. + State->Echo.ApFeedCoeff = ALfpMult(float2ALfp(0.5f), aluPow(diffusion, int2ALfp(2))); + + // Calculate the echo all-pass attenuation coefficient. + State->Echo.ApCoeff = CalcDecayCoeff(ECHO_ALLPASS_LENGTH, decayTime); + + // Calculate the damping coefficient for each low-pass filter. + State->Echo.LpCoeff = CalcDampingCoeff(hfRatio, echoTime, decayTime, + State->Echo.Coeff, cw); + + /* Calculate the echo mixing coefficients. The first is applied to the + * echo itself. The second is used to attenuate the late reverb when + * echo depth is high and diffusion is low, so the echo is slightly + * stronger than the decorrelated echos in the reverb tail. + */ + State->Echo.MixCoeff[0] = ALfpMult(ALfpMult(reverbGain, lateGain), echoDepth); + State->Echo.MixCoeff[1] = (int2ALfp(1) - ALfpMult(ALfpMult(echoDepth, float2ALfp(0.5f)), (int2ALfp(1) - diffusion))); +} + +// Update the early and late 3D panning gains. +static ALvoid Update3DPanning(const ALCdevice *Device, const ALfp *ReflectionsPan, const ALfp *LateReverbPan, ALverbState *State) +{ + ALfp earlyPan[3] = { ReflectionsPan[0], ReflectionsPan[1], + ReflectionsPan[2] }; + ALfp latePan[3] = { LateReverbPan[0], LateReverbPan[1], + LateReverbPan[2] }; + const ALfp *speakerGain; + ALfp dirGain; + ALfp length; + ALuint index; + ALint pos; + + // Calculate the 3D-panning gains for the early reflections and late + // reverb. + length = (ALfpMult(earlyPan[0],earlyPan[0]) + ALfpMult(earlyPan[1],earlyPan[1]) + ALfpMult(earlyPan[2],earlyPan[2])); + if(length > int2ALfp(1)) + { + length = ALfpDiv(int2ALfp(1), aluSqrt(length)); + earlyPan[0] = ALfpMult(earlyPan[0], length); + earlyPan[1] = ALfpMult(earlyPan[1], length); + earlyPan[2] = ALfpMult(earlyPan[2], length); + } + length = (ALfpMult(latePan[0],latePan[0]) + ALfpMult(latePan[1],latePan[1]) + ALfpMult(latePan[2],latePan[2])); + if(length > int2ALfp(1)) + { + length = ALfpDiv(int2ALfp(1), aluSqrt(length)); + latePan[0] = ALfpMult(latePan[0], length); + latePan[1] = ALfpMult(latePan[1], length); + latePan[2] = ALfpMult(latePan[2], length); + } + + /* This code applies directional reverb just like the mixer applies + * directional sources. It diffuses the sound toward all speakers as the + * magnitude of the panning vector drops, which is only a rough + * approximation of the expansion of sound across the speakers from the + * panning direction. + */ + pos = aluCart2LUTpos(earlyPan[2], earlyPan[0]); + speakerGain = &Device->PanningLUT[MAXCHANNELS * pos]; + dirGain = aluSqrt((ALfpMult(earlyPan[0], earlyPan[0]) + ALfpMult(earlyPan[2], earlyPan[2]))); + + for(index = 0;index < MAXCHANNELS;index++) + State->Early.PanGain[index] = int2ALfp(0); + for(index = 0;index < Device->NumChan;index++) + { + Channel chan = Device->Speaker2Chan[index]; + State->Early.PanGain[chan] = (int2ALfp(1) + ALfpMult((speakerGain[chan]-int2ALfp(1)),dirGain)); + } + + + pos = aluCart2LUTpos(latePan[2], latePan[0]); + speakerGain = &Device->PanningLUT[MAXCHANNELS * pos]; + dirGain = aluSqrt((ALfpMult(latePan[0], latePan[0]) + ALfpMult(latePan[2], latePan[2]))); + + for(index = 0;index < MAXCHANNELS;index++) + State->Late.PanGain[index] = int2ALfp(0); + for(index = 0;index < Device->NumChan;index++) + { + Channel chan = Device->Speaker2Chan[index]; + State->Late.PanGain[chan] = (int2ALfp(1) + ALfpMult((speakerGain[chan]-int2ALfp(1)),dirGain)); + } +} + +// Basic delay line input/output routines. +static __inline ALfp DelayLineOut(DelayLine *Delay, ALuint offset) +{ + return Delay->Line[offset&Delay->Mask]; +} + +static __inline ALvoid DelayLineIn(DelayLine *Delay, ALuint offset, ALfp in) +{ + Delay->Line[offset&Delay->Mask] = in; +} + +// Attenuated delay line output routine. +static __inline ALfp AttenuatedDelayLineOut(DelayLine *Delay, ALuint offset, ALfp coeff) +{ + return ALfpMult(coeff, Delay->Line[offset&Delay->Mask]); +} + +// Basic attenuated all-pass input/output routine. +static __inline ALfp AllpassInOut(DelayLine *Delay, ALuint outOffset, ALuint inOffset, ALfp in, ALfp feedCoeff, ALfp coeff) +{ + ALfp out, feed; + + out = DelayLineOut(Delay, outOffset); + feed = ALfpMult(feedCoeff, in); + DelayLineIn(Delay, inOffset, (ALfpMult(feedCoeff, (out - feed)) + in)); + + // The time-based attenuation is only applied to the delay output to + // keep it from affecting the feed-back path (which is already controlled + // by the all-pass feed coefficient). + return (ALfpMult(coeff, out) - feed); +} + +// Given an input sample, this function produces modulation for the late +// reverb. +static __inline ALfp EAXModulation(ALverbState *State, ALfp in) +{ + ALfp sinus, frac; + ALuint offset; + ALfp out0, out1; + + // Calculate the sinus rythm (dependent on modulation time and the + // sampling rate). The center of the sinus is moved to reduce the delay + // of the effect when the time or depth are low. + sinus = (int2ALfp(1) - __cos(ALfpDiv(ALfpMult(float2ALfp(2.0f * M_PI), int2ALfp(State->Mod.Index)), int2ALfp(State->Mod.Range)))); + + // The depth determines the range over which to read the input samples + // from, so it must be filtered to reduce the distortion caused by even + // small parameter changes. + State->Mod.Filter = lerp(State->Mod.Filter, State->Mod.Depth, + State->Mod.Coeff); + + // Calculate the read offset and fraction between it and the next sample. + frac = (int2ALfp(1) + ALfpMult(State->Mod.Filter, sinus)); + offset = (ALuint)ALfp2int(frac); + frac = (frac - int2ALfp(offset)); + + // Get the two samples crossed by the offset, and feed the delay line + // with the next input sample. + out0 = DelayLineOut(&State->Mod.Delay, State->Offset - offset); + out1 = DelayLineOut(&State->Mod.Delay, State->Offset - offset - 1); + DelayLineIn(&State->Mod.Delay, State->Offset, in); + + // Step the modulation index forward, keeping it bound to its range. + State->Mod.Index = (State->Mod.Index + 1) % State->Mod.Range; + + // The output is obtained by linearly interpolating the two samples that + // were acquired above. + return lerp(out0, out1, frac); +} + +// Delay line output routine for early reflections. +static __inline ALfp EarlyDelayLineOut(ALverbState *State, ALuint index) +{ + return AttenuatedDelayLineOut(&State->Early.Delay[index], + State->Offset - State->Early.Offset[index], + State->Early.Coeff[index]); +} + +// Given an input sample, this function produces four-channel output for the +// early reflections. +static __inline ALvoid EarlyReflection(ALverbState *State, ALfp in, ALfp *out) +{ + ALfp d[4], v, f[4]; + + // Obtain the decayed results of each early delay line. + d[0] = EarlyDelayLineOut(State, 0); + d[1] = EarlyDelayLineOut(State, 1); + d[2] = EarlyDelayLineOut(State, 2); + d[3] = EarlyDelayLineOut(State, 3); + + /* The following uses a lossless scattering junction from waveguide + * theory. It actually amounts to a householder mixing matrix, which + * will produce a maximally diffuse response, and means this can probably + * be considered a simple feed-back delay network (FDN). + * N + * --- + * \ + * v = 2/N / d_i + * --- + * i=1 + */ + v = ALfpMult((d[0] + d[1] + d[2] + d[3]), float2ALfp(0.5f)); + // The junction is loaded with the input here. + v = (v + in); + + // Calculate the feed values for the delay lines. + f[0] = (v - d[0]); + f[1] = (v - d[1]); + f[2] = (v - d[2]); + f[3] = (v - d[3]); + + // Re-feed the delay lines. + DelayLineIn(&State->Early.Delay[0], State->Offset, f[0]); + DelayLineIn(&State->Early.Delay[1], State->Offset, f[1]); + DelayLineIn(&State->Early.Delay[2], State->Offset, f[2]); + DelayLineIn(&State->Early.Delay[3], State->Offset, f[3]); + + // Output the results of the junction for all four channels. + out[0] = ALfpMult(State->Early.Gain, f[0]); + out[1] = ALfpMult(State->Early.Gain, f[1]); + out[2] = ALfpMult(State->Early.Gain, f[2]); + out[3] = ALfpMult(State->Early.Gain, f[3]); +} + +// All-pass input/output routine for late reverb. +static __inline ALfp LateAllPassInOut(ALverbState *State, ALuint index, ALfp in) +{ + return AllpassInOut(&State->Late.ApDelay[index], + State->Offset - State->Late.ApOffset[index], + State->Offset, in, State->Late.ApFeedCoeff, + State->Late.ApCoeff[index]); +} + +// Delay line output routine for late reverb. +static __inline ALfp LateDelayLineOut(ALverbState *State, ALuint index) +{ + return AttenuatedDelayLineOut(&State->Late.Delay[index], + State->Offset - State->Late.Offset[index], + State->Late.Coeff[index]); +} + +// Low-pass filter input/output routine for late reverb. +static __inline ALfp LateLowPassInOut(ALverbState *State, ALuint index, ALfp in) +{ + in = lerp(in, State->Late.LpSample[index], State->Late.LpCoeff[index]); + State->Late.LpSample[index] = in; + return in; +} + +// Given four decorrelated input samples, this function produces four-channel +// output for the late reverb. +static __inline ALvoid LateReverb(ALverbState *State, ALfp *in, ALfp *out) +{ + ALfp d[4], f[4]; + + // Obtain the decayed results of the cyclical delay lines, and add the + // corresponding input channels. Then pass the results through the + // low-pass filters. + + // This is where the feed-back cycles from line 0 to 1 to 3 to 2 and back + // to 0. + d[0] = LateLowPassInOut(State, 2, (in[2] + LateDelayLineOut(State, 2))); + d[1] = LateLowPassInOut(State, 0, (in[0] + LateDelayLineOut(State, 0))); + d[2] = LateLowPassInOut(State, 3, (in[3] + LateDelayLineOut(State, 3))); + d[3] = LateLowPassInOut(State, 1, (in[1] + LateDelayLineOut(State, 1))); + + // To help increase diffusion, run each line through an all-pass filter. + // When there is no diffusion, the shortest all-pass filter will feed the + // shortest delay line. + d[0] = LateAllPassInOut(State, 0, d[0]); + d[1] = LateAllPassInOut(State, 1, d[1]); + d[2] = LateAllPassInOut(State, 2, d[2]); + d[3] = LateAllPassInOut(State, 3, d[3]); + + /* Late reverb is done with a modified feed-back delay network (FDN) + * topology. Four input lines are each fed through their own all-pass + * filter and then into the mixing matrix. The four outputs of the + * mixing matrix are then cycled back to the inputs. Each output feeds + * a different input to form a circlular feed cycle. + * + * The mixing matrix used is a 4D skew-symmetric rotation matrix derived + * using a single unitary rotational parameter: + * + * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2 + * [ -a, d, c, -b ] + * [ -b, -c, d, a ] + * [ -c, b, -a, d ] + * + * The rotation is constructed from the effect's diffusion parameter, + * yielding: 1 = x^2 + 3 y^2; where a, b, and c are the coefficient y + * with differing signs, and d is the coefficient x. The matrix is thus: + * + * [ x, y, -y, y ] n = sqrt(matrix_order - 1) + * [ -y, x, y, y ] t = diffusion_parameter * atan(n) + * [ y, -y, x, y ] x = cos(t) + * [ -y, -y, -y, x ] y = sin(t) / n + * + * To reduce the number of multiplies, the x coefficient is applied with + * the cyclical delay line coefficients. Thus only the y coefficient is + * applied when mixing, and is modified to be: y / x. + */ + f[0] = (d[0] + ALfpMult(State->Late.MixCoeff, ( d[1] + -1*d[2] + d[3]))); + f[1] = (d[1] + ALfpMult(State->Late.MixCoeff, (-1*d[0] + d[2] + d[3]))); + f[2] = (d[2] + ALfpMult(State->Late.MixCoeff, ( d[0] + -1*d[1] + d[3]))); + f[3] = (d[3] + ALfpMult(State->Late.MixCoeff, (-1*d[0] + -1*d[1] + -1*d[2] ))); + + // Output the results of the matrix for all four channels, attenuated by + // the late reverb gain (which is attenuated by the 'x' mix coefficient). + out[0] = ALfpMult(State->Late.Gain, f[0]); + out[1] = ALfpMult(State->Late.Gain, f[1]); + out[2] = ALfpMult(State->Late.Gain, f[2]); + out[3] = ALfpMult(State->Late.Gain, f[3]); + + // Re-feed the cyclical delay lines. + DelayLineIn(&State->Late.Delay[0], State->Offset, f[0]); + DelayLineIn(&State->Late.Delay[1], State->Offset, f[1]); + DelayLineIn(&State->Late.Delay[2], State->Offset, f[2]); + DelayLineIn(&State->Late.Delay[3], State->Offset, f[3]); +} + +// Given an input sample, this function mixes echo into the four-channel late +// reverb. +static __inline ALvoid EAXEcho(ALverbState *State, ALfp in, ALfp *late) +{ + ALfp out, feed; + + // Get the latest attenuated echo sample for output. + feed = AttenuatedDelayLineOut(&State->Echo.Delay, + State->Offset - State->Echo.Offset, + State->Echo.Coeff); + + // Mix the output into the late reverb channels. + out = ALfpMult(State->Echo.MixCoeff[0], feed); + late[0] = (ALfpMult(State->Echo.MixCoeff[1], late[0]) + out); + late[1] = (ALfpMult(State->Echo.MixCoeff[1], late[1]) + out); + late[2] = (ALfpMult(State->Echo.MixCoeff[1], late[2]) + out); + late[3] = (ALfpMult(State->Echo.MixCoeff[1], late[3]) + out); + + // Mix the energy-attenuated input with the output and pass it through + // the echo low-pass filter. + feed = (feed + ALfpMult(State->Echo.DensityGain, in)); + feed = lerp(feed, State->Echo.LpSample, State->Echo.LpCoeff); + State->Echo.LpSample = feed; + + // Then the echo all-pass filter. + feed = AllpassInOut(&State->Echo.ApDelay, + State->Offset - State->Echo.ApOffset, + State->Offset, feed, State->Echo.ApFeedCoeff, + State->Echo.ApCoeff); + + // Feed the delay with the mixed and filtered sample. + DelayLineIn(&State->Echo.Delay, State->Offset, feed); +} + +// Perform the non-EAX reverb pass on a given input sample, resulting in +// four-channel output. +static __inline ALvoid VerbPass(ALverbState *State, ALfp in, ALfp *early, ALfp *late) +{ + ALfp feed, taps[4]; + + // Low-pass filter the incoming sample. + in = lpFilter2P(&State->LpFilter, 0, in); + + // Feed the initial delay line. + DelayLineIn(&State->Delay, State->Offset, in); + + // Calculate the early reflection from the first delay tap. + in = DelayLineOut(&State->Delay, State->Offset - State->DelayTap[0]); + EarlyReflection(State, in, early); + + // Feed the decorrelator from the energy-attenuated output of the second + // delay tap. + in = DelayLineOut(&State->Delay, State->Offset - State->DelayTap[1]); + feed = ALfpMult(in, State->Late.DensityGain); + DelayLineIn(&State->Decorrelator, State->Offset, feed); + + // Calculate the late reverb from the decorrelator taps. + taps[0] = feed; + taps[1] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[0]); + taps[2] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[1]); + taps[3] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[2]); + LateReverb(State, taps, late); + + // Step all delays forward one sample. + State->Offset++; +} + +// Perform the EAX reverb pass on a given input sample, resulting in four- +// channel output. +static __inline ALvoid EAXVerbPass(ALverbState *State, ALfp in, ALfp *early, ALfp *late) +{ + ALfp feed, taps[4]; + + // Low-pass filter the incoming sample. + in = lpFilter2P(&State->LpFilter, 0, in); + + // Perform any modulation on the input. + in = EAXModulation(State, in); + + // Feed the initial delay line. + DelayLineIn(&State->Delay, State->Offset, in); + + // Calculate the early reflection from the first delay tap. + in = DelayLineOut(&State->Delay, State->Offset - State->DelayTap[0]); + EarlyReflection(State, in, early); + + // Feed the decorrelator from the energy-attenuated output of the second + // delay tap. + in = DelayLineOut(&State->Delay, State->Offset - State->DelayTap[1]); + feed = ALfpMult(in, State->Late.DensityGain); + DelayLineIn(&State->Decorrelator, State->Offset, feed); + + // Calculate the late reverb from the decorrelator taps. + taps[0] = feed; + taps[1] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[0]); + taps[2] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[1]); + taps[3] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[2]); + LateReverb(State, taps, late); + + // Calculate and mix in any echo. + EAXEcho(State, in, late); + + // Step all delays forward one sample. + State->Offset++; +} + +// This destroys the reverb state. It should be called only when the effect +// slot has a different (or no) effect loaded over the reverb effect. +static ALvoid VerbDestroy(ALeffectState *effect) +{ + ALverbState *State = (ALverbState*)effect; + if(State) + { + free(State->SampleBuffer); + State->SampleBuffer = NULL; + free(State); + } +} + +// This updates the device-dependant reverb state. This is called on +// initialization and any time the device parameters (eg. playback frequency, +// or format) have been changed. +static ALboolean VerbDeviceUpdate(ALeffectState *effect, ALCdevice *Device) +{ + ALverbState *State = (ALverbState*)effect; + ALuint frequency = Device->Frequency; + ALuint index; + + // Allocate the delay lines. + if(!AllocLines(AL_FALSE, frequency, State)) + return AL_FALSE; + + // The early reflection and late all-pass filter line lengths are static, + // so their offsets only need to be calculated once. + for(index = 0;index < 4;index++) + { + State->Early.Offset[index] = ALfp2int(ALfpMult(EARLY_LINE_LENGTH[index], + int2ALfp(frequency))); + State->Late.ApOffset[index] = ALfp2int(ALfpMult(ALLPASS_LINE_LENGTH[index], + int2ALfp(frequency))); + } + + for(index = 0;index < MAXCHANNELS;index++) + State->Gain[index] = int2ALfp(0); + for(index = 0;index < Device->NumChan;index++) + { + Channel chan = Device->Speaker2Chan[index]; + State->Gain[chan] = int2ALfp(1); + } + + return AL_TRUE; +} + +// This updates the device-dependant EAX reverb state. This is called on +// initialization and any time the device parameters (eg. playback frequency, +// format) have been changed. +static ALboolean EAXVerbDeviceUpdate(ALeffectState *effect, ALCdevice *Device) +{ + ALverbState *State = (ALverbState*)effect; + ALuint frequency = Device->Frequency, index; + + // Allocate the delay lines. + if(!AllocLines(AL_TRUE, frequency, State)) + return AL_FALSE; + + // Calculate the modulation filter coefficient. Notice that the exponent + // is calculated given the current sample rate. This ensures that the + // resulting filter response over time is consistent across all sample + // rates. + State->Mod.Coeff = aluPow(MODULATION_FILTER_COEFF, + ALfpDiv(MODULATION_FILTER_CONST, int2ALfp(frequency))); + + // The early reflection and late all-pass filter line lengths are static, + // so their offsets only need to be calculated once. + for(index = 0;index < 4;index++) + { + State->Early.Offset[index] = ALfp2int(ALfpMult(EARLY_LINE_LENGTH[index], + int2ALfp(frequency))); + State->Late.ApOffset[index] = ALfp2int(ALfpMult(ALLPASS_LINE_LENGTH[index], + int2ALfp(frequency))); + } + + // The echo all-pass filter line length is static, so its offset only + // needs to be calculated once. + State->Echo.ApOffset = ALfp2int(ALfpMult(ECHO_ALLPASS_LENGTH, int2ALfp(frequency))); + + return AL_TRUE; +} + +// This updates the reverb state. This is called any time the reverb effect +// is loaded into a slot. +static ALvoid VerbUpdate(ALeffectState *effect, ALCcontext *Context, const ALeffect *Effect) +{ + ALverbState *State = (ALverbState*)effect; + ALuint frequency = Context->Device->Frequency; + ALfp cw, x, y, hfRatio; + + // Calculate the master low-pass filter (from the master effect HF gain). + cw = CalcI3DL2HFreq(Effect->Reverb.HFReference, frequency); + // This is done with 2 chained 1-pole filters, so no need to square g. + State->LpFilter.coeff = lpCoeffCalc(Effect->Reverb.GainHF, cw); + + // Update the initial effect delay. + UpdateDelayLine(Effect->Reverb.ReflectionsDelay, + Effect->Reverb.LateReverbDelay, frequency, State); + + // Update the early lines. + UpdateEarlyLines(Effect->Reverb.Gain, Effect->Reverb.ReflectionsGain, + Effect->Reverb.LateReverbDelay, State); + + // Update the decorrelator. + UpdateDecorrelator(Effect->Reverb.Density, frequency, State); + + // Get the mixing matrix coefficients (x and y). + CalcMatrixCoeffs(Effect->Reverb.Diffusion, &x, &y); + // Then divide x into y to simplify the matrix calculation. + State->Late.MixCoeff = ALfpDiv(y, x); + + // If the HF limit parameter is flagged, calculate an appropriate limit + // based on the air absorption parameter. + hfRatio = Effect->Reverb.DecayHFRatio; + if(Effect->Reverb.DecayHFLimit && Effect->Reverb.AirAbsorptionGainHF < int2ALfp(1)) + hfRatio = CalcLimitedHfRatio(hfRatio, Effect->Reverb.AirAbsorptionGainHF, + Effect->Reverb.DecayTime); + + // Update the late lines. + UpdateLateLines(Effect->Reverb.Gain, Effect->Reverb.LateReverbGain, + x, Effect->Reverb.Density, Effect->Reverb.DecayTime, + Effect->Reverb.Diffusion, hfRatio, cw, frequency, State); +} + +// This updates the EAX reverb state. This is called any time the EAX reverb +// effect is loaded into a slot. +static ALvoid EAXVerbUpdate(ALeffectState *effect, ALCcontext *Context, const ALeffect *Effect) +{ + ALverbState *State = (ALverbState*)effect; + ALuint frequency = Context->Device->Frequency; + ALfp cw, x, y, hfRatio; + + // Calculate the master low-pass filter (from the master effect HF gain). + cw = CalcI3DL2HFreq(Effect->Reverb.HFReference, frequency); + // This is done with 2 chained 1-pole filters, so no need to square g. + State->LpFilter.coeff = lpCoeffCalc(Effect->Reverb.GainHF, cw); + + // Update the modulator line. + UpdateModulator(Effect->Reverb.ModulationTime, + Effect->Reverb.ModulationDepth, frequency, State); + + // Update the initial effect delay. + UpdateDelayLine(Effect->Reverb.ReflectionsDelay, + Effect->Reverb.LateReverbDelay, frequency, State); + + // Update the early lines. + UpdateEarlyLines(Effect->Reverb.Gain, Effect->Reverb.ReflectionsGain, + Effect->Reverb.LateReverbDelay, State); + + // Update the decorrelator. + UpdateDecorrelator(Effect->Reverb.Density, frequency, State); + + // Get the mixing matrix coefficients (x and y). + CalcMatrixCoeffs(Effect->Reverb.Diffusion, &x, &y); + // Then divide x into y to simplify the matrix calculation. + State->Late.MixCoeff = ALfpDiv(y, x); + + // If the HF limit parameter is flagged, calculate an appropriate limit + // based on the air absorption parameter. + hfRatio = Effect->Reverb.DecayHFRatio; + if(Effect->Reverb.DecayHFLimit && Effect->Reverb.AirAbsorptionGainHF < int2ALfp(1)) + hfRatio = CalcLimitedHfRatio(hfRatio, Effect->Reverb.AirAbsorptionGainHF, + Effect->Reverb.DecayTime); + + // Update the late lines. + UpdateLateLines(Effect->Reverb.Gain, Effect->Reverb.LateReverbGain, + x, Effect->Reverb.Density, Effect->Reverb.DecayTime, + Effect->Reverb.Diffusion, hfRatio, cw, frequency, State); + + // Update the echo line. + UpdateEchoLine(Effect->Reverb.Gain, Effect->Reverb.LateReverbGain, + Effect->Reverb.EchoTime, Effect->Reverb.DecayTime, + Effect->Reverb.Diffusion, Effect->Reverb.EchoDepth, + hfRatio, cw, frequency, State); + + // Update early and late 3D panning. + Update3DPanning(Context->Device, Effect->Reverb.ReflectionsPan, + Effect->Reverb.LateReverbPan, State); +} + +// This processes the reverb state, given the input samples and an output +// buffer. +static ALvoid VerbProcess(ALeffectState *effect, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfp *SamplesIn, ALfp (*SamplesOut)[MAXCHANNELS]) +{ + ALverbState *State = (ALverbState*)effect; + ALuint index; + ALfp early[4], late[4], out[4]; + ALfp gain = Slot->Gain; + const ALfp *panGain = State->Gain; + + for(index = 0;index < SamplesToDo;index++) + { + // Process reverb for this sample. + VerbPass(State, SamplesIn[index], early, late); + + // Mix early reflections and late reverb. + out[0] = ALfpMult((early[0] + late[0]), gain); + out[1] = ALfpMult((early[1] + late[1]), gain); +#ifdef APPORTABLE_OPTIMIZED_OUT + out[2] = ALfpMult((early[2] + late[2]), gain); + out[3] = ALfpMult((early[3] + late[3]), gain); +#endif + + // Output the results. + SamplesOut[index][FRONT_LEFT] = (SamplesOut[index][FRONT_LEFT] + ALfpMult(panGain[FRONT_LEFT] , out[0])); + SamplesOut[index][FRONT_RIGHT] = (SamplesOut[index][FRONT_RIGHT] + ALfpMult(panGain[FRONT_RIGHT] , out[1])); +#ifdef APPORTABLE_OPTIMIZED_OUT + SamplesOut[index][FRONT_CENTER] = (SamplesOut[index][FRONT_CENTER] + ALfpMult(panGain[FRONT_CENTER] , out[3])); + SamplesOut[index][SIDE_LEFT] = (SamplesOut[index][SIDE_LEFT] + ALfpMult(panGain[SIDE_LEFT] , out[0])); + SamplesOut[index][SIDE_RIGHT] = (SamplesOut[index][SIDE_RIGHT] + ALfpMult(panGain[SIDE_RIGHT] , out[1])); + SamplesOut[index][BACK_LEFT] = (SamplesOut[index][BACK_LEFT] + ALfpMult(panGain[BACK_LEFT] , out[0])); + SamplesOut[index][BACK_RIGHT] = (SamplesOut[index][BACK_RIGHT] + ALfpMult(panGain[BACK_RIGHT] , out[1])); + SamplesOut[index][BACK_CENTER] = (SamplesOut[index][BACK_CENTER] + ALfpMult(panGain[BACK_CENTER] , out[2])); +#endif + } +} + +// This processes the EAX reverb state, given the input samples and an output +// buffer. +static ALvoid EAXVerbProcess(ALeffectState *effect, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfp *SamplesIn, ALfp (*SamplesOut)[MAXCHANNELS]) +{ + ALverbState *State = (ALverbState*)effect; + ALuint index; + ALfp early[4], late[4]; + ALfp gain = Slot->Gain; + + for(index = 0;index < SamplesToDo;index++) + { + // Process reverb for this sample. + EAXVerbPass(State, SamplesIn[index], early, late); + + // Unfortunately, while the number and configuration of gains for + // panning adjust according to MAXCHANNELS, the output from the + // reverb engine is not so scalable. + SamplesOut[index][FRONT_LEFT] = (SamplesOut[index][FRONT_LEFT] + + ALfpMult((ALfpMult(State->Early.PanGain[FRONT_LEFT],early[0]) + + ALfpMult(State->Late.PanGain[FRONT_LEFT],late[0])), gain)); + SamplesOut[index][FRONT_RIGHT] = (SamplesOut[index][FRONT_RIGHT] + + ALfpMult((ALfpMult(State->Early.PanGain[FRONT_RIGHT],early[1]) + + ALfpMult(State->Late.PanGain[FRONT_RIGHT],late[1])), gain)); +#ifdef APPORTABLE_OPTIMIZED_OUT + SamplesOut[index][FRONT_CENTER] = (SamplesOut[index][FRONT_CENTER] + + ALfpMult((ALfpMult(State->Early.PanGain[FRONT_LEFT],early[3]) + + ALfpMult(State->Late.PanGain[FRONT_CENTER],late[3])), gain)); + SamplesOut[index][SIDE_LEFT] = (SamplesOut[index][SIDE_LEFT] + + ALfpMult((ALfpMult(State->Early.PanGain[SIDE_LEFT],early[0]) + + ALfpMult(State->Late.PanGain[SIDE_LEFT],late[0])), gain)); + SamplesOut[index][SIDE_RIGHT] = (SamplesOut[index][SIDE_RIGHT] + + ALfpMult((ALfpMult(State->Early.PanGain[SIDE_RIGHT],early[1]) + + ALfpMult(State->Late.PanGain[SIDE_RIGHT],late[1])), gain)); + SamplesOut[index][BACK_LEFT] = (SamplesOut[index][BACK_LEFT] + + ALfpMult((ALfpMult(State->Early.PanGain[BACK_LEFT],early[0]) + + ALfpMult(State->Late.PanGain[BACK_LEFT],late[0])), gain)); + SamplesOut[index][BACK_RIGHT] = (SamplesOut[index][BACK_RIGHT] + + ALfpMult((ALfpMult(State->Early.PanGain[BACK_RIGHT],early[1]) + + ALfpMult(State->Late.PanGain[BACK_RIGHT],late[1])), gain)); + SamplesOut[index][BACK_CENTER] = (SamplesOut[index][BACK_CENTER] + + ALfpMult((ALfpMult(State->Early.PanGain[BACK_CENTER],early[2]) + + ALfpMult(State->Late.PanGain[BACK_CENTER],late[2])), gain)); +#endif + + } +} + +// This creates the reverb state. It should be called only when the reverb +// effect is loaded into a slot that doesn't already have a reverb effect. +ALeffectState *VerbCreate(void) +{ + ALverbState *State = NULL; + ALuint index; + + State = malloc(sizeof(ALverbState)); + if(!State) + return NULL; + + State->state.Destroy = VerbDestroy; + State->state.DeviceUpdate = VerbDeviceUpdate; + State->state.Update = VerbUpdate; + State->state.Process = VerbProcess; + + State->TotalSamples = 0; + State->SampleBuffer = NULL; + + State->LpFilter.coeff = int2ALfp(0); + State->LpFilter.history[0] = int2ALfp(0); + State->LpFilter.history[1] = int2ALfp(0); + + State->Mod.Delay.Mask = 0; + State->Mod.Delay.Line = NULL; + State->Mod.Index = 0; + State->Mod.Range = 1; + State->Mod.Depth = int2ALfp(0); + State->Mod.Coeff = int2ALfp(0); + State->Mod.Filter = int2ALfp(0); + + State->Delay.Mask = 0; + State->Delay.Line = NULL; + State->DelayTap[0] = 0; + State->DelayTap[1] = 0; + + State->Early.Gain = int2ALfp(0); + for(index = 0;index < 4;index++) + { + State->Early.Coeff[index] = int2ALfp(0); + State->Early.Delay[index].Mask = 0; + State->Early.Delay[index].Line = NULL; + State->Early.Offset[index] = 0; + } + + State->Decorrelator.Mask = 0; + State->Decorrelator.Line = NULL; + State->DecoTap[0] = 0; + State->DecoTap[1] = 0; + State->DecoTap[2] = 0; + + State->Late.Gain = int2ALfp(0); + State->Late.DensityGain = int2ALfp(0); + State->Late.ApFeedCoeff = int2ALfp(0); + State->Late.MixCoeff = int2ALfp(0); + for(index = 0;index < 4;index++) + { + State->Late.ApCoeff[index] = int2ALfp(0); + State->Late.ApDelay[index].Mask = 0; + State->Late.ApDelay[index].Line = NULL; + State->Late.ApOffset[index] = 0; + + State->Late.Coeff[index] = int2ALfp(0); + State->Late.Delay[index].Mask = 0; + State->Late.Delay[index].Line = NULL; + State->Late.Offset[index] = 0; + + State->Late.LpCoeff[index] = int2ALfp(0); + State->Late.LpSample[index] = int2ALfp(0); + } + + for(index = 0;index < MAXCHANNELS;index++) + { + State->Early.PanGain[index] = int2ALfp(0); + State->Late.PanGain[index] = int2ALfp(0); + } + + State->Echo.DensityGain = int2ALfp(0); + State->Echo.Delay.Mask = 0; + State->Echo.Delay.Line = NULL; + State->Echo.ApDelay.Mask = 0; + State->Echo.ApDelay.Line = NULL; + State->Echo.Coeff = int2ALfp(0); + State->Echo.ApFeedCoeff = int2ALfp(0); + State->Echo.ApCoeff = int2ALfp(0); + State->Echo.Offset = 0; + State->Echo.ApOffset = 0; + State->Echo.LpCoeff = int2ALfp(0); + State->Echo.LpSample = int2ALfp(0); + State->Echo.MixCoeff[0] = int2ALfp(0); + State->Echo.MixCoeff[1] = int2ALfp(0); + + State->Offset = 0; + + State->Gain = State->Late.PanGain; + + return &State->state; +} + +ALeffectState *EAXVerbCreate(void) +{ + ALeffectState *State = VerbCreate(); + if(State) + { + State->DeviceUpdate = EAXVerbDeviceUpdate; + State->Update = EAXVerbUpdate; + State->Process = EAXVerbProcess; + } + return State; +} diff --git a/jni/OpenAL/Alc/alcRing.c b/jni/OpenAL/Alc/alcRing.c new file mode 100644 index 0000000..3361eb6 --- /dev/null +++ b/jni/OpenAL/Alc/alcRing.c @@ -0,0 +1,131 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include + +#include "alMain.h" + + +struct RingBuffer { + ALubyte *mem; + + ALsizei frame_size; + ALsizei length; + ALint read_pos; + ALint write_pos; + + CRITICAL_SECTION cs; +}; + + +RingBuffer *CreateRingBuffer(ALsizei frame_size, ALsizei length) +{ + RingBuffer *ring = calloc(1, sizeof(*ring)); + if(ring) + { + ring->frame_size = frame_size; + ring->length = length+1; + ring->write_pos = 1; + ring->mem = malloc(ring->length * ring->frame_size); + if(!ring->mem) + { + free(ring); + ring = NULL; + } + + InitializeCriticalSection(&ring->cs); + } + return ring; +} + +void DestroyRingBuffer(RingBuffer *ring) +{ + if(ring) + { + DeleteCriticalSection(&ring->cs); + free(ring->mem); + free(ring); + } +} + +ALsizei RingBufferSize(RingBuffer *ring) +{ + ALsizei s; + + EnterCriticalSection(&ring->cs); + s = (ring->write_pos-ring->read_pos-1+ring->length) % ring->length; + LeaveCriticalSection(&ring->cs); + + return s; +} + +void WriteRingBuffer(RingBuffer *ring, const ALubyte *data, ALsizei len) +{ + int remain; + + EnterCriticalSection(&ring->cs); + + remain = (ring->read_pos-ring->write_pos+ring->length) % ring->length; + if(remain < len) len = remain; + + if(len > 0) + { + remain = ring->length - ring->write_pos; + if(remain < len) + { + memcpy(ring->mem+(ring->write_pos*ring->frame_size), data, + remain*ring->frame_size); + memcpy(ring->mem, data+(remain*ring->frame_size), + (len-remain)*ring->frame_size); + } + else + memcpy(ring->mem+(ring->write_pos*ring->frame_size), data, + len*ring->frame_size); + + ring->write_pos += len; + ring->write_pos %= ring->length; + } + + LeaveCriticalSection(&ring->cs); +} + +void ReadRingBuffer(RingBuffer *ring, ALubyte *data, ALsizei len) +{ + int remain; + + EnterCriticalSection(&ring->cs); + + remain = ring->length - ring->read_pos; + if(remain < len) + { + memcpy(data, ring->mem+(ring->read_pos*ring->frame_size), remain*ring->frame_size); + memcpy(data+(remain*ring->frame_size), ring->mem, (len-remain)*ring->frame_size); + } + else + memcpy(data, ring->mem+(ring->read_pos*ring->frame_size), len*ring->frame_size); + + ring->read_pos += len; + ring->read_pos %= ring->length; + + LeaveCriticalSection(&ring->cs); +} diff --git a/jni/OpenAL/Alc/alcThread.c b/jni/OpenAL/Alc/alcThread.c new file mode 100644 index 0000000..582dfd8 --- /dev/null +++ b/jni/OpenAL/Alc/alcThread.c @@ -0,0 +1,128 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include + +#include "alMain.h" +#include "alThunk.h" + + +#ifdef _WIN32 + +typedef struct { + ALuint (*func)(ALvoid*); + ALvoid *ptr; + HANDLE thread; +} ThreadInfo; + +static DWORD CALLBACK StarterFunc(void *ptr) +{ + ThreadInfo *inf = (ThreadInfo*)ptr; + ALint ret; + + ret = inf->func(inf->ptr); + ExitThread((DWORD)ret); + + return (DWORD)ret; +} + +ALvoid *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr) +{ + DWORD dummy; + ThreadInfo *inf = malloc(sizeof(ThreadInfo)); + if(!inf) return 0; + + inf->func = func; + inf->ptr = ptr; + + inf->thread = CreateThread(NULL, 0, StarterFunc, inf, 0, &dummy); + if(!inf->thread) + { + free(inf); + return NULL; + } + + return inf; +} + +ALuint StopThread(ALvoid *thread) +{ + ThreadInfo *inf = thread; + DWORD ret = 0; + + WaitForSingleObject(inf->thread, INFINITE); + GetExitCodeThread(inf->thread, &ret); + CloseHandle(inf->thread); + + free(inf); + + return (ALuint)ret; +} + +#else + +#include + +typedef struct { + ALuint (*func)(ALvoid*); + ALvoid *ptr; + ALuint ret; + pthread_t thread; +} ThreadInfo; + +static void *StarterFunc(void *ptr) +{ + ThreadInfo *inf = (ThreadInfo*)ptr; + inf->ret = inf->func(inf->ptr); + return NULL; +} + +ALvoid *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr) +{ + ThreadInfo *inf = malloc(sizeof(ThreadInfo)); + if(!inf) return NULL; + + inf->func = func; + inf->ptr = ptr; + if(pthread_create(&inf->thread, NULL, StarterFunc, inf) != 0) + { + free(inf); + return NULL; + } + + return inf; +} + +ALuint StopThread(ALvoid *thread) +{ + ThreadInfo *inf = thread; + ALuint ret; + + pthread_join(inf->thread, NULL); + ret = inf->ret; + + free(inf); + + return ret; +} + +#endif diff --git a/jni/OpenAL/Alc/alsa.c b/jni/OpenAL/Alc/alsa.c new file mode 100644 index 0000000..a7e8758 --- /dev/null +++ b/jni/OpenAL/Alc/alsa.c @@ -0,0 +1,1048 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#ifdef HAVE_DLFCN_H +#include +#endif +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#include + + +typedef struct { + snd_pcm_t *pcmHandle; + + ALvoid *buffer; + ALsizei size; + + ALboolean doCapture; + RingBuffer *ring; + + volatile int killNow; + ALvoid *thread; +} alsa_data; + +typedef struct { + ALCchar *name; + int card, dev; +} DevMap; + +static void *alsa_handle; +#define MAKE_FUNC(f) static typeof(f) * p##f +MAKE_FUNC(snd_strerror); +MAKE_FUNC(snd_pcm_open); +MAKE_FUNC(snd_pcm_close); +MAKE_FUNC(snd_pcm_nonblock); +MAKE_FUNC(snd_pcm_frames_to_bytes); +MAKE_FUNC(snd_pcm_bytes_to_frames); +MAKE_FUNC(snd_pcm_hw_params_malloc); +MAKE_FUNC(snd_pcm_hw_params_free); +MAKE_FUNC(snd_pcm_hw_params_any); +MAKE_FUNC(snd_pcm_hw_params_set_access); +MAKE_FUNC(snd_pcm_hw_params_set_format); +MAKE_FUNC(snd_pcm_hw_params_set_channels); +MAKE_FUNC(snd_pcm_hw_params_set_periods_near); +MAKE_FUNC(snd_pcm_hw_params_set_rate_near); +MAKE_FUNC(snd_pcm_hw_params_set_rate); +MAKE_FUNC(snd_pcm_hw_params_set_rate_resample); +MAKE_FUNC(snd_pcm_hw_params_set_buffer_time_near); +MAKE_FUNC(snd_pcm_hw_params_set_period_time_near); +MAKE_FUNC(snd_pcm_hw_params_set_buffer_size_near); +MAKE_FUNC(snd_pcm_hw_params_set_period_size_near); +MAKE_FUNC(snd_pcm_hw_params_set_buffer_size_min); +MAKE_FUNC(snd_pcm_hw_params_get_buffer_size); +MAKE_FUNC(snd_pcm_hw_params_get_period_size); +MAKE_FUNC(snd_pcm_hw_params_get_access); +MAKE_FUNC(snd_pcm_hw_params_get_periods); +MAKE_FUNC(snd_pcm_hw_params); +MAKE_FUNC(snd_pcm_sw_params_malloc); +MAKE_FUNC(snd_pcm_sw_params_current); +MAKE_FUNC(snd_pcm_sw_params_set_avail_min); +MAKE_FUNC(snd_pcm_sw_params); +MAKE_FUNC(snd_pcm_sw_params_free); +MAKE_FUNC(snd_pcm_prepare); +MAKE_FUNC(snd_pcm_start); +MAKE_FUNC(snd_pcm_resume); +MAKE_FUNC(snd_pcm_wait); +MAKE_FUNC(snd_pcm_state); +MAKE_FUNC(snd_pcm_avail_update); +MAKE_FUNC(snd_pcm_areas_silence); +MAKE_FUNC(snd_pcm_mmap_begin); +MAKE_FUNC(snd_pcm_mmap_commit); +MAKE_FUNC(snd_pcm_readi); +MAKE_FUNC(snd_pcm_writei); +MAKE_FUNC(snd_pcm_drain); +MAKE_FUNC(snd_pcm_recover); +MAKE_FUNC(snd_pcm_info_malloc); +MAKE_FUNC(snd_pcm_info_free); +MAKE_FUNC(snd_pcm_info_set_device); +MAKE_FUNC(snd_pcm_info_set_subdevice); +MAKE_FUNC(snd_pcm_info_set_stream); +MAKE_FUNC(snd_pcm_info_get_name); +MAKE_FUNC(snd_ctl_pcm_next_device); +MAKE_FUNC(snd_ctl_pcm_info); +MAKE_FUNC(snd_ctl_open); +MAKE_FUNC(snd_ctl_close); +MAKE_FUNC(snd_ctl_card_info_malloc); +MAKE_FUNC(snd_ctl_card_info_free); +MAKE_FUNC(snd_ctl_card_info); +MAKE_FUNC(snd_ctl_card_info_get_name); +MAKE_FUNC(snd_card_next); +#undef MAKE_FUNC + + +static const ALCchar alsaDevice[] = "ALSA Default"; +static DevMap *allDevNameMap; +static ALuint numDevNames; +static DevMap *allCaptureDevNameMap; +static ALuint numCaptureDevNames; + + +void *alsa_load(void) +{ + if(!alsa_handle) + { + char *str; + +#ifdef HAVE_DLFCN_H + alsa_handle = dlopen("libasound.so.2", RTLD_NOW); + if(!alsa_handle) + return NULL; + dlerror(); + +#define LOAD_FUNC(f) do { \ + p##f = dlsym(alsa_handle, #f); \ + if((str=dlerror()) != NULL) \ + { \ + dlclose(alsa_handle); \ + alsa_handle = NULL; \ + AL_PRINT("Could not load %s from libasound.so.2: %s\n", #f, str); \ + return NULL; \ + } \ +} while(0) +#else + str = NULL; + alsa_handle = (void*)0xDEADBEEF; +#define LOAD_FUNC(f) p##f = f +#endif + +LOAD_FUNC(snd_strerror); +LOAD_FUNC(snd_pcm_open); +LOAD_FUNC(snd_pcm_close); +LOAD_FUNC(snd_pcm_nonblock); +LOAD_FUNC(snd_pcm_frames_to_bytes); +LOAD_FUNC(snd_pcm_bytes_to_frames); +LOAD_FUNC(snd_pcm_hw_params_malloc); +LOAD_FUNC(snd_pcm_hw_params_free); +LOAD_FUNC(snd_pcm_hw_params_any); +LOAD_FUNC(snd_pcm_hw_params_set_access); +LOAD_FUNC(snd_pcm_hw_params_set_format); +LOAD_FUNC(snd_pcm_hw_params_set_channels); +LOAD_FUNC(snd_pcm_hw_params_set_periods_near); +LOAD_FUNC(snd_pcm_hw_params_set_rate_near); +LOAD_FUNC(snd_pcm_hw_params_set_rate); +LOAD_FUNC(snd_pcm_hw_params_set_rate_resample); +LOAD_FUNC(snd_pcm_hw_params_set_buffer_time_near); +LOAD_FUNC(snd_pcm_hw_params_set_period_time_near); +LOAD_FUNC(snd_pcm_hw_params_set_buffer_size_near); +LOAD_FUNC(snd_pcm_hw_params_set_buffer_size_min); +LOAD_FUNC(snd_pcm_hw_params_set_period_size_near); +LOAD_FUNC(snd_pcm_hw_params_get_buffer_size); +LOAD_FUNC(snd_pcm_hw_params_get_period_size); +LOAD_FUNC(snd_pcm_hw_params_get_access); +LOAD_FUNC(snd_pcm_hw_params_get_periods); +LOAD_FUNC(snd_pcm_hw_params); +LOAD_FUNC(snd_pcm_sw_params_malloc); +LOAD_FUNC(snd_pcm_sw_params_current); +LOAD_FUNC(snd_pcm_sw_params_set_avail_min); +LOAD_FUNC(snd_pcm_sw_params); +LOAD_FUNC(snd_pcm_sw_params_free); +LOAD_FUNC(snd_pcm_prepare); +LOAD_FUNC(snd_pcm_start); +LOAD_FUNC(snd_pcm_resume); +LOAD_FUNC(snd_pcm_wait); +LOAD_FUNC(snd_pcm_state); +LOAD_FUNC(snd_pcm_avail_update); +LOAD_FUNC(snd_pcm_areas_silence); +LOAD_FUNC(snd_pcm_mmap_begin); +LOAD_FUNC(snd_pcm_mmap_commit); +LOAD_FUNC(snd_pcm_readi); +LOAD_FUNC(snd_pcm_writei); +LOAD_FUNC(snd_pcm_drain); +LOAD_FUNC(snd_pcm_recover); + +LOAD_FUNC(snd_pcm_info_malloc); +LOAD_FUNC(snd_pcm_info_free); +LOAD_FUNC(snd_pcm_info_set_device); +LOAD_FUNC(snd_pcm_info_set_subdevice); +LOAD_FUNC(snd_pcm_info_set_stream); +LOAD_FUNC(snd_pcm_info_get_name); +LOAD_FUNC(snd_ctl_pcm_next_device); +LOAD_FUNC(snd_ctl_pcm_info); +LOAD_FUNC(snd_ctl_open); +LOAD_FUNC(snd_ctl_close); +LOAD_FUNC(snd_ctl_card_info_malloc); +LOAD_FUNC(snd_ctl_card_info_free); +LOAD_FUNC(snd_ctl_card_info); +LOAD_FUNC(snd_ctl_card_info_get_name); +LOAD_FUNC(snd_card_next); + +#undef LOAD_FUNC + } + return alsa_handle; +} + +static DevMap *probe_devices(snd_pcm_stream_t stream, ALuint *count) +{ + snd_ctl_t *handle; + int card, err, dev, idx; + snd_ctl_card_info_t *info; + snd_pcm_info_t *pcminfo; + DevMap *DevList; + char name[1024]; + + psnd_ctl_card_info_malloc(&info); + psnd_pcm_info_malloc(&pcminfo); + + card = -1; + if((err=psnd_card_next(&card)) < 0) + AL_PRINT("Failed to find a card: %s\n", psnd_strerror(err)); + + DevList = malloc(sizeof(DevMap) * 1); + DevList[0].name = strdup("ALSA Default"); + idx = 1; + while(card >= 0) + { + sprintf(name, "hw:%d", card); + if((err = psnd_ctl_open(&handle, name, 0)) < 0) + { + AL_PRINT("control open (%i): %s\n", card, psnd_strerror(err)); + goto next_card; + } + if((err = psnd_ctl_card_info(handle, info)) < 0) + { + AL_PRINT("control hardware info (%i): %s\n", card, psnd_strerror(err)); + psnd_ctl_close(handle); + goto next_card; + } + + dev = -1; + while(1) + { + const char *cname, *dname; + void *temp; + + if(psnd_ctl_pcm_next_device(handle, &dev) < 0) + AL_PRINT("snd_ctl_pcm_next_device failed\n"); + if(dev < 0) + break; + + psnd_pcm_info_set_device(pcminfo, dev); + psnd_pcm_info_set_subdevice(pcminfo, 0); + psnd_pcm_info_set_stream(pcminfo, stream); + if((err = psnd_ctl_pcm_info(handle, pcminfo)) < 0) { + if(err != -ENOENT) + AL_PRINT("control digital audio info (%i): %s\n", card, psnd_strerror(err)); + continue; + } + + temp = realloc(DevList, sizeof(DevMap) * (idx+1)); + if(temp) + { + DevList = temp; + cname = psnd_ctl_card_info_get_name(info); + dname = psnd_pcm_info_get_name(pcminfo); + snprintf(name, sizeof(name), "%s [%s] (hw:%d,%d) via ALSA", + cname, dname, card, dev); + DevList[idx].name = strdup(name); + DevList[idx].card = card; + DevList[idx].dev = dev; + idx++; + } + } + psnd_ctl_close(handle); + next_card: + if(psnd_card_next(&card) < 0) { + AL_PRINT("snd_card_next failed\n"); + break; + } + } + + psnd_pcm_info_free(pcminfo); + psnd_ctl_card_info_free(info); + + *count = idx; + return DevList; +} + + +static int xrun_recovery(snd_pcm_t *handle, int err) +{ + err = psnd_pcm_recover(handle, err, 1); + if(err < 0) + AL_PRINT("recover failed: %s\n", psnd_strerror(err)); + return err; +} + +static int verify_state(snd_pcm_t *handle) +{ + snd_pcm_state_t state = psnd_pcm_state(handle); + if(state == SND_PCM_STATE_DISCONNECTED) + return -ENODEV; + if(state == SND_PCM_STATE_XRUN) + { + int err = xrun_recovery(handle, -EPIPE); + if(err < 0) return err; + } + else if(state == SND_PCM_STATE_SUSPENDED) + { + int err = xrun_recovery(handle, -ESTRPIPE); + if(err < 0) return err; + } + + return state; +} + + +static ALuint ALSAProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + alsa_data *data = (alsa_data*)pDevice->ExtraData; + const snd_pcm_channel_area_t *areas = NULL; + snd_pcm_sframes_t avail, commitres; + snd_pcm_uframes_t offset, frames; + char *WritePtr; + int err; + + SetRTPriority(); + + while(!data->killNow) + { + int state = verify_state(data->pcmHandle); + if(state < 0) + { + AL_PRINT("Invalid state detected: %s\n", psnd_strerror(state)); + aluHandleDisconnect(pDevice); + break; + } + + avail = psnd_pcm_avail_update(data->pcmHandle); + if(avail < 0) + { + AL_PRINT("available update failed: %s\n", psnd_strerror(avail)); + continue; + } + + // make sure there's frames to process + if((snd_pcm_uframes_t)avail < pDevice->UpdateSize) + { + if(state != SND_PCM_STATE_RUNNING) + { + err = psnd_pcm_start(data->pcmHandle); + if(err < 0) + { + AL_PRINT("start failed: %s\n", psnd_strerror(err)); + continue; + } + } + if(psnd_pcm_wait(data->pcmHandle, 1000) == 0) + AL_PRINT("Wait timeout... buffer size too low?\n"); + continue; + } + avail -= avail%pDevice->UpdateSize; + + // it is possible that contiguous areas are smaller, thus we use a loop + while(avail > 0) + { + frames = avail; + + err = psnd_pcm_mmap_begin(data->pcmHandle, &areas, &offset, &frames); + if(err < 0) + { + AL_PRINT("mmap begin error: %s\n", psnd_strerror(err)); + break; + } + + WritePtr = (char*)areas->addr + (offset * areas->step / 8); + aluMixData(pDevice, WritePtr, frames); + + commitres = psnd_pcm_mmap_commit(data->pcmHandle, offset, frames); + if(commitres < 0 || (commitres-frames) != 0) + { + AL_PRINT("mmap commit error: %s\n", + psnd_strerror(commitres >= 0 ? -EPIPE : commitres)); + break; + } + + avail -= frames; + } + } + + return 0; +} + +static ALuint ALSANoMMapProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + alsa_data *data = (alsa_data*)pDevice->ExtraData; + snd_pcm_sframes_t avail; + char *WritePtr; + + SetRTPriority(); + + while(!data->killNow) + { + int state = verify_state(data->pcmHandle); + if(state < 0) + { + AL_PRINT("Invalid state detected: %s\n", psnd_strerror(state)); + aluHandleDisconnect(pDevice); + break; + } + + WritePtr = data->buffer; + avail = data->size / psnd_pcm_frames_to_bytes(data->pcmHandle, 1); + aluMixData(pDevice, WritePtr, avail); + + while(avail > 0) + { + int ret = psnd_pcm_writei(data->pcmHandle, WritePtr, avail); + switch (ret) + { + case -EAGAIN: + continue; + case -ESTRPIPE: + case -EPIPE: + case -EINTR: + ret = psnd_pcm_recover(data->pcmHandle, ret, 1); + if(ret < 0) + avail = 0; + break; + default: + if (ret >= 0) + { + WritePtr += psnd_pcm_frames_to_bytes(data->pcmHandle, ret); + avail -= ret; + } + break; + } + if (ret < 0) + { + ret = psnd_pcm_prepare(data->pcmHandle); + if(ret < 0) + break; + } + } + } + + return 0; +} + +static ALCboolean alsa_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + alsa_data *data; + char driver[64]; + int i; + + if(!alsa_load()) + return ALC_FALSE; + + strncpy(driver, GetConfigValue("alsa", "device", "default"), sizeof(driver)-1); + driver[sizeof(driver)-1] = 0; + + if(!deviceName) + deviceName = alsaDevice; + else if(strcmp(deviceName, alsaDevice) != 0) + { + size_t idx; + + if(!allDevNameMap) + allDevNameMap = probe_devices(SND_PCM_STREAM_PLAYBACK, &numDevNames); + + for(idx = 0;idx < numDevNames;idx++) + { + if(allDevNameMap[idx].name && + strcmp(deviceName, allDevNameMap[idx].name) == 0) + { + if(idx > 0) + sprintf(driver, "hw:%d,%d", allDevNameMap[idx].card, allDevNameMap[idx].dev); + break; + } + } + if(idx == numDevNames) + return ALC_FALSE; + } + + data = (alsa_data*)calloc(1, sizeof(alsa_data)); + + i = psnd_pcm_open(&data->pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if(i >= 0) + { + i = psnd_pcm_nonblock(data->pcmHandle, 0); + if(i < 0) + psnd_pcm_close(data->pcmHandle); + } + if(i < 0) + { + free(data); + AL_PRINT("Could not open playback device '%s': %s\n", driver, psnd_strerror(i)); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void alsa_close_playback(ALCdevice *device) +{ + alsa_data *data = (alsa_data*)device->ExtraData; + + psnd_pcm_close(data->pcmHandle); + free(data); + device->ExtraData = NULL; +} + +static ALCboolean alsa_reset_playback(ALCdevice *device) +{ + alsa_data *data = (alsa_data*)device->ExtraData; + snd_pcm_uframes_t periodSizeInFrames; + unsigned int periodLen, bufferLen; + snd_pcm_sw_params_t *sp = NULL; + snd_pcm_hw_params_t *p = NULL; + snd_pcm_access_t access; + snd_pcm_format_t format; + unsigned int periods; + unsigned int rate; + int allowmmap; + char *err; + int i; + + + format = -1; + switch(device->FmtType) + { + case DevFmtByte: + format = SND_PCM_FORMAT_S8; + break; + case DevFmtUByte: + format = SND_PCM_FORMAT_U8; + break; + case DevFmtShort: + format = SND_PCM_FORMAT_S16; + break; + case DevFmtUShort: + format = SND_PCM_FORMAT_U16; + break; + case DevFmtFloat: + format = SND_PCM_FORMAT_FLOAT; + break; + } + + allowmmap = GetConfigValueBool("alsa", "mmap", 1); + periods = device->NumUpdates; + periodLen = (ALuint64)device->UpdateSize * 1000000 / device->Frequency; + bufferLen = periodLen * periods; + rate = device->Frequency; + + err = NULL; + psnd_pcm_hw_params_malloc(&p); + + if((i=psnd_pcm_hw_params_any(data->pcmHandle, p)) < 0) + err = "any"; + /* set interleaved access */ + if(i >= 0 && (!allowmmap || (i=psnd_pcm_hw_params_set_access(data->pcmHandle, p, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0)) + { + if(periods > 2) + { + periods--; + bufferLen = periodLen * periods; + } + if((i=psnd_pcm_hw_params_set_access(data->pcmHandle, p, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + err = "set access"; + } + /* set format (implicitly sets sample bits) */ + if(i >= 0 && (i=psnd_pcm_hw_params_set_format(data->pcmHandle, p, format)) < 0) + { + device->FmtType = DevFmtFloat; + if(format == SND_PCM_FORMAT_FLOAT || + (i=psnd_pcm_hw_params_set_format(data->pcmHandle, p, SND_PCM_FORMAT_FLOAT)) < 0) + { + device->FmtType = DevFmtShort; + if(format == SND_PCM_FORMAT_S16 || + (i=psnd_pcm_hw_params_set_format(data->pcmHandle, p, SND_PCM_FORMAT_S16)) < 0) + { + device->FmtType = DevFmtUByte; + if(format == SND_PCM_FORMAT_U8 || + (i=psnd_pcm_hw_params_set_format(data->pcmHandle, p, SND_PCM_FORMAT_U8)) < 0) + err = "set format"; + } + } + } + /* set channels (implicitly sets frame bits) */ + if(i >= 0 && (i=psnd_pcm_hw_params_set_channels(data->pcmHandle, p, ChannelsFromDevFmt(device->FmtChans))) < 0) + { + device->FmtChans = DevFmtStereo; + if((i=psnd_pcm_hw_params_set_channels(data->pcmHandle, p, 2)) < 0) + { + device->FmtChans = DevFmtMono; + if((i=psnd_pcm_hw_params_set_channels(data->pcmHandle, p, 1)) < 0) + err = "set channels"; + } + } + if(i >= 0 && (i=psnd_pcm_hw_params_set_rate_resample(data->pcmHandle, p, 0)) < 0) + { + AL_PRINT("Failed to disable ALSA resampler\n"); + i = 0; + } + /* set rate (implicitly constrains period/buffer parameters) */ + if(i >= 0 && (i=psnd_pcm_hw_params_set_rate_near(data->pcmHandle, p, &rate, NULL)) < 0) + err = "set rate near"; + /* set buffer time (implicitly constrains period/buffer parameters) */ + if(i >= 0 && (i=psnd_pcm_hw_params_set_buffer_time_near(data->pcmHandle, p, &bufferLen, NULL)) < 0) + err = "set buffer time near"; + /* set period time in frame units (implicitly sets buffer size/bytes/time and period size/bytes) */ + if(i >= 0 && (i=psnd_pcm_hw_params_set_period_time_near(data->pcmHandle, p, &periodLen, NULL)) < 0) + err = "set period time near"; + /* install and prepare hardware configuration */ + if(i >= 0 && (i=psnd_pcm_hw_params(data->pcmHandle, p)) < 0) + err = "set params"; + if(i >= 0 && (i=psnd_pcm_hw_params_get_access(p, &access)) < 0) + err = "get access"; + if(i >= 0 && (i=psnd_pcm_hw_params_get_period_size(p, &periodSizeInFrames, NULL)) < 0) + err = "get period size"; + if(i >= 0 && (i=psnd_pcm_hw_params_get_periods(p, &periods, NULL)) < 0) + err = "get periods"; + if(i < 0) + { + AL_PRINT("%s failed: %s\n", err, psnd_strerror(i)); + psnd_pcm_hw_params_free(p); + return ALC_FALSE; + } + + psnd_pcm_hw_params_free(p); + + err = NULL; + psnd_pcm_sw_params_malloc(&sp); + + if((i=psnd_pcm_sw_params_current(data->pcmHandle, sp)) != 0) + err = "sw current"; + if(i == 0 && (i=psnd_pcm_sw_params_set_avail_min(data->pcmHandle, sp, periodSizeInFrames)) != 0) + err = "sw set avail min"; + if(i == 0 && (i=psnd_pcm_sw_params(data->pcmHandle, sp)) != 0) + err = "sw set params"; + if(i != 0) + { + AL_PRINT("%s failed: %s\n", err, psnd_strerror(i)); + psnd_pcm_sw_params_free(sp); + return ALC_FALSE; + } + + psnd_pcm_sw_params_free(sp); + + device->Frequency = rate; + + SetDefaultChannelOrder(device); + + data->size = psnd_pcm_frames_to_bytes(data->pcmHandle, periodSizeInFrames); + if(access == SND_PCM_ACCESS_RW_INTERLEAVED) + { + /* Increase periods by one, since the temp buffer counts as an extra + * period */ + periods++; + data->buffer = malloc(data->size); + if(!data->buffer) + { + AL_PRINT("buffer malloc failed\n"); + return ALC_FALSE; + } + device->UpdateSize = periodSizeInFrames; + device->NumUpdates = periods; + data->thread = StartThread(ALSANoMMapProc, device); + } + else + { + i = psnd_pcm_prepare(data->pcmHandle); + if(i < 0) + { + AL_PRINT("prepare error: %s\n", psnd_strerror(i)); + return ALC_FALSE; + } + device->UpdateSize = periodSizeInFrames; + device->NumUpdates = periods; + data->thread = StartThread(ALSAProc, device); + } + if(data->thread == NULL) + { + AL_PRINT("Could not create playback thread\n"); + free(data->buffer); + data->buffer = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void alsa_stop_playback(ALCdevice *device) +{ + alsa_data *data = (alsa_data*)device->ExtraData; + + if(data->thread) + { + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + } + data->killNow = 0; + free(data->buffer); + data->buffer = NULL; +} + + +static ALCboolean alsa_open_capture(ALCdevice *pDevice, const ALCchar *deviceName) +{ + snd_pcm_hw_params_t *p; + snd_pcm_uframes_t bufferSizeInFrames; + snd_pcm_format_t format; + ALuint frameSize; + alsa_data *data; + char driver[64]; + char *err; + int i; + + if(!alsa_load()) + return ALC_FALSE; + + strncpy(driver, GetConfigValue("alsa", "capture", "default"), sizeof(driver)-1); + driver[sizeof(driver)-1] = 0; + + if(!allCaptureDevNameMap) + allCaptureDevNameMap = probe_devices(SND_PCM_STREAM_CAPTURE, &numCaptureDevNames); + + if(!deviceName) + deviceName = allCaptureDevNameMap[0].name; + else + { + size_t idx; + + for(idx = 0;idx < numCaptureDevNames;idx++) + { + if(allCaptureDevNameMap[idx].name && + strcmp(deviceName, allCaptureDevNameMap[idx].name) == 0) + { + if(idx > 0) + sprintf(driver, "plughw:%d,%d", allCaptureDevNameMap[idx].card, allCaptureDevNameMap[idx].dev); + break; + } + } + if(idx == numCaptureDevNames) + return ALC_FALSE; + } + + data = (alsa_data*)calloc(1, sizeof(alsa_data)); + + i = psnd_pcm_open(&data->pcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + if(i < 0) + { + AL_PRINT("Could not open capture device '%s': %s\n", driver, psnd_strerror(i)); + free(data); + return ALC_FALSE; + } + + format = -1; + switch(pDevice->FmtType) + { + case DevFmtByte: + format = SND_PCM_FORMAT_S8; + break; + case DevFmtUByte: + format = SND_PCM_FORMAT_U8; + break; + case DevFmtShort: + format = SND_PCM_FORMAT_S16; + break; + case DevFmtUShort: + format = SND_PCM_FORMAT_U16; + break; + case DevFmtFloat: + format = SND_PCM_FORMAT_FLOAT; + break; + } + + err = NULL; + bufferSizeInFrames = pDevice->UpdateSize * pDevice->NumUpdates; + psnd_pcm_hw_params_malloc(&p); + + if((i=psnd_pcm_hw_params_any(data->pcmHandle, p)) < 0) + err = "any"; + /* set interleaved access */ + if(i >= 0 && (i=psnd_pcm_hw_params_set_access(data->pcmHandle, p, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + err = "set access"; + /* set format (implicitly sets sample bits) */ + if(i >= 0 && (i=psnd_pcm_hw_params_set_format(data->pcmHandle, p, format)) < 0) + err = "set format"; + /* set channels (implicitly sets frame bits) */ + if(i >= 0 && (i=psnd_pcm_hw_params_set_channels(data->pcmHandle, p, ChannelsFromDevFmt(pDevice->FmtChans))) < 0) + err = "set channels"; + /* set rate (implicitly constrains period/buffer parameters) */ + if(i >= 0 && (i=psnd_pcm_hw_params_set_rate(data->pcmHandle, p, pDevice->Frequency, 0)) < 0) + err = "set rate near"; + /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ + if(i >= 0 && (i=psnd_pcm_hw_params_set_buffer_size_near(data->pcmHandle, p, &bufferSizeInFrames)) < 0) + err = "set buffer size near"; + /* install and prepare hardware configuration */ + if(i >= 0 && (i=psnd_pcm_hw_params(data->pcmHandle, p)) < 0) + err = "set params"; + if(i < 0) + { + AL_PRINT("%s failed: %s\n", err, psnd_strerror(i)); + psnd_pcm_hw_params_free(p); + goto error; + } + + if((i=psnd_pcm_hw_params_get_period_size(p, &bufferSizeInFrames, NULL)) < 0) + { + AL_PRINT("get size failed: %s\n", psnd_strerror(i)); + psnd_pcm_hw_params_free(p); + goto error; + } + + psnd_pcm_hw_params_free(p); + + frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + data->ring = CreateRingBuffer(frameSize, pDevice->UpdateSize*pDevice->NumUpdates); + if(!data->ring) + { + AL_PRINT("ring buffer create failed\n"); + goto error; + } + + data->size = psnd_pcm_frames_to_bytes(data->pcmHandle, bufferSizeInFrames); + data->buffer = malloc(data->size); + if(!data->buffer) + { + AL_PRINT("buffer malloc failed\n"); + goto error; + } + + pDevice->szDeviceName = strdup(deviceName); + + pDevice->ExtraData = data; + return ALC_TRUE; + +error: + free(data->buffer); + DestroyRingBuffer(data->ring); + psnd_pcm_close(data->pcmHandle); + free(data); + + pDevice->ExtraData = NULL; + return ALC_FALSE; +} + +static void alsa_close_capture(ALCdevice *pDevice) +{ + alsa_data *data = (alsa_data*)pDevice->ExtraData; + + psnd_pcm_close(data->pcmHandle); + DestroyRingBuffer(data->ring); + + free(data->buffer); + free(data); + pDevice->ExtraData = NULL; +} + +static void alsa_start_capture(ALCdevice *Device) +{ + alsa_data *data = (alsa_data*)Device->ExtraData; + int err; + + err = psnd_pcm_start(data->pcmHandle); + if(err < 0) + { + AL_PRINT("start failed: %s\n", psnd_strerror(err)); + aluHandleDisconnect(Device); + } + else + data->doCapture = AL_TRUE; +} + +static void alsa_stop_capture(ALCdevice *Device) +{ + alsa_data *data = (alsa_data*)Device->ExtraData; + psnd_pcm_drain(data->pcmHandle); + data->doCapture = AL_FALSE; +} + +static ALCuint alsa_available_samples(ALCdevice *Device) +{ + alsa_data *data = (alsa_data*)Device->ExtraData; + snd_pcm_sframes_t avail; + + avail = (Device->Connected ? psnd_pcm_avail_update(data->pcmHandle) : 0); + if(avail < 0) + { + AL_PRINT("avail update failed: %s\n", psnd_strerror(avail)); + + if((avail=psnd_pcm_recover(data->pcmHandle, avail, 1)) >= 0) + { + if(data->doCapture) + avail = psnd_pcm_start(data->pcmHandle); + if(avail >= 0) + avail = psnd_pcm_avail_update(data->pcmHandle); + } + if(avail < 0) + { + AL_PRINT("restore error: %s\n", psnd_strerror(avail)); + aluHandleDisconnect(Device); + } + } + while(avail > 0) + { + snd_pcm_sframes_t amt; + + amt = psnd_pcm_bytes_to_frames(data->pcmHandle, data->size); + if(avail < amt) amt = avail; + + amt = psnd_pcm_readi(data->pcmHandle, data->buffer, amt); + if(amt < 0) + { + AL_PRINT("read error: %s\n", psnd_strerror(amt)); + + if(amt == -EAGAIN) + continue; + if((amt=psnd_pcm_recover(data->pcmHandle, amt, 1)) >= 0) + { + if(data->doCapture) + amt = psnd_pcm_start(data->pcmHandle); + if(amt >= 0) + amt = psnd_pcm_avail_update(data->pcmHandle); + } + if(amt < 0) + { + AL_PRINT("restore error: %s\n", psnd_strerror(amt)); + aluHandleDisconnect(Device); + break; + } + avail = amt; + continue; + } + + WriteRingBuffer(data->ring, data->buffer, amt); + avail -= amt; + } + + return RingBufferSize(data->ring); +} + +static void alsa_capture_samples(ALCdevice *Device, ALCvoid *Buffer, ALCuint Samples) +{ + alsa_data *data = (alsa_data*)Device->ExtraData; + + if(Samples <= alsa_available_samples(Device)) + ReadRingBuffer(data->ring, Buffer, Samples); + else + alcSetError(Device, ALC_INVALID_VALUE); +} + + +BackendFuncs alsa_funcs = { + alsa_open_playback, + alsa_close_playback, + alsa_reset_playback, + alsa_stop_playback, + alsa_open_capture, + alsa_close_capture, + alsa_start_capture, + alsa_stop_capture, + alsa_capture_samples, + alsa_available_samples +}; + +void alc_alsa_init(BackendFuncs *func_list) +{ + *func_list = alsa_funcs; +} + +void alc_alsa_deinit(void) +{ + ALuint i; + + for(i = 0;i < numDevNames;++i) + free(allDevNameMap[i].name); + free(allDevNameMap); + allDevNameMap = NULL; + numDevNames = 0; + + for(i = 0;i < numCaptureDevNames;++i) + free(allCaptureDevNameMap[i].name); + free(allCaptureDevNameMap); + allCaptureDevNameMap = NULL; + numCaptureDevNames = 0; + + if(alsa_handle) + { +#ifdef HAVE_DLFCN_H + dlclose(alsa_handle); +#endif + alsa_handle = NULL; + } +} + +void alc_alsa_probe(int type) +{ + ALuint i; + + if(!alsa_load()) + return; + + if(type == DEVICE_PROBE) + AppendDeviceList(alsaDevice); + else if(type == ALL_DEVICE_PROBE) + { + for(i = 0;i < numDevNames;++i) + free(allDevNameMap[i].name); + + free(allDevNameMap); + allDevNameMap = probe_devices(SND_PCM_STREAM_PLAYBACK, &numDevNames); + + for(i = 0;i < numDevNames;++i) + AppendAllDeviceList(allDevNameMap[i].name); + } + else if(type == CAPTURE_DEVICE_PROBE) + { + for(i = 0;i < numCaptureDevNames;++i) + free(allCaptureDevNameMap[i].name); + + free(allCaptureDevNameMap); + allCaptureDevNameMap = probe_devices(SND_PCM_STREAM_CAPTURE, &numCaptureDevNames); + + for(i = 0;i < numCaptureDevNames;++i) + AppendCaptureDeviceList(allCaptureDevNameMap[i].name); + } +} diff --git a/jni/OpenAL/Alc/apportable_openal_funcs.h b/jni/OpenAL/Alc/apportable_openal_funcs.h new file mode 100644 index 0000000..f970e60 --- /dev/null +++ b/jni/OpenAL/Alc/apportable_openal_funcs.h @@ -0,0 +1,11 @@ +typedef struct { + void (*alc_android_suspend)(); + void (*alc_android_resume)(); + void (*alc_android_set_java_vm)(JavaVM*); +#ifdef HAVE_OPENSLES + SLEngineItf (*alc_opensles_get_native_audio_engine_engine)(); + SLEngineItf (*alc_opensles_get_native_audio_output_mix)(); + SLresult (*alc_opensles_create_native_audio_engine)(); +#endif +} ApportableOpenALFuncs; +ApportableOpenALFuncs apportableOpenALFuncs; diff --git a/jni/OpenAL/Alc/audiotrack.c b/jni/OpenAL/Alc/audiotrack.c new file mode 100755 index 0000000..536b628 --- /dev/null +++ b/jni/OpenAL/Alc/audiotrack.c @@ -0,0 +1,320 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#include "apportable_openal_funcs.h" + +static const ALCchar android_device[] = "Android Default"; + +static JavaVM* javaVM = NULL; +static JNIEnv* env; + +static jclass cAudioTrack = NULL; + +static jmethodID mAudioTrack; +static jmethodID mGetMinBufferSize; +static jmethodID mPlay; +static jmethodID mPause; +static jmethodID mStop; +static jmethodID mRelease; +static jmethodID mWrite; + +static int suspended = 0; +static int audioTrackPlaying = 0; + +typedef struct +{ + pthread_t thread; + volatile int running; +} AndroidData; + +#define STREAM_MUSIC 3 +#define CHANNEL_CONFIGURATION_MONO 2 +#define CHANNEL_CONFIGURATION_STEREO 3 +#define ENCODING_PCM_8BIT 3 +#define ENCODING_PCM_16BIT 2 +#define MODE_STREAM 1 + +static void* thread_function(void* arg) +{ + ALCdevice* device = (ALCdevice*)arg; + AndroidData* data = (AndroidData*)device->ExtraData; + + JNIEnv* env; + (*javaVM)->AttachCurrentThread(javaVM, &env, NULL); + + (*env)->PushLocalFrame(env, 2); + + int sampleRateInHz = device->Frequency; + int channelConfig = ChannelsFromDevFmt(device->FmtChans) == 1 ? CHANNEL_CONFIGURATION_MONO : CHANNEL_CONFIGURATION_STEREO; + int audioFormat = BytesFromDevFmt(device->FmtType) == 1 ? ENCODING_PCM_8BIT : ENCODING_PCM_16BIT; + + int bufferSizeInBytes = (*env)->CallStaticIntMethod(env, cAudioTrack, + mGetMinBufferSize, sampleRateInHz, channelConfig, audioFormat); + + // Suggestion from Eric Wing + /* According to the author Martins Mozelko, I should multiply bufferSizeInBytes to tune performance. + Say, multiply by 2. + But making this number smaller seems to reduce latency... + I have tried dividing by 2, 4, and 8. 8 refuses to play any sound. + It seems that this just divides out the multiplication of NumUpdates (default=4) + which returns it to min buffer size. + bufferSizeInBytes is used in multiple places and + bufferSizeInSamples is tied directly to bufferSizeInBytes though, so we need to be careful + about what we want to change. + I'm assuming Martins is correct and this is the indeed the place we want to change it. + Dividing out the bufferSizeInSamples separately and skipping the multiply did not work. + Omitting the multiply and not dividing did work, but the buffers may be unnecessarily large. + */ + bufferSizeInBytes = bufferSizeInBytes / device->NumUpdates; + + int bufferSizeInSamples = bufferSizeInBytes / FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + + jobject track = (*env)->NewObject(env, cAudioTrack, mAudioTrack, + STREAM_MUSIC, sampleRateInHz, channelConfig, audioFormat, device->NumUpdates * bufferSizeInBytes, MODE_STREAM); + + (*env)->CallNonvirtualVoidMethod(env, track, cAudioTrack, mPlay); + audioTrackPlaying = 1; + + jarray buffer = (*env)->NewByteArray(env, bufferSizeInBytes); + + while (data->running) + { + if (suspended) { + if (audioTrackPlaying) { + (*env)->CallNonvirtualVoidMethod(env, track, cAudioTrack, mPause); + audioTrackPlaying = 0; + } + usleep(5000); + continue; + } else if (!audioTrackPlaying) { + (*env)->CallNonvirtualVoidMethod(env, track, cAudioTrack, mPlay); + audioTrackPlaying = 1; + } + + void* pBuffer = (*env)->GetPrimitiveArrayCritical(env, buffer, NULL); + + if (pBuffer) + { + aluMixData(device, pBuffer, bufferSizeInSamples); + (*env)->ReleasePrimitiveArrayCritical(env, buffer, pBuffer, 0); + + (*env)->CallNonvirtualIntMethod(env, track, cAudioTrack, mWrite, buffer, 0, bufferSizeInBytes); + } + else + { + AL_PRINT("Failed to get pointer to array bytes"); + } + } + + (*env)->CallNonvirtualVoidMethod(env, track, cAudioTrack, mStop); + (*env)->CallNonvirtualVoidMethod(env, track, cAudioTrack, mRelease); + + (*env)->PopLocalFrame(env, NULL); + + (*javaVM)->DetachCurrentThread(javaVM); + return NULL; +} + +static ALCboolean android_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + (*javaVM)->AttachCurrentThread(javaVM, &env, NULL); + AndroidData* data; + int channels; + int bytes; + + if (!cAudioTrack) + { + /* Cache AudioTrack class and it's method id's + * And do this only once! + */ + + cAudioTrack = (*env)->FindClass(env, "android/media/AudioTrack"); + if (!cAudioTrack) + { + AL_PRINT("android.media.AudioTrack class is not found. Are you running at least 1.5 version?"); + return ALC_FALSE; + } + + cAudioTrack = (*env)->NewGlobalRef(env, cAudioTrack); + + mAudioTrack = (*env)->GetMethodID(env, cAudioTrack, "", "(IIIIII)V"); + mGetMinBufferSize = (*env)->GetStaticMethodID(env, cAudioTrack, "getMinBufferSize", "(III)I"); + mPlay = (*env)->GetMethodID(env, cAudioTrack, "play", "()V"); + mPause = (*env)->GetMethodID(env, cAudioTrack, "pause", "()V"); + mStop = (*env)->GetMethodID(env, cAudioTrack, "stop", "()V"); + mRelease = (*env)->GetMethodID(env, cAudioTrack, "release", "()V"); + mWrite = (*env)->GetMethodID(env, cAudioTrack, "write", "([BII)I"); + } + + if (!deviceName) + { + deviceName = android_device; + } + else if (strcmp(deviceName, android_device) != 0) + { + return ALC_FALSE; + } + + data = (AndroidData*)calloc(1, sizeof(*data)); + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void android_close_playback(ALCdevice *device) +{ + AndroidData* data = (AndroidData*)device->ExtraData; + if (data != NULL) + { + free(data); + device->ExtraData = NULL; + } +} + +static ALCboolean android_reset_playback(ALCdevice *device) +{ + AndroidData* data = (AndroidData*)device->ExtraData; + + // if (ChannelsFromDevFmt(device->FmtChans) >= 2) + // { + // device->Format = BytesFromDevFmt(device->FmtType) >= 2 ? AL_FORMAT_STEREO16 : AL_FORMAT_STEREO8; + // } + // else + // { + // device->Format = BytesFromDevFmt(device->FmtType) >= 2 ? AL_FORMAT_MONO16 : AL_FORMAT_MONO8; + // } + + SetDefaultChannelOrder(device); + + data->running = 1; + pthread_create(&data->thread, NULL, thread_function, device); + + return ALC_TRUE; +} + +static void android_stop_playback(ALCdevice *device) +{ + AndroidData* data = (AndroidData*)device->ExtraData; + + if (data->running) + { + data->running = 0; + pthread_join(data->thread, NULL); + } +} + +static ALCboolean android_open_capture(ALCdevice *pDevice, const ALCchar *deviceName) +{ + (void)pDevice; + (void)deviceName; + return ALC_FALSE; +} + +static void android_close_capture(ALCdevice *pDevice) +{ + (void)pDevice; +} + +static void android_start_capture(ALCdevice *pDevice) +{ + (void)pDevice; +} + +static void android_stop_capture(ALCdevice *pDevice) +{ + (void)pDevice; +} + +static void android_capture_samples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples) +{ + (void)pDevice; + (void)pBuffer; + (void)lSamples; +} + +static ALCuint android_available_samples(ALCdevice *pDevice) +{ + (void)pDevice; + return 0; +} + +static const BackendFuncs android_funcs = { + android_open_playback, + android_close_playback, + android_reset_playback, + android_stop_playback, + android_open_capture, + android_close_capture, + android_start_capture, + android_stop_capture, + android_capture_samples, + android_available_samples +}; + +static void alc_audiotrack_suspend() +{ + suspended = 1; +} + +static void alc_audiotrack_resume() +{ + suspended = 0; +} + +static void alc_audiotrack_set_java_vm(JavaVM *vm) +{ + javaVM = vm; +} + +void alc_audiotrack_init(BackendFuncs *func_list) +{ + *func_list = android_funcs; + apportableOpenALFuncs.alc_android_suspend = alc_audiotrack_suspend; + apportableOpenALFuncs.alc_android_resume = alc_audiotrack_resume; + apportableOpenALFuncs.alc_android_set_java_vm = alc_audiotrack_set_java_vm; +} + +void alc_audiotrack_deinit(void) +{ + /* release cached AudioTrack class */ + (*env)->DeleteGlobalRef(env, cAudioTrack); + (*javaVM)->DetachCurrentThread(javaVM); +} + +void alc_audiotrack_probe(int type) +{ + if (type == DEVICE_PROBE) + { + AppendDeviceList(android_device); + } + else if (type == ALL_DEVICE_PROBE) + { + AppendAllDeviceList(android_device); + } +} diff --git a/jni/OpenAL/Alc/bs2b.c b/jni/OpenAL/Alc/bs2b.c new file mode 100644 index 0000000..36f946a --- /dev/null +++ b/jni/OpenAL/Alc/bs2b.c @@ -0,0 +1,209 @@ +/*- + * Copyright (c) 2005 Boris Mikhaylov + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include + +#include "bs2b.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +/* Single pole IIR filter. + * O[n] = a0*I[n] + a1*I[n-1] + b1*O[n-1] + */ + +/* Lowpass filter */ +#define lo_filter(in, out_1) (bs2b->a0_lo*(in) + bs2b->b1_lo*(out_1)) + +/* Highboost filter */ +#define hi_filter(in, in_1, out_1) (bs2b->a0_hi*(in) + bs2b->a1_hi*(in_1) + bs2b->b1_hi*(out_1)) + +/* Set up all data. */ +static void init(struct bs2b *bs2b) +{ + double Fc_lo, Fc_hi; + double G_lo, G_hi; + double x; + + if ((bs2b->srate > 192000) || (bs2b->srate < 2000)) + bs2b->srate = BS2B_DEFAULT_SRATE; + + switch(bs2b->level) + { + case BS2B_LOW_CLEVEL: /* Low crossfeed level */ + Fc_lo = 360.0; + Fc_hi = 501.0; + G_lo = 0.398107170553497; + G_hi = 0.205671765275719; + break; + + case BS2B_MIDDLE_CLEVEL: /* Middle crossfeed level */ + Fc_lo = 500.0; + Fc_hi = 711.0; + G_lo = 0.459726988530872; + G_hi = 0.228208484414988; + break; + + case BS2B_HIGH_CLEVEL: /* High crossfeed level (virtual speakers are closer to itself) */ + Fc_lo = 700.0; + Fc_hi = 1021.0; + G_lo = 0.530884444230988; + G_hi = 0.250105790667544; + break; + + case BS2B_LOW_ECLEVEL: /* Low easy crossfeed level */ + Fc_lo = 360.0; + Fc_hi = 494.0; + G_lo = 0.316227766016838; + G_hi = 0.168236228897329; + break; + + case BS2B_MIDDLE_ECLEVEL: /* Middle easy crossfeed level */ + Fc_lo = 500.0; + Fc_hi = 689.0; + G_lo = 0.354813389233575; + G_hi = 0.187169483835901; + break; + + default: /* High easy crossfeed level */ + bs2b->level = BS2B_HIGH_ECLEVEL; + + Fc_lo = 700.0; + Fc_hi = 975.0; + G_lo = 0.398107170553497; + G_hi = 0.205671765275719; + break; + } /* switch */ + + /* $fc = $Fc / $s; + * $d = 1 / 2 / pi / $fc; + * $x = exp(-1 / $d); + */ + + x = exp(-2.0 * M_PI * Fc_lo / bs2b->srate); + bs2b->b1_lo = x; + bs2b->a0_lo = G_lo * (1.0 - x); + + x = exp(-2.0 * M_PI * Fc_hi / bs2b->srate); + bs2b->b1_hi = x; + bs2b->a0_hi = 1.0 - G_hi * (1.0 - x); + bs2b->a1_hi = -x; + + bs2b->gain = 1.0 / (1.0 - G_hi + G_lo); +} /* init */ + +/* Exported functions. + * See descriptions in "bs2b.h" + */ + +void bs2b_set_level(struct bs2b *bs2b, int level) +{ + if(level == bs2b->level) + return; + bs2b->level = level; + init(bs2b); +} /* bs2b_set_level */ + +int bs2b_get_level(struct bs2b *bs2b) +{ + return bs2b->level; +} /* bs2b_get_level */ + +void bs2b_set_srate(struct bs2b *bs2b, int srate) +{ + if (srate == bs2b->srate) + return; + bs2b->srate = srate; + init(bs2b); +} /* bs2b_set_srate */ + +int bs2b_get_srate(struct bs2b *bs2b) +{ + return bs2b->srate; +} /* bs2b_get_srate */ + +void bs2b_clear(struct bs2b *bs2b) +{ + int loopv = sizeof(bs2b->last_sample); + + while (loopv) + { + ((char *)&bs2b->last_sample)[--loopv] = 0; + } +} /* bs2b_clear */ + +int bs2b_is_clear(struct bs2b *bs2b) +{ + int loopv = sizeof(bs2b->last_sample); + + while (loopv) + { + if (((char *)&bs2b->last_sample)[--loopv] != 0) + return 0; + } + return 1; +} /* bs2b_is_clear */ + +void bs2b_cross_feed(struct bs2b *bs2b, ALfp *ALsample) +{ + //FIXME fully convert to fixed point math + float sample[2]; + sample[0] = ALfp2float(ALsample[0]); + sample[1] = ALfp2float(ALsample[1]); + + /* Lowpass filter */ + bs2b->last_sample.lo[0] = lo_filter(sample[0], bs2b->last_sample.lo[0]); + bs2b->last_sample.lo[1] = lo_filter(sample[1], bs2b->last_sample.lo[1]); + + /* Highboost filter */ + bs2b->last_sample.hi[0] = hi_filter(sample[0], bs2b->last_sample.asis[0], bs2b->last_sample.hi[0]); + bs2b->last_sample.hi[1] = hi_filter(sample[1], bs2b->last_sample.asis[1], bs2b->last_sample.hi[1]); + bs2b->last_sample.asis[0] = sample[0]; + bs2b->last_sample.asis[1] = sample[1]; + + /* Crossfeed */ + sample[0] = bs2b->last_sample.hi[0] + bs2b->last_sample.lo[1]; + sample[1] = bs2b->last_sample.hi[1] + bs2b->last_sample.lo[0]; + + /* Bass boost cause allpass attenuation */ + sample[0] *= bs2b->gain; + sample[1] *= bs2b->gain; + + /* Clipping of overloaded samples */ +#if 0 + if (sample[0] > 1.0) + sample[0] = 1.0; + if (sample[0] < -1.0) + sample[0] = -1.0; + if (sample[1] > 1.0) + sample[1] = 1.0; + if (sample[1] < -1.0) + sample[1] = -1.0; +#endif + + ALsample[0] = float2ALfp(sample[0]); + ALsample[1] = float2ALfp(sample[1]); +} /* bs2b_cross_feed */ diff --git a/jni/OpenAL/Alc/dsound.c b/jni/OpenAL/Alc/dsound.c new file mode 100644 index 0000000..26e6d46 --- /dev/null +++ b/jni/OpenAL/Alc/dsound.c @@ -0,0 +1,612 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#define _WIN32_WINNT 0x0500 +#define INITGUID +#include +#include +#include + +#include +#include +#include +#ifndef _WAVEFORMATEXTENSIBLE_ +#include +#include +#endif + +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#ifndef DSSPEAKER_5POINT1 +#define DSSPEAKER_5POINT1 6 +#endif +#ifndef DSSPEAKER_7POINT1 +#define DSSPEAKER_7POINT1 7 +#endif + +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); + +static void *ds_handle; +static HRESULT (WINAPI *pDirectSoundCreate)(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter); +static HRESULT (WINAPI *pDirectSoundEnumerateA)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); + + +typedef struct { + // DirectSound Playback Device + LPDIRECTSOUND lpDS; + LPDIRECTSOUNDBUFFER DSpbuffer; + LPDIRECTSOUNDBUFFER DSsbuffer; + + volatile int killNow; + ALvoid *thread; +} DSoundData; + + +typedef struct { + ALCchar *name; + GUID guid; +} DevMap; + +static const ALCchar dsDevice[] = "DirectSound Default"; +static DevMap *DeviceList; +static ALuint NumDevices; + + +void *DSoundLoad(void) +{ + if(!ds_handle) + { +#ifdef _WIN32 + ds_handle = LoadLibraryA("dsound.dll"); + if(ds_handle == NULL) + { + AL_PRINT("Failed to load dsound.dll\n"); + return NULL; + } + +#define LOAD_FUNC(f) do { \ + p##f = (void*)GetProcAddress((HMODULE)ds_handle, #f); \ + if(p##f == NULL) \ + { \ + FreeLibrary(ds_handle); \ + ds_handle = NULL; \ + AL_PRINT("Could not load %s from dsound.dll\n", #f); \ + return NULL; \ + } \ +} while(0) +#else + ds_handle = (void*)0xDEADBEEF; +#define LOAD_FUNC(f) p##f = f +#endif + +LOAD_FUNC(DirectSoundCreate); +LOAD_FUNC(DirectSoundEnumerateA); +#undef LOAD_FUNC + } + return ds_handle; +} + + +static BOOL CALLBACK DSoundEnumDevices(LPGUID guid, LPCSTR desc, LPCSTR drvname, LPVOID data) +{ + char str[1024]; + void *temp; + int count; + ALuint i; + + (void)data; + (void)drvname; + + if(NumDevices == 0) + { + temp = realloc(DeviceList, sizeof(DevMap) * (NumDevices+1)); + if(temp) + { + DeviceList = temp; + DeviceList[NumDevices].name = strdup(dsDevice); + DeviceList[NumDevices].guid = GUID_NULL; + NumDevices++; + } + } + + if(!guid) + return TRUE; + + count = 0; + do { + if(count == 0) + snprintf(str, sizeof(str), "%s via DirectSound", desc); + else + snprintf(str, sizeof(str), "%s #%d via DirectSound", desc, count+1); + count++; + + for(i = 0;i < NumDevices;i++) + { + if(strcmp(str, DeviceList[i].name) == 0) + break; + } + } while(i != NumDevices); + + temp = realloc(DeviceList, sizeof(DevMap) * (NumDevices+1)); + if(temp) + { + DeviceList = temp; + DeviceList[NumDevices].name = strdup(str); + DeviceList[NumDevices].guid = *guid; + NumDevices++; + } + + return TRUE; +} + + +static ALuint DSoundProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + DSoundData *pData = (DSoundData*)pDevice->ExtraData; + DSBCAPS DSBCaps; + DWORD LastCursor = 0; + DWORD PlayCursor; + VOID *WritePtr1, *WritePtr2; + DWORD WriteCnt1, WriteCnt2; + BOOL Playing = FALSE; + DWORD FrameSize; + DWORD FragSize; + DWORD avail; + HRESULT err; + + SetRTPriority(); + + memset(&DSBCaps, 0, sizeof(DSBCaps)); + DSBCaps.dwSize = sizeof(DSBCaps); + err = IDirectSoundBuffer_GetCaps(pData->DSsbuffer, &DSBCaps); + if(FAILED(err)) + { + AL_PRINT("Failed to get buffer caps: 0x%lx\n", err); + aluHandleDisconnect(pDevice); + return 1; + } + + FrameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + FragSize = pDevice->UpdateSize * FrameSize; + + IDirectSoundBuffer_GetCurrentPosition(pData->DSsbuffer, &LastCursor, NULL); + while(!pData->killNow) + { + // Get current play and write cursors + IDirectSoundBuffer_GetCurrentPosition(pData->DSsbuffer, &PlayCursor, NULL); + avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes; + + if(avail < FragSize) + { + if(!Playing) + { + err = IDirectSoundBuffer_Play(pData->DSsbuffer, 0, 0, DSBPLAY_LOOPING); + if(FAILED(err)) + { + AL_PRINT("Failed to play buffer: 0x%lx\n", err); + aluHandleDisconnect(pDevice); + return 1; + } + Playing = TRUE; + } + Sleep(1); + continue; + } + avail -= avail%FragSize; + + // Lock output buffer + WriteCnt1 = 0; + WriteCnt2 = 0; + err = IDirectSoundBuffer_Lock(pData->DSsbuffer, LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0); + + // If the buffer is lost, restore it and lock + if(err == DSERR_BUFFERLOST) + { + err = IDirectSoundBuffer_Restore(pData->DSsbuffer); + if(SUCCEEDED(err)) + { + Playing = FALSE; + LastCursor = 0; + err = IDirectSoundBuffer_Lock(pData->DSsbuffer, 0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0); + } + } + + // Successfully locked the output buffer + if(SUCCEEDED(err)) + { + // If we have an active context, mix data directly into output buffer otherwise fill with silence + aluMixData(pDevice, WritePtr1, WriteCnt1/FrameSize); + aluMixData(pDevice, WritePtr2, WriteCnt2/FrameSize); + + // Unlock output buffer only when successfully locked + IDirectSoundBuffer_Unlock(pData->DSsbuffer, WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); + } + else + { + AL_PRINT("Buffer lock error: %#lx\n", err); + aluHandleDisconnect(pDevice); + return 1; + } + + // Update old write cursor location + LastCursor += WriteCnt1+WriteCnt2; + LastCursor %= DSBCaps.dwBufferBytes; + } + + return 0; +} + +static ALCboolean DSoundOpenPlayback(ALCdevice *device, const ALCchar *deviceName) +{ + DSoundData *pData = NULL; + LPGUID guid = NULL; + HRESULT hr; + + if(!DSoundLoad()) + return ALC_FALSE; + + if(!deviceName) + deviceName = dsDevice; + else if(strcmp(deviceName, dsDevice) != 0) + { + ALuint i; + + if(!DeviceList) + { + hr = pDirectSoundEnumerateA(DSoundEnumDevices, NULL); + if(FAILED(hr)) + AL_PRINT("Error enumerating DirectSound devices (%#x)!\n", (unsigned int)hr); + } + + for(i = 0;i < NumDevices;i++) + { + if(strcmp(deviceName, DeviceList[i].name) == 0) + { + if(i > 0) + guid = &DeviceList[i].guid; + break; + } + } + if(i == NumDevices) + return ALC_FALSE; + } + + //Initialise requested device + pData = calloc(1, sizeof(DSoundData)); + if(!pData) + { + alcSetError(device, ALC_OUT_OF_MEMORY); + return ALC_FALSE; + } + + //DirectSound Init code + hr = pDirectSoundCreate(guid, &pData->lpDS, NULL); + if(SUCCEEDED(hr)) + hr = IDirectSound_SetCooperativeLevel(pData->lpDS, GetForegroundWindow(), DSSCL_PRIORITY); + if(FAILED(hr)) + { + if(pData->lpDS) + IDirectSound_Release(pData->lpDS); + free(pData); + AL_PRINT("Device init failed: 0x%08lx\n", hr); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = pData; + return ALC_TRUE; +} + +static void DSoundClosePlayback(ALCdevice *device) +{ + DSoundData *pData = device->ExtraData; + + IDirectSound_Release(pData->lpDS); + free(pData); + device->ExtraData = NULL; +} + +static ALCboolean DSoundResetPlayback(ALCdevice *device) +{ + DSoundData *pData = (DSoundData*)device->ExtraData; + DSBUFFERDESC DSBDescription; + WAVEFORMATEXTENSIBLE OutputType; + DWORD speakers; + HRESULT hr; + + memset(&OutputType, 0, sizeof(OutputType)); + + switch(device->FmtType) + { + case DevFmtByte: + device->FmtType = DevFmtUByte; + break; + case DevFmtUShort: + device->FmtType = DevFmtShort; + break; + case DevFmtUByte: + case DevFmtShort: + case DevFmtFloat: + break; + } + + hr = IDirectSound_GetSpeakerConfig(pData->lpDS, &speakers); + if(SUCCEEDED(hr) && ConfigValueExists(NULL, "format")) + { + switch(device->FmtChans) + { + case DevFmtMono: + speakers = DSSPEAKER_COMBINED(DSSPEAKER_MONO, 0); + break; + case DevFmtStereo: + speakers = DSSPEAKER_COMBINED(DSSPEAKER_STEREO, 0); + break; + case DevFmtQuad: + speakers = DSSPEAKER_COMBINED(DSSPEAKER_QUAD, 0); + break; + case DevFmtX51: + speakers = DSSPEAKER_COMBINED(DSSPEAKER_5POINT1, 0); + break; + case DevFmtX61: + /* ??? */; + break; + case DevFmtX71: + speakers = DSSPEAKER_COMBINED(DSSPEAKER_7POINT1, 0); + break; + } + } + if(SUCCEEDED(hr)) + { + speakers = DSSPEAKER_CONFIG(speakers); + if(speakers == DSSPEAKER_MONO) + { + device->FmtChans = DevFmtMono; + OutputType.dwChannelMask = SPEAKER_FRONT_CENTER; + } + else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE) + { + device->FmtChans = DevFmtStereo; + OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT; + } + else if(speakers == DSSPEAKER_QUAD) + { + device->FmtChans = DevFmtQuad; + OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT; + } + else if(speakers == DSSPEAKER_5POINT1) + { + device->FmtChans = DevFmtX51; + OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT; + } + else if(speakers == DSSPEAKER_7POINT1) + { + device->FmtChans = DevFmtX71; + OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT; + } + + OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; + OutputType.Format.nChannels = ChannelsFromDevFmt(device->FmtChans); + OutputType.Format.wBitsPerSample = BytesFromDevFmt(device->FmtType) * 8; + OutputType.Format.nBlockAlign = OutputType.Format.nChannels*OutputType.Format.wBitsPerSample/8; + OutputType.Format.nSamplesPerSec = device->Frequency; + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec*OutputType.Format.nBlockAlign; + OutputType.Format.cbSize = 0; + } + + if(OutputType.Format.nChannels > 2 || OutputType.Format.wBitsPerSample > 16) + { + OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.Format.cbSize = 22; + if(OutputType.Format.wBitsPerSample == 32) + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + else + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + else + { + if(SUCCEEDED(hr)) + { + memset(&DSBDescription,0,sizeof(DSBUFFERDESC)); + DSBDescription.dwSize=sizeof(DSBUFFERDESC); + DSBDescription.dwFlags=DSBCAPS_PRIMARYBUFFER; + hr = IDirectSound_CreateSoundBuffer(pData->lpDS, &DSBDescription, &pData->DSpbuffer, NULL); + } + if(SUCCEEDED(hr)) + hr = IDirectSoundBuffer_SetFormat(pData->DSpbuffer,&OutputType.Format); + } + + if(SUCCEEDED(hr)) + { + memset(&DSBDescription,0,sizeof(DSBUFFERDESC)); + DSBDescription.dwSize=sizeof(DSBUFFERDESC); + DSBDescription.dwFlags=DSBCAPS_GLOBALFOCUS|DSBCAPS_GETCURRENTPOSITION2; + DSBDescription.dwBufferBytes=device->UpdateSize * device->NumUpdates * + OutputType.Format.nBlockAlign; + DSBDescription.lpwfxFormat=&OutputType.Format; + hr = IDirectSound_CreateSoundBuffer(pData->lpDS, &DSBDescription, &pData->DSsbuffer, NULL); + } + + if(SUCCEEDED(hr)) + { + SetDefaultWFXChannelOrder(device); + pData->thread = StartThread(DSoundProc, device); + if(!pData->thread) + hr = E_FAIL; + } + + if(FAILED(hr)) + { + if (pData->DSsbuffer) + IDirectSoundBuffer_Release(pData->DSsbuffer); + pData->DSsbuffer = NULL; + if (pData->DSpbuffer) + IDirectSoundBuffer_Release(pData->DSpbuffer); + pData->DSpbuffer = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void DSoundStopPlayback(ALCdevice *device) +{ + DSoundData *pData = device->ExtraData; + + if(!pData->thread) + return; + + pData->killNow = 1; + StopThread(pData->thread); + pData->thread = NULL; + + pData->killNow = 0; + + IDirectSoundBuffer_Release(pData->DSsbuffer); + pData->DSsbuffer = NULL; + if (pData->DSpbuffer) + IDirectSoundBuffer_Release(pData->DSpbuffer); + pData->DSpbuffer = NULL; +} + + +static ALCboolean DSoundOpenCapture(ALCdevice *pDevice, const ALCchar *deviceName) +{ + (void)pDevice; + (void)deviceName; + return ALC_FALSE; +} + +static void DSoundCloseCapture(ALCdevice *pDevice) +{ + (void)pDevice; +} + +static void DSoundStartCapture(ALCdevice *pDevice) +{ + (void)pDevice; +} + +static void DSoundStopCapture(ALCdevice *pDevice) +{ + (void)pDevice; +} + +static void DSoundCaptureSamples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples) +{ + (void)pDevice; + (void)pBuffer; + (void)lSamples; +} + +static ALCuint DSoundAvailableSamples(ALCdevice *pDevice) +{ + (void)pDevice; + return 0; +} + + +BackendFuncs DSoundFuncs = { + DSoundOpenPlayback, + DSoundClosePlayback, + DSoundResetPlayback, + DSoundStopPlayback, + DSoundOpenCapture, + DSoundCloseCapture, + DSoundStartCapture, + DSoundStopCapture, + DSoundCaptureSamples, + DSoundAvailableSamples +}; + + +void alcDSoundInit(BackendFuncs *FuncList) +{ + *FuncList = DSoundFuncs; +} + +void alcDSoundDeinit(void) +{ + ALuint i; + + for(i = 0;i < NumDevices;++i) + free(DeviceList[i].name); + free(DeviceList); + DeviceList = NULL; + NumDevices = 0; + + if(ds_handle) + { +#ifdef _WIN32 + FreeLibrary(ds_handle); +#endif + ds_handle = NULL; + } +} + +void alcDSoundProbe(int type) +{ + if(!DSoundLoad()) return; + + if(type == DEVICE_PROBE) + AppendDeviceList(dsDevice); + else if(type == ALL_DEVICE_PROBE) + { + HRESULT hr; + ALuint i; + + for(i = 0;i < NumDevices;++i) + free(DeviceList[i].name); + free(DeviceList); + DeviceList = NULL; + NumDevices = 0; + + hr = pDirectSoundEnumerateA(DSoundEnumDevices, NULL); + if(FAILED(hr)) + AL_PRINT("Error enumerating DirectSound devices (%#x)!\n", (unsigned int)hr); + else + { + for(i = 0;i < NumDevices;i++) + AppendAllDeviceList(DeviceList[i].name); + } + } +} diff --git a/jni/OpenAL/Alc/mixer.c b/jni/OpenAL/Alc/mixer.c new file mode 100644 index 0000000..036441f --- /dev/null +++ b/jni/OpenAL/Alc/mixer.c @@ -0,0 +1,813 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" +#include "alSource.h" +#include "alBuffer.h" +#include "alListener.h" +#include "alAuxEffectSlot.h" +#include "alu.h" +#include "bs2b.h" + + +static __inline ALdfp point32(const ALfp *vals, ALint step, ALint frac) +{ return vals[0]; (void)step; (void)frac; } +static __inline ALdfp lerp32(const ALfp *vals, ALint step, ALint frac) +{ return lerp(vals[0], vals[step], ALfpMult(int2ALfp(frac), ALfpDiv(int2ALfp(1),int2ALfp(FRACTIONONE)))); } +static __inline ALdfp cubic32(const ALfp *vals, ALint step, ALint frac) +{ return cubic(vals[-step], vals[0], vals[step], vals[step+step], + ALfpMult(int2ALfp(frac), ALfpDiv(int2ALfp(1),int2ALfp(FRACTIONONE)))); } + +static __inline ALdfp point16(const ALshort *vals, ALint step, ALint frac) +{ return ALfpMult(int2ALfp(vals[0]), float2ALfp(1.0/32767.0)); (void)step; (void)frac; } +static __inline ALdfp lerp16(const ALshort *vals, ALint step, ALint frac) +{ return ALfpMult(lerp(int2ALfp(vals[0]), int2ALfp(vals[step]), ALfpMult(int2ALfp(frac), ALfpDiv(int2ALfp(1),int2ALfp(FRACTIONONE)))), + float2ALfp(1.0/32767.0)); } +static __inline ALdfp cubic16(const ALshort *vals, ALint step, ALint frac) +{ return ALfpMult(cubic(int2ALfp(vals[-step]), int2ALfp(vals[0]), int2ALfp(vals[step]), int2ALfp(vals[step+step]), + ALfpMult(int2ALfp(frac), ALfpDiv(int2ALfp(1),int2ALfp(FRACTIONONE)))), float2ALfp(1.0/32767.0)); } + +static __inline ALdfp point8(const ALubyte *vals, ALint step, ALint frac) +{ return ALfpMult(int2ALfp((int)vals[0]-128), float2ALfp(1.0/127.0)); (void)step; (void)frac; } +static __inline ALdfp lerp8(const ALubyte *vals, ALint step, ALint frac) +{ return ALfpMult((lerp(int2ALfp(vals[0]), int2ALfp(vals[step]), + ALfpMult(int2ALfp(frac), ALfpDiv(int2ALfp(1),int2ALfp(FRACTIONONE))))- + int2ALfp(128)), + float2ALfp(1.0/127.0)); } +static __inline ALdfp cubic8(const ALubyte *vals, ALint step, ALint frac) +{ return ALfpMult((cubic(int2ALfp(vals[-step]), int2ALfp(vals[0]), int2ALfp(vals[step]), int2ALfp(vals[step+step]), + ALfpMult(int2ALfp(frac), ALfpDiv(int2ALfp(1),int2ALfp(FRACTIONONE))))- + int2ALfp(128)), + float2ALfp(1.0/127.0)); } + + +#define DECL_TEMPLATE(T, sampler) \ +static void Mix_##T##_1_##sampler(ALsource *Source, ALCdevice *Device, \ + const T *data, ALuint *DataPosInt, ALuint *DataPosFrac, \ + ALuint OutPos, ALuint SamplesToDo, ALuint BufferSize) \ +{ \ + ALfp (*DryBuffer)[MAXCHANNELS]; \ + ALfp *ClickRemoval, *PendingClicks; \ + ALuint pos, frac; \ + ALfp DrySend[MAXCHANNELS]; \ + FILTER *DryFilter; \ + ALuint BufferIdx; \ + ALuint increment; \ + ALuint out, c; \ + ALfp value; \ + \ + increment = Source->Params.Step; \ + \ + DryBuffer = Device->DryBuffer; \ + ClickRemoval = Device->ClickRemoval; \ + PendingClicks = Device->PendingClicks; \ + DryFilter = &Source->Params.iirFilter; \ + for(c = 0;c < MAXCHANNELS;c++) \ + DrySend[c] = Source->Params.DryGains[0][c]; \ + \ + pos = 0; \ + frac = *DataPosFrac; \ + \ + if(OutPos == 0) \ + { \ + value = sampler(data+pos, 1, frac); \ + \ + value = lpFilter4PC(DryFilter, 0, value); \ + for(c = 0;c < MAXCHANNELS;c++) \ + ClickRemoval[c] = (ClickRemoval[c] - ALfpMult(value,DrySend[c])); \ + } \ + for(BufferIdx = 0;BufferIdx < BufferSize;BufferIdx++) \ + { \ + /* First order interpolator */ \ + value = sampler(data+pos, 1, frac); \ + \ + /* Direct path final mix buffer and panning */ \ + value = lpFilter4P(DryFilter, 0, value); \ + for(c = 0;c < MAXCHANNELS;c++) \ + DryBuffer[OutPos][c] = (DryBuffer[OutPos][c] + ALfpMult(value,DrySend[c])); \ + \ + frac += increment; \ + pos += frac>>FRACTIONBITS; \ + frac &= FRACTIONMASK; \ + OutPos++; \ + } \ + if(OutPos == SamplesToDo) \ + { \ + value = sampler(data+pos, 1, frac); \ + \ + value = lpFilter4PC(DryFilter, 0, value); \ + for(c = 0;c < MAXCHANNELS;c++) \ + PendingClicks[c] = (PendingClicks[c] + ALfpMult(value,DrySend[c])); \ + } \ + \ + for(out = 0;out < Device->NumAuxSends;out++) \ + { \ + ALfp WetSend; \ + ALfp *WetBuffer; \ + ALfp *WetClickRemoval; \ + ALfp *WetPendingClicks; \ + FILTER *WetFilter; \ + \ + if(!Source->Send[out].Slot || \ + Source->Send[out].Slot->effect.type == AL_EFFECT_NULL) \ + continue; \ + \ + WetBuffer = Source->Send[out].Slot->WetBuffer; \ + WetClickRemoval = Source->Send[out].Slot->ClickRemoval; \ + WetPendingClicks = Source->Send[out].Slot->PendingClicks; \ + WetFilter = &Source->Params.Send[out].iirFilter; \ + WetSend = Source->Params.Send[out].WetGain; \ + \ + pos = 0; \ + frac = *DataPosFrac; \ + OutPos -= BufferSize; \ + \ + if(OutPos == 0) \ + { \ + value = sampler(data+pos, 1, frac); \ + \ + value = lpFilter2PC(WetFilter, 0, value); \ + WetClickRemoval[0] = (WetClickRemoval[0] - ALfpMult(value,WetSend)); \ + } \ + for(BufferIdx = 0;BufferIdx < BufferSize;BufferIdx++) \ + { \ + /* First order interpolator */ \ + value = sampler(data+pos, 1, frac); \ + \ + /* Room path final mix buffer and panning */ \ + value = lpFilter2P(WetFilter, 0, value); \ + WetBuffer[OutPos] = (WetBuffer[OutPos] + ALfpMult(value,WetSend)); \ + \ + frac += increment; \ + pos += frac>>FRACTIONBITS; \ + frac &= FRACTIONMASK; \ + OutPos++; \ + } \ + if(OutPos == SamplesToDo) \ + { \ + value = sampler(data+pos, 1, frac); \ + \ + value = lpFilter2PC(WetFilter, 0, value); \ + WetPendingClicks[0] = (WetPendingClicks[0] + ALfpMult(value,WetSend)); \ + } \ + } \ + *DataPosInt += pos; \ + *DataPosFrac = frac; \ +} + +DECL_TEMPLATE(ALfp, point32) +DECL_TEMPLATE(ALfp, lerp32) +DECL_TEMPLATE(ALfp, cubic32) + +DECL_TEMPLATE(ALshort, point16) +DECL_TEMPLATE(ALshort, lerp16) +DECL_TEMPLATE(ALshort, cubic16) + +DECL_TEMPLATE(ALubyte, point8) +DECL_TEMPLATE(ALubyte, lerp8) +DECL_TEMPLATE(ALubyte, cubic8) + +#undef DECL_TEMPLATE + + +#define DECL_TEMPLATE(T, chnct, sampler) \ +static void Mix_##T##_##chnct##_##sampler(ALsource *Source, ALCdevice *Device,\ + const T *data, ALuint *DataPosInt, ALuint *DataPosFrac, \ + ALuint OutPos, ALuint SamplesToDo, ALuint BufferSize) \ +{ \ + const ALuint Channels = chnct; \ + const ALfp scaler = ALfpDiv(int2ALfp(1),int2ALfp(chnct)); \ + ALfp (*DryBuffer)[MAXCHANNELS]; \ + ALfp *ClickRemoval, *PendingClicks; \ + ALuint pos, frac; \ + ALfp DrySend[chnct][MAXCHANNELS]; \ + FILTER *DryFilter; \ + ALuint BufferIdx; \ + ALuint increment; \ + ALuint i, out, c; \ + ALfp value; \ + \ + increment = Source->Params.Step; \ + \ + DryBuffer = Device->DryBuffer; \ + ClickRemoval = Device->ClickRemoval; \ + PendingClicks = Device->PendingClicks; \ + DryFilter = &Source->Params.iirFilter; \ + for(i = 0;i < Channels;i++) \ + { \ + for(c = 0;c < MAXCHANNELS;c++) \ + DrySend[i][c] = Source->Params.DryGains[i][c]; \ + } \ + \ + pos = 0; \ + frac = *DataPosFrac; \ + \ + if(OutPos == 0) \ + { \ + for(i = 0;i < Channels;i++) \ + { \ + value = sampler(data + pos*Channels + i, Channels, frac); \ + \ + value = lpFilter2PC(DryFilter, i*2, value); \ + for(c = 0;c < MAXCHANNELS;c++) \ + ClickRemoval[c] = (ClickRemoval[c] - ALfpMult(value,DrySend[i][c])); \ + } \ + } \ + for(BufferIdx = 0;BufferIdx < BufferSize;BufferIdx++) \ + { \ + for(i = 0;i < Channels;i++) \ + { \ + value = sampler(data + pos*Channels + i, Channels, frac); \ + \ + value = lpFilter2P(DryFilter, i*2, value); \ + for(c = 0;c < MAXCHANNELS;c++) \ + DryBuffer[OutPos][c] = (DryBuffer[OutPos][c] + ALfpMult(value,DrySend[i][c])); \ + } \ + \ + frac += increment; \ + pos += frac>>FRACTIONBITS; \ + frac &= FRACTIONMASK; \ + OutPos++; \ + } \ + if(OutPos == SamplesToDo) \ + { \ + for(i = 0;i < Channels;i++) \ + { \ + value = sampler(data + pos*Channels + i, Channels, frac); \ + \ + value = lpFilter2PC(DryFilter, i*2, value); \ + for(c = 0;c < MAXCHANNELS;c++) \ + PendingClicks[c] = (PendingClicks[c] + ALfpMult(value,DrySend[i][c])); \ + } \ + } \ + \ + for(out = 0;out < Device->NumAuxSends;out++) \ + { \ + ALfp WetSend; \ + ALfp *WetBuffer; \ + ALfp *WetClickRemoval; \ + ALfp *WetPendingClicks; \ + FILTER *WetFilter; \ + \ + if(!Source->Send[out].Slot || \ + Source->Send[out].Slot->effect.type == AL_EFFECT_NULL) \ + continue; \ + \ + WetBuffer = Source->Send[out].Slot->WetBuffer; \ + WetClickRemoval = Source->Send[out].Slot->ClickRemoval; \ + WetPendingClicks = Source->Send[out].Slot->PendingClicks; \ + WetFilter = &Source->Params.Send[out].iirFilter; \ + WetSend = Source->Params.Send[out].WetGain; \ + \ + pos = 0; \ + frac = *DataPosFrac; \ + OutPos -= BufferSize; \ + \ + if(OutPos == 0) \ + { \ + for(i = 0;i < Channels;i++) \ + { \ + value = sampler(data + pos*Channels + i, Channels, frac); \ + \ + value = lpFilter1PC(WetFilter, i, value); \ + WetClickRemoval[0] = (WetClickRemoval[0] - ALfpMult(ALfpMult(value,WetSend), scaler)); \ + } \ + } \ + for(BufferIdx = 0;BufferIdx < BufferSize;BufferIdx++) \ + { \ + for(i = 0;i < Channels;i++) \ + { \ + value = sampler(data + pos*Channels + i, Channels, frac); \ + \ + value = lpFilter1P(WetFilter, i, value); \ + WetBuffer[OutPos] = (WetBuffer[OutPos] + ALfpMult(ALfpMult(value,WetSend), scaler)); \ + } \ + \ + frac += increment; \ + pos += frac>>FRACTIONBITS; \ + frac &= FRACTIONMASK; \ + OutPos++; \ + } \ + if(OutPos == SamplesToDo) \ + { \ + for(i = 0;i < Channels;i++) \ + { \ + value = sampler(data + pos*Channels + i, Channels, frac); \ + \ + value = lpFilter1PC(WetFilter, i, value); \ + WetPendingClicks[0] = (WetPendingClicks[0] + ALfpMult(ALfpMult(value,WetSend), scaler)); \ + } \ + } \ + } \ + *DataPosInt += pos; \ + *DataPosFrac = frac; \ +} + +DECL_TEMPLATE(ALfp, 2, point32) +DECL_TEMPLATE(ALfp, 2, lerp32) +DECL_TEMPLATE(ALfp, 2, cubic32) + +DECL_TEMPLATE(ALshort, 2, point16) +DECL_TEMPLATE(ALshort, 2, lerp16) +DECL_TEMPLATE(ALshort, 2, cubic16) + +DECL_TEMPLATE(ALubyte, 2, point8) +DECL_TEMPLATE(ALubyte, 2, lerp8) +DECL_TEMPLATE(ALubyte, 2, cubic8) + + +DECL_TEMPLATE(ALfp, 4, point32) +DECL_TEMPLATE(ALfp, 4, lerp32) +DECL_TEMPLATE(ALfp, 4, cubic32) + +DECL_TEMPLATE(ALshort, 4, point16) +DECL_TEMPLATE(ALshort, 4, lerp16) +DECL_TEMPLATE(ALshort, 4, cubic16) + +DECL_TEMPLATE(ALubyte, 4, point8) +DECL_TEMPLATE(ALubyte, 4, lerp8) +DECL_TEMPLATE(ALubyte, 4, cubic8) + + +DECL_TEMPLATE(ALfp, 6, point32) +DECL_TEMPLATE(ALfp, 6, lerp32) +DECL_TEMPLATE(ALfp, 6, cubic32) + +DECL_TEMPLATE(ALshort, 6, point16) +DECL_TEMPLATE(ALshort, 6, lerp16) +DECL_TEMPLATE(ALshort, 6, cubic16) + +DECL_TEMPLATE(ALubyte, 6, point8) +DECL_TEMPLATE(ALubyte, 6, lerp8) +DECL_TEMPLATE(ALubyte, 6, cubic8) + + +DECL_TEMPLATE(ALfp, 7, point32) +DECL_TEMPLATE(ALfp, 7, lerp32) +DECL_TEMPLATE(ALfp, 7, cubic32) + +DECL_TEMPLATE(ALshort, 7, point16) +DECL_TEMPLATE(ALshort, 7, lerp16) +DECL_TEMPLATE(ALshort, 7, cubic16) + +DECL_TEMPLATE(ALubyte, 7, point8) +DECL_TEMPLATE(ALubyte, 7, lerp8) +DECL_TEMPLATE(ALubyte, 7, cubic8) + + +DECL_TEMPLATE(ALfp, 8, point32) +DECL_TEMPLATE(ALfp, 8, lerp32) +DECL_TEMPLATE(ALfp, 8, cubic32) + +DECL_TEMPLATE(ALshort, 8, point16) +DECL_TEMPLATE(ALshort, 8, lerp16) +DECL_TEMPLATE(ALshort, 8, cubic16) + +DECL_TEMPLATE(ALubyte, 8, point8) +DECL_TEMPLATE(ALubyte, 8, lerp8) +DECL_TEMPLATE(ALubyte, 8, cubic8) + +#undef DECL_TEMPLATE + + +#define DECL_TEMPLATE(T, sampler) \ +static void Mix_##T##_##sampler(ALsource *Source, ALCdevice *Device, \ + enum FmtChannels FmtChannels, \ + const ALvoid *Data, ALuint *DataPosInt, ALuint *DataPosFrac, \ + ALuint OutPos, ALuint SamplesToDo, ALuint BufferSize) \ +{ \ + switch(FmtChannels) \ + { \ + case FmtMono: \ + Mix_##T##_1_##sampler(Source, Device, Data, DataPosInt, DataPosFrac, \ + OutPos, SamplesToDo, BufferSize); \ + break; \ + case FmtStereo: \ + case FmtRear: \ + Mix_##T##_2_##sampler(Source, Device, Data, DataPosInt, DataPosFrac, \ + OutPos, SamplesToDo, BufferSize); \ + break; \ + case FmtQuad: \ + Mix_##T##_4_##sampler(Source, Device, Data, DataPosInt, DataPosFrac, \ + OutPos, SamplesToDo, BufferSize); \ + break; \ + case FmtX51: \ + Mix_##T##_6_##sampler(Source, Device, Data, DataPosInt, DataPosFrac, \ + OutPos, SamplesToDo, BufferSize); \ + break; \ + case FmtX61: \ + Mix_##T##_7_##sampler(Source, Device, Data, DataPosInt, DataPosFrac, \ + OutPos, SamplesToDo, BufferSize); \ + break; \ + case FmtX71: \ + Mix_##T##_8_##sampler(Source, Device, Data, DataPosInt, DataPosFrac, \ + OutPos, SamplesToDo, BufferSize); \ + break; \ + } \ +} + +DECL_TEMPLATE(ALfp, point32) +DECL_TEMPLATE(ALfp, lerp32) +DECL_TEMPLATE(ALfp, cubic32) + +DECL_TEMPLATE(ALshort, point16) +DECL_TEMPLATE(ALshort, lerp16) +DECL_TEMPLATE(ALshort, cubic16) + +DECL_TEMPLATE(ALubyte, point8) +DECL_TEMPLATE(ALubyte, lerp8) +DECL_TEMPLATE(ALubyte, cubic8) + +#undef DECL_TEMPLATE + + +#define DECL_TEMPLATE(sampler) \ +static void Mix_##sampler(ALsource *Source, ALCdevice *Device, \ + enum FmtChannels FmtChannels, enum FmtType FmtType, \ + const ALvoid *Data, ALuint *DataPosInt, ALuint *DataPosFrac, \ + ALuint OutPos, ALuint SamplesToDo, ALuint BufferSize) \ +{ \ + switch(FmtType) \ + { \ + case FmtUByte: \ + Mix_ALubyte_##sampler##8(Source, Device, FmtChannels, \ + Data, DataPosInt, DataPosFrac, \ + OutPos, SamplesToDo, BufferSize); \ + break; \ + \ + case FmtShort: \ + Mix_ALshort_##sampler##16(Source, Device, FmtChannels, \ + Data, DataPosInt, DataPosFrac, \ + OutPos, SamplesToDo, BufferSize); \ + break; \ + \ + case FmtFloat: \ + Mix_ALfp_##sampler##32(Source, Device, FmtChannels, \ + Data, DataPosInt, DataPosFrac, \ + OutPos, SamplesToDo, BufferSize); \ + break; \ + } \ +} + +DECL_TEMPLATE(point) +DECL_TEMPLATE(lerp) +DECL_TEMPLATE(cubic) + +#undef DECL_TEMPLATE + + +ALvoid MixSource(ALsource *Source, ALCdevice *Device, ALuint SamplesToDo) +{ + ALbufferlistitem *BufferListItem; + ALuint DataPosInt, DataPosFrac; + enum FmtChannels FmtChannels; + enum FmtType FmtType; + ALuint BuffersPlayed; + ALboolean Looping; + ALuint increment; + resampler_t Resampler; + ALenum State; + ALuint OutPos; + ALuint FrameSize; + ALint64 DataSize64; + ALuint i; + + /* Get source info */ + State = Source->state; + BuffersPlayed = Source->BuffersPlayed; + DataPosInt = Source->position; + DataPosFrac = Source->position_fraction; + Looping = Source->bLooping; + increment = Source->Params.Step; + Resampler = (increment == FRACTIONONE) ? POINT_RESAMPLER : + Source->Resampler; + + /* Get buffer info */ + FrameSize = 0; + FmtChannels = FmtMono; + FmtType = FmtUByte; + BufferListItem = Source->queue; + for(i = 0;i < Source->BuffersInQueue;i++) + { + const ALbuffer *ALBuffer; + if((ALBuffer=BufferListItem->buffer) != NULL) + { + FmtChannels = ALBuffer->FmtChannels; + FmtType = ALBuffer->FmtType; + FrameSize = FrameSizeFromFmt(FmtChannels, FmtType); + break; + } + BufferListItem = BufferListItem->next; + } + + /* Get current buffer queue item */ + BufferListItem = Source->queue; + for(i = 0;i < BuffersPlayed;i++) + BufferListItem = BufferListItem->next; + + OutPos = 0; + do { + const ALuint BufferPrePadding = ResamplerPrePadding[Resampler]; + const ALuint BufferPadding = ResamplerPadding[Resampler]; + ALubyte StackData[STACK_DATA_SIZE]; + ALubyte *SrcData = StackData; + ALuint SrcDataSize = 0; + ALuint BufferSize; + + /* Figure out how many buffer bytes will be needed */ + DataSize64 = SamplesToDo-OutPos+1; + DataSize64 *= increment; + DataSize64 += DataPosFrac+FRACTIONMASK; + DataSize64 >>= FRACTIONBITS; + DataSize64 += BufferPadding+BufferPrePadding; + DataSize64 *= FrameSize; + + BufferSize = min(DataSize64, STACK_DATA_SIZE); + BufferSize -= BufferSize%FrameSize; + + if(Source->lSourceType == AL_STATIC) + { + const ALbuffer *ALBuffer = Source->Buffer; + const ALubyte *Data = ALBuffer->data; + ALuint DataSize; + ALuint pos; + + /* If current pos is beyond the loop range, do not loop */ + if(Looping == AL_FALSE || DataPosInt >= (ALuint)ALBuffer->LoopEnd) + { + Looping = AL_FALSE; + + if(DataPosInt >= BufferPrePadding) + pos = (DataPosInt-BufferPrePadding)*FrameSize; + else + { + DataSize = (BufferPrePadding-DataPosInt)*FrameSize; + DataSize = min(BufferSize, DataSize); + + memset(&SrcData[SrcDataSize], (FmtType==FmtUByte)?0x80:0, DataSize); + SrcDataSize += DataSize; + BufferSize -= DataSize; + + pos = 0; + } + + /* Copy what's left to play in the source buffer, and clear the + * rest of the temp buffer */ + DataSize = ALBuffer->size - pos; + DataSize = min(BufferSize, DataSize); + + memcpy(&SrcData[SrcDataSize], &Data[pos], DataSize); + SrcDataSize += DataSize; + BufferSize -= DataSize; + + memset(&SrcData[SrcDataSize], (FmtType==FmtUByte)?0x80:0, BufferSize); + SrcDataSize += BufferSize; + BufferSize -= BufferSize; + } + else + { + ALuint LoopStart = ALBuffer->LoopStart; + ALuint LoopEnd = ALBuffer->LoopEnd; + + if(DataPosInt >= LoopStart) + { + pos = DataPosInt-LoopStart; + while(pos < BufferPrePadding) + pos += LoopEnd-LoopStart; + pos -= BufferPrePadding; + pos += LoopStart; + pos *= FrameSize; + } + else if(DataPosInt >= BufferPrePadding) + pos = (DataPosInt-BufferPrePadding)*FrameSize; + else + { + DataSize = (BufferPrePadding-DataPosInt)*FrameSize; + DataSize = min(BufferSize, DataSize); + + memset(&SrcData[SrcDataSize], (FmtType==FmtUByte)?0x80:0, DataSize); + SrcDataSize += DataSize; + BufferSize -= DataSize; + + pos = 0; + } + + /* Copy what's left of this loop iteration, then copy repeats + * of the loop section */ + DataSize = LoopEnd*FrameSize - pos; + DataSize = min(BufferSize, DataSize); + + memcpy(&SrcData[SrcDataSize], &Data[pos], DataSize); + SrcDataSize += DataSize; + BufferSize -= DataSize; + + DataSize = (LoopEnd-LoopStart) * FrameSize; + while(BufferSize > 0) + { + DataSize = min(BufferSize, DataSize); + + memcpy(&SrcData[SrcDataSize], &Data[LoopStart*FrameSize], DataSize); + SrcDataSize += DataSize; + BufferSize -= DataSize; + } + } + } + else + { + /* Crawl the buffer queue to fill in the temp buffer */ + ALbufferlistitem *BufferListIter = BufferListItem; + ALuint pos; + + if(DataPosInt >= BufferPrePadding) + pos = (DataPosInt-BufferPrePadding)*FrameSize; + else + { + pos = (BufferPrePadding-DataPosInt)*FrameSize; + while(pos > 0) + { + if(!BufferListIter->prev && !Looping) + { + ALuint DataSize = min(BufferSize, pos); + + memset(&SrcData[SrcDataSize], (FmtType==FmtUByte)?0x80:0, DataSize); + SrcDataSize += DataSize; + BufferSize -= DataSize; + + pos = 0; + break; + } + + if(BufferListIter->prev) + BufferListIter = BufferListIter->prev; + else + { + while(BufferListIter->next) + BufferListIter = BufferListIter->next; + } + + if(BufferListIter->buffer) + { + if((ALuint)BufferListIter->buffer->size > pos) + { + pos = BufferListIter->buffer->size - pos; + break; + } + pos -= BufferListIter->buffer->size; + } + } + } + + while(BufferListIter && BufferSize > 0) + { + const ALbuffer *ALBuffer; + if((ALBuffer=BufferListIter->buffer) != NULL) + { + const ALubyte *Data = ALBuffer->data; + ALuint DataSize = ALBuffer->size; + + /* Skip the data already played */ + if(DataSize <= pos) + pos -= DataSize; + else + { + Data += pos; + DataSize -= pos; + pos -= pos; + + DataSize = min(BufferSize, DataSize); + memcpy(&SrcData[SrcDataSize], Data, DataSize); + SrcDataSize += DataSize; + BufferSize -= DataSize; + } + } + BufferListIter = BufferListIter->next; + if(!BufferListIter && Looping) + BufferListIter = Source->queue; + else if(!BufferListIter) + { + memset(&SrcData[SrcDataSize], (FmtType==FmtUByte)?0x80:0, BufferSize); + SrcDataSize += BufferSize; + BufferSize -= BufferSize; + } + } + } + + /* Figure out how many samples we can mix. */ + DataSize64 = SrcDataSize / FrameSize; + DataSize64 -= BufferPadding+BufferPrePadding; + DataSize64 <<= FRACTIONBITS; + DataSize64 -= increment; + DataSize64 -= DataPosFrac; + + BufferSize = (ALuint)((DataSize64+(increment-1)) / increment); + BufferSize = min(BufferSize, (SamplesToDo-OutPos)); + + SrcData += BufferPrePadding*FrameSize; + switch(Resampler) + { + case POINT_RESAMPLER: + Mix_point(Source, Device, FmtChannels, FmtType, + SrcData, &DataPosInt, &DataPosFrac, + OutPos, SamplesToDo, BufferSize); + break; + case LINEAR_RESAMPLER: + Mix_lerp(Source, Device, FmtChannels, FmtType, + SrcData, &DataPosInt, &DataPosFrac, + OutPos, SamplesToDo, BufferSize); + break; + case CUBIC_RESAMPLER: + Mix_cubic(Source, Device, FmtChannels, FmtType, + SrcData, &DataPosInt, &DataPosFrac, + OutPos, SamplesToDo, BufferSize); + break; + case RESAMPLER_MIN: + case RESAMPLER_MAX: + break; + } + OutPos += BufferSize; + + /* Handle looping sources */ + while(1) + { + const ALbuffer *ALBuffer; + ALuint DataSize = 0; + ALuint LoopStart = 0; + ALuint LoopEnd = 0; + + if((ALBuffer=BufferListItem->buffer) != NULL) + { + DataSize = ALBuffer->size / FrameSize; + LoopStart = ALBuffer->LoopStart; + LoopEnd = ALBuffer->LoopEnd; + if(LoopEnd > DataPosInt) + break; + } + + if(Looping && Source->lSourceType == AL_STATIC) + { + BufferListItem = Source->queue; + DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; + break; + } + + if(DataSize > DataPosInt) + break; + + if(BufferListItem->next) + { + BufferListItem = BufferListItem->next; + BuffersPlayed++; + } + else if(Looping) + { + BufferListItem = Source->queue; + BuffersPlayed = 0; + } + else + { + State = AL_STOPPED; + BufferListItem = Source->queue; + BuffersPlayed = Source->BuffersInQueue; + DataPosInt = 0; + DataPosFrac = 0; + break; + } + + DataPosInt -= DataSize; + } + } while(State == AL_PLAYING && OutPos < SamplesToDo); + + /* Update source info */ + Source->state = State; + Source->BuffersPlayed = BuffersPlayed; + Source->position = DataPosInt; + Source->position_fraction = DataPosFrac; + Source->Buffer = BufferListItem->buffer; +} diff --git a/jni/OpenAL/Alc/null.c b/jni/OpenAL/Alc/null.c new file mode 100644 index 0000000..75fc883 --- /dev/null +++ b/jni/OpenAL/Alc/null.c @@ -0,0 +1,182 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + + +typedef struct { + ALvoid *buffer; + ALuint size; + + volatile int killNow; + ALvoid *thread; +} null_data; + + +static const ALCchar nullDevice[] = "No Output"; + +static ALuint NullProc(ALvoid *ptr) +{ + ALCdevice *Device = (ALCdevice*)ptr; + null_data *data = (null_data*)Device->ExtraData; + ALuint now, start; + ALuint64 avail, done; + const ALuint restTime = ((ALuint)((ALuint64)Device->UpdateSize * 1000 / + Device->Frequency)) / 2; + + done = 0; + start = timeGetTime(); + while(!data->killNow && Device->Connected) + { + now = timeGetTime(); + + avail = (ALuint64)(now-start) * Device->Frequency / 1000; + if(avail < done) + { + /* Timer wrapped. Add the remainder of the cycle to the available + * count and reset the number of samples done */ + avail += (ALuint64)0xFFFFFFFFu*Device->Frequency/1000 - done; + done = 0; + } + if(avail-done < Device->UpdateSize) + { + Sleep(restTime); + continue; + } + + while(avail-done >= Device->UpdateSize) + { + aluMixData(Device, data->buffer, Device->UpdateSize); + done += Device->UpdateSize; + } + } + + return 0; +} + +static ALCboolean null_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + null_data *data; + + if(!deviceName) + deviceName = nullDevice; + else if(strcmp(deviceName, nullDevice) != 0) + return ALC_FALSE; + + data = (null_data*)calloc(1, sizeof(*data)); + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void null_close_playback(ALCdevice *device) +{ + null_data *data = (null_data*)device->ExtraData; + + free(data); + device->ExtraData = NULL; +} + +static ALCboolean null_reset_playback(ALCdevice *device) +{ + null_data *data = (null_data*)device->ExtraData; + + data->size = device->UpdateSize * FrameSizeFromDevFmt(device->FmtChans, + device->FmtType); + data->buffer = malloc(data->size); + if(!data->buffer) + { + AL_PRINT("buffer malloc failed\n"); + return ALC_FALSE; + } + SetDefaultWFXChannelOrder(device); + + data->thread = StartThread(NullProc, device); + if(data->thread == NULL) + { + free(data->buffer); + data->buffer = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void null_stop_playback(ALCdevice *device) +{ + null_data *data = (null_data*)device->ExtraData; + + if(!data->thread) + return; + + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + + data->killNow = 0; + + free(data->buffer); + data->buffer = NULL; +} + + +static ALCboolean null_open_capture(ALCdevice *device, const ALCchar *deviceName) +{ + (void)device; + (void)deviceName; + return ALC_FALSE; +} + + +BackendFuncs null_funcs = { + null_open_playback, + null_close_playback, + null_reset_playback, + null_stop_playback, + null_open_capture, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +void alc_null_init(BackendFuncs *func_list) +{ + *func_list = null_funcs; +} + +void alc_null_deinit(void) +{ +} + +void alc_null_probe(int type) +{ + if(type == DEVICE_PROBE) + AppendDeviceList(nullDevice); + else if(type == ALL_DEVICE_PROBE) + AppendAllDeviceList(nullDevice); +} diff --git a/jni/OpenAL/Alc/opensles.c b/jni/OpenAL/Alc/opensles.c new file mode 100644 index 0000000..44cedea --- /dev/null +++ b/jni/OpenAL/Alc/opensles.c @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This is an OpenAL backend for Android using the native audio APIs based on OpenSL ES 1.0.1. + * It is based on source code for the native-audio sample app bundled with NDK. + */ + +#include +#include +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#include "verde/verde_helpers.h" + +#define LOG_NDEBUG 0 +#define LOG_TAG "OpenAL" + +// for __android_log_print(ANDROID_LOG_INFO, "YourApp", "formatted message"); +#if 1 +#define LOGV(...) VERDE_DEBUG(__VA_ARGS__) +#endif + +// for native audio +#include +#include + +#include "apportable_openal_funcs.h" + +// engine interfaces +static SLObjectItf engineObject = NULL; +static SLEngineItf engineEngine; + +// output mix interfaces +static SLObjectItf outputMixObject = NULL; + +// buffer queue player interfaces +static SLObjectItf bqPlayerObject = NULL; +static SLPlayItf bqPlayerPlay; +static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; + +// this callback handler is called every time a buffer finishes playing +static void opensles_callback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + // LOGV("opensles_callback"); +#define bufferSize (1024*4) + static char buffer0[bufferSize], buffer1[bufferSize]; + static char * const buffers[2] = {buffer0, buffer1}; + static unsigned bufferIndex = 0; +#if 0 + static unsigned nextCount = ~0; + static char nextBuffer[1024] = "Hello, world!"; + static unsigned nextSize = 1024; +#endif + + assert(bq == bqPlayerBufferQueue); + assert(NULL != context); + ALCdevice *pDevice = (ALCdevice *) context; + ALint frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + void *buffer = buffers[bufferIndex]; + // LOGV("bq=%p, pDevice=%p, frameSize=%u, buffer=%p, bufferIndex=%u, bufferSize=%u", bq, pDevice, frameSize, buffer, bufferIndex, bufferSize); + bufferIndex ^= 1; + // LOGV("aluMixData"); + + aluMixData(pDevice, buffer, bufferSize/frameSize); + + // LOGV("Enqueue2"); +#if 0 + // for streaming playback, replace this test by logic to find and fill the next buffer + if (--nextCount > 0 && NULL != nextBuffer && 0 != nextSize) { + SLresult result; + // enqueue another buffer + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, nextBuffer, nextSize); + // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, + // which for this code example would indicate a programming error + assert(SL_RESULT_SUCCESS == result); + } +#else + SLresult result; + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, bufferSize); + assert(SL_RESULT_SUCCESS == result); +#endif +} + + +static const ALCchar opensles_device[] = "OpenSL ES"; + +// Apportable extensions +SLEngineItf alc_opensles_get_native_audio_engine_engine() +{ + return engineEngine; +} + +SLEngineItf alc_opensles_get_native_audio_output_mix() +{ + return outputMixObject; +} + +SLresult alc_opensles_create_native_audio_engine() +{ + if (engineObject) + return SL_RESULT_SUCCESS; + + SLresult result; + + // create engine + result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + + // realize the engine + result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + + // get the engine interface, which is needed in order to create other objects + result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); + assert(SL_RESULT_SUCCESS == result); + + // create output mix + result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + + // realize the output mix + result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + + return result; +} + +// Backend functions, in same order as type BackendFuncs +static ALCboolean opensles_open_playback(ALCdevice *pDevice, const ALCchar *deviceName) +{ + LOGV("opensles_open_playback pDevice=%p, deviceName=%s", pDevice, deviceName); + + // create the engine and output mix objects + SLresult result = alc_opensles_create_native_audio_engine(); + + // create buffer queue audio player + + // configure audio source + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; + SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, + SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN}; + SLDataSource audioSrc = {&loc_bufq, &format_pcm}; + + // configure audio sink + SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; + SLDataSink audioSnk = {&loc_outmix, NULL}; + + // create audio player + LOGV("create audio player"); + const SLInterfaceID ids[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean req[1] = {SL_BOOLEAN_TRUE}; + result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, + 1, ids, req); + assert(SL_RESULT_SUCCESS == result); + + // realize the player + result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + + // get the play interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); + assert(SL_RESULT_SUCCESS == result); + + // get the buffer queue interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, + &bqPlayerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + + // register callback on the buffer queue + result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, opensles_callback, (void *) pDevice); + assert(SL_RESULT_SUCCESS == result); + + // set the player's state to playing + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); + assert(SL_RESULT_SUCCESS == result); + + // enqueue the first buffer to kick off the callbacks + LOGV("enqueue"); + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, "\0", 1); + assert(SL_RESULT_SUCCESS == result); + + return ALC_TRUE; +} + + +static void opensles_close_playback(ALCdevice *pDevice) +{ + LOGV("opensles_close_playback pDevice=%p", pDevice); + + // shut down the native audio system + + // destroy buffer queue audio player object, and invalidate all associated interfaces + if (bqPlayerObject != NULL) { + (*bqPlayerObject)->Destroy(bqPlayerObject); + bqPlayerObject = NULL; + bqPlayerPlay = NULL; + bqPlayerBufferQueue = NULL; + } + + // destroy output mix object, and invalidate all associated interfaces + if (outputMixObject != NULL) { + (*outputMixObject)->Destroy(outputMixObject); + outputMixObject = NULL; + } + + // destroy engine object, and invalidate all associated interfaces + if (engineObject != NULL) { + (*engineObject)->Destroy(engineObject); + engineObject = NULL; + engineEngine = NULL; + } + +} + +static ALCboolean opensles_reset_playback(ALCdevice *pDevice) +{ + LOGV("opensles_reset_playback pDevice=%p", pDevice); + unsigned bits = BytesFromDevFmt(pDevice->FmtType) * 8; + unsigned channels = ChannelsFromDevFmt(pDevice->FmtChans); + unsigned samples = pDevice->UpdateSize; + unsigned size = samples * channels * bits / 8; + LOGV("bits=%u, channels=%u, samples=%u, size=%u, freq=%u", bits, channels, samples, size, pDevice->Frequency); + SetDefaultWFXChannelOrder(pDevice); + +#if 0 + if (pSDL_WasInit(SDL_INIT_AUDIO)) + { + pSDL_PauseAudio(1); + pSDL_CloseAudio(); + pSDL_QuitSubSystem(SDL_INIT_AUDIO); + } + + switch (aluBytesFromFormat(device->Format)) + { + case 1: + desired.format = AUDIO_U8; + break; + case 4: + switch (ChannelsFromDevFormat(device->DevFmt)) + { + case 1: device->Format = AL_FORMAT_MONO16; break; + case 2: device->Format = AL_FORMAT_STEREO16; break; + case 4: device->Format = AL_FORMAT_QUAD16; break; + case 6: device->Format = AL_FORMAT_51CHN16; break; + case 7: device->Format = AL_FORMAT_61CHN16; break; + case 8: device->Format = AL_FORMAT_71CHN16; break; + } + /* fall-through */ + case 2: + desired.format = AUDIO_S16; + break; + default: + AL_PRINT("Unknown format: 0x%x\n", device->Format); + return ALC_FALSE; + } + + desired.freq = device->Frequency; + desired.channels = aluChannelsFromFormat(device->Format); + desired.samples = device->UpdateSize * desired.channels; + desired.callback = opensles_callback; + desired.userdata = device; + + device->NumUpdates = 2; + + if (pSDL_InitSubSystem(SDL_INIT_AUDIO) == -1) + { + AL_PRINT("SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s\n", pSDL_GetError()); + return ALC_FALSE; + } + + if (pSDL_OpenAudio(&desired, NULL) == -1) + { + AL_PRINT("SDL_OpenAudio failed: %s\n", pSDL_GetError()); + pSDL_QuitSubSystem(SDL_INIT_AUDIO); + return ALC_FALSE; + } + + pSDL_PauseAudio(0); +#endif + + return ALC_TRUE; +} + + +static void opensles_stop_playback(ALCdevice *pDevice) +{ + LOGV("opensles_stop_playback device=%p", pDevice); +} + +static ALCboolean opensles_open_capture(ALCdevice *pDevice, const ALCchar *deviceName) +{ + LOGV("opensles_open_capture device=%p, deviceName=%s", pDevice, deviceName); + return ALC_FALSE; +} + +static void opensles_close_capture(ALCdevice *pDevice) +{ + LOGV("opensles_closed_capture device=%p", pDevice); +} + +static void opensles_start_capture(ALCdevice *pDevice) +{ + LOGV("opensles_start_capture device=%p", pDevice); +} + +static void opensles_stop_capture(ALCdevice *pDevice) +{ + LOGV("opensles_stop_capture device=%p", pDevice); +} + +static void opensles_capture_samples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples) +{ + LOGV("opensles_capture_samples device=%p, pBuffer=%p, lSamples=%u", pDevice, pBuffer, lSamples); +} + +static ALCuint opensles_available_samples(ALCdevice *pDevice) +{ + LOGV("opensles_available_samples device=%p", pDevice); + return 0; +} + +// table of backend function pointers + +BackendFuncs opensles_funcs = { + opensles_open_playback, + opensles_close_playback, + opensles_reset_playback, + opensles_stop_playback, + opensles_open_capture, + opensles_close_capture, + opensles_start_capture, + opensles_stop_capture, + opensles_capture_samples, + opensles_available_samples +}; + +// global entry points called from XYZZY + + +void alc_opensles_suspend() +{ + SLresult result; + + if (bqPlayerPlay) { + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED); + assert(SL_RESULT_SUCCESS == result); + result = (*bqPlayerBufferQueue)->Clear(bqPlayerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + } +} + +void alc_opensles_resume() +{ + SLresult result; + + if (bqPlayerPlay) { + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); + assert(SL_RESULT_SUCCESS == result); + // Pump some blank data into the buffer to stimulate the callback + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, "\0", 1); + assert(SL_RESULT_SUCCESS == result); + } +} + +void alc_opensles_init(BackendFuncs *func_list) +{ + LOGV("alc_opensles_init"); + *func_list = opensles_funcs; + apportableOpenALFuncs.alc_android_suspend = alc_opensles_suspend; + apportableOpenALFuncs.alc_android_resume = alc_opensles_resume; + apportableOpenALFuncs.alc_opensles_get_native_audio_engine_engine = alc_opensles_get_native_audio_engine_engine; + apportableOpenALFuncs.alc_opensles_get_native_audio_output_mix = alc_opensles_get_native_audio_output_mix; + apportableOpenALFuncs.alc_opensles_create_native_audio_engine = alc_opensles_create_native_audio_engine; +} + +void alc_opensles_deinit(void) +{ + LOGV("alc_opensles_deinit"); +} + +void alc_opensles_probe(int type) +{ + switch (type) { + case DEVICE_PROBE: + LOGV("alc_opensles_probe DEVICE_PROBE"); + AppendDeviceList(opensles_device); + break; + case ALL_DEVICE_PROBE: + LOGV("alc_opensles_probe ALL_DEVICE_PROBE"); + AppendAllDeviceList(opensles_device); + break; + default: + LOGV("alc_opensles_probe type=%d", type); + break; + } +} diff --git a/jni/OpenAL/Alc/oss.c b/jni/OpenAL/Alc/oss.c new file mode 100644 index 0000000..ea18689 --- /dev/null +++ b/jni/OpenAL/Alc/oss.c @@ -0,0 +1,521 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#include + +/* + * 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 + +static const ALCchar oss_device[] = "OSS Default"; + +typedef struct { + int fd; + volatile int killNow; + ALvoid *thread; + + ALubyte *mix_data; + int data_size; + + RingBuffer *ring; + int doCapture; +} oss_data; + + +static int log2i(ALCuint x) +{ + int y = 0; + while (x > 1) + { + x >>= 1; + y++; + } + return y; +} + + +static ALuint OSSProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + oss_data *data = (oss_data*)pDevice->ExtraData; + ALint frameSize; + ssize_t wrote; + + SetRTPriority(); + + frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + while(!data->killNow && pDevice->Connected) + { + ALint len = data->data_size; + ALubyte *WritePtr = data->mix_data; + + aluMixData(pDevice, WritePtr, len/frameSize); + while(len > 0 && !data->killNow) + { + wrote = write(data->fd, WritePtr, len); + if(wrote < 0) + { + if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) + { + AL_PRINT("write failed: %s\n", strerror(errno)); + aluHandleDisconnect(pDevice); + break; + } + + Sleep(1); + continue; + } + + len -= wrote; + WritePtr += wrote; + } + } + + return 0; +} + +static ALuint OSSCaptureProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + oss_data *data = (oss_data*)pDevice->ExtraData; + int frameSize; + int amt; + + SetRTPriority(); + + frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + while(!data->killNow) + { + amt = read(data->fd, data->mix_data, data->data_size); + if(amt < 0) + { + AL_PRINT("read failed: %s\n", strerror(errno)); + aluHandleDisconnect(pDevice); + break; + } + if(amt == 0) + { + Sleep(1); + continue; + } + if(data->doCapture) + WriteRingBuffer(data->ring, data->mix_data, amt/frameSize); + } + + return 0; +} + +static ALCboolean oss_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + char driver[64]; + oss_data *data; + + strncpy(driver, GetConfigValue("oss", "device", "/dev/dsp"), sizeof(driver)-1); + driver[sizeof(driver)-1] = 0; + if(!deviceName) + deviceName = oss_device; + else if(strcmp(deviceName, oss_device) != 0) + return ALC_FALSE; + + data = (oss_data*)calloc(1, sizeof(oss_data)); + data->killNow = 0; + + data->fd = open(driver, O_WRONLY); + if(data->fd == -1) + { + free(data); + AL_PRINT("Could not open %s: %s\n", driver, strerror(errno)); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void oss_close_playback(ALCdevice *device) +{ + oss_data *data = (oss_data*)device->ExtraData; + + close(data->fd); + free(data); + device->ExtraData = NULL; +} + +static ALCboolean oss_reset_playback(ALCdevice *device) +{ + oss_data *data = (oss_data*)device->ExtraData; + 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 DevFmtFloat: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + ossFormat = AFMT_S16_NE; + break; + } + + periods = device->NumUpdates; + numChannels = ChannelsFromDevFmt(device->FmtChans); + frameSize = numChannels * BytesFromDevFmt(device->FmtType); + + ossSpeed = device->Frequency; + log2FragmentSize = log2i(device->UpdateSize * frameSize); + + /* according to the OSS spec, 16 bytes are the minimum */ + if (log2FragmentSize < 4) + log2FragmentSize = 4; + /* Subtract one period since the temp mixing buffer counts as one. Still + * need at least two on the card, though. */ + if(periods > 2) periods--; + 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(data->fd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_SETFMT, &ossFormat)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_CHANNELS, &numChannels)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_SPEED, &ossSpeed)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_GETOSPACE, &info)); + if(0) + { + err: + AL_PRINT("%s failed: %s\n", err, strerror(errno)); + return ALC_FALSE; + } +#undef CHECKERR + + if((int)ChannelsFromDevFmt(device->FmtChans) != numChannels) + { + AL_PRINT("Could not set %d channels, got %d instead\n", ChannelsFromDevFmt(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))) + { + AL_PRINT("Could not set %#x format type, got OSS format %#x\n", device->FmtType, ossFormat); + return ALC_FALSE; + } + + device->Frequency = ossSpeed; + device->UpdateSize = info.fragsize / frameSize; + device->NumUpdates = info.fragments + 1; + + data->data_size = device->UpdateSize * frameSize; + data->mix_data = calloc(1, data->data_size); + + SetDefaultChannelOrder(device); + + data->thread = StartThread(OSSProc, device); + if(data->thread == NULL) + { + free(data->mix_data); + data->mix_data = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void oss_stop_playback(ALCdevice *device) +{ + oss_data *data = (oss_data*)device->ExtraData; + + if(!data->thread) + return; + + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + + data->killNow = 0; + if(ioctl(data->fd, SNDCTL_DSP_RESET) != 0) + AL_PRINT("Error resetting device: %s\n", strerror(errno)); + + free(data->mix_data); + data->mix_data = NULL; +} + + +static ALCboolean oss_open_capture(ALCdevice *device, const ALCchar *deviceName) +{ + int numFragmentsLogSize; + int log2FragmentSize; + unsigned int periods; + audio_buf_info info; + ALuint frameSize; + int numChannels; + char driver[64]; + oss_data *data; + int ossFormat; + int ossSpeed; + char *err; + + strncpy(driver, GetConfigValue("oss", "capture", "/dev/dsp"), sizeof(driver)-1); + driver[sizeof(driver)-1] = 0; + if(!deviceName) + deviceName = oss_device; + else if(strcmp(deviceName, oss_device) != 0) + return ALC_FALSE; + + data = (oss_data*)calloc(1, sizeof(oss_data)); + data->killNow = 0; + + data->fd = open(driver, O_RDONLY); + if(data->fd == -1) + { + free(data); + AL_PRINT("Could not open %s: %s\n", driver, strerror(errno)); + return ALC_FALSE; + } + + 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 DevFmtFloat: + free(data); + AL_PRINT("Format type %#x capture not supported on OSS\n", device->FmtType); + return ALC_FALSE; + } + + periods = 4; + numChannels = ChannelsFromDevFmt(device->FmtChans); + 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(data->fd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_SETFMT, &ossFormat)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_CHANNELS, &numChannels)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_SPEED, &ossSpeed)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_GETISPACE, &info)); + if(0) + { + err: + AL_PRINT("%s failed: %s\n", err, strerror(errno)); + close(data->fd); + free(data); + return ALC_FALSE; + } +#undef CHECKERR + + if((int)ChannelsFromDevFmt(device->FmtChans) != numChannels) + { + AL_PRINT("Could not set %d channels, got %d instead\n", ChannelsFromDevFmt(device->FmtChans), numChannels); + close(data->fd); + free(data); + return ALC_FALSE; + } + + if(!((ossFormat == AFMT_S8 && device->FmtType == DevFmtByte) || + (ossFormat == AFMT_U8 && device->FmtType == DevFmtUByte) || + (ossFormat == AFMT_S16_NE && device->FmtType == DevFmtShort))) + { + AL_PRINT("Could not set %#x format type, got OSS format %#x\n", device->FmtType, ossFormat); + close(data->fd); + free(data); + return ALC_FALSE; + } + + data->ring = CreateRingBuffer(frameSize, device->UpdateSize * device->NumUpdates); + if(!data->ring) + { + AL_PRINT("ring buffer create failed\n"); + close(data->fd); + free(data); + return ALC_FALSE; + } + + data->data_size = info.fragsize; + data->mix_data = calloc(1, data->data_size); + + device->ExtraData = data; + data->thread = StartThread(OSSCaptureProc, device); + if(data->thread == NULL) + { + device->ExtraData = NULL; + free(data->mix_data); + free(data); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + return ALC_TRUE; +} + +static void oss_close_capture(ALCdevice *device) +{ + oss_data *data = (oss_data*)device->ExtraData; + data->killNow = 1; + StopThread(data->thread); + + close(data->fd); + + DestroyRingBuffer(data->ring); + + free(data->mix_data); + free(data); + device->ExtraData = NULL; +} + +static void oss_start_capture(ALCdevice *pDevice) +{ + oss_data *data = (oss_data*)pDevice->ExtraData; + data->doCapture = 1; +} + +static void oss_stop_capture(ALCdevice *pDevice) +{ + oss_data *data = (oss_data*)pDevice->ExtraData; + data->doCapture = 0; +} + +static void oss_capture_samples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples) +{ + oss_data *data = (oss_data*)pDevice->ExtraData; + if(lSamples <= (ALCuint)RingBufferSize(data->ring)) + ReadRingBuffer(data->ring, pBuffer, lSamples); + else + alcSetError(pDevice, ALC_INVALID_VALUE); +} + +static ALCuint oss_available_samples(ALCdevice *pDevice) +{ + oss_data *data = (oss_data*)pDevice->ExtraData; + return RingBufferSize(data->ring); +} + + +BackendFuncs oss_funcs = { + oss_open_playback, + oss_close_playback, + oss_reset_playback, + oss_stop_playback, + oss_open_capture, + oss_close_capture, + oss_start_capture, + oss_stop_capture, + oss_capture_samples, + oss_available_samples +}; + +void alc_oss_init(BackendFuncs *func_list) +{ + *func_list = oss_funcs; +} + +void alc_oss_deinit(void) +{ +} + +void alc_oss_probe(int type) +{ + if(type == DEVICE_PROBE) + { +#ifdef HAVE_STAT + struct stat buf; + if(stat(GetConfigValue("oss", "device", "/dev/dsp"), &buf) == 0) +#endif + AppendDeviceList(oss_device); + } + else if(type == ALL_DEVICE_PROBE) + { +#ifdef HAVE_STAT + struct stat buf; + if(stat(GetConfigValue("oss", "device", "/dev/dsp"), &buf) == 0) +#endif + AppendAllDeviceList(oss_device); + } + else if(type == CAPTURE_DEVICE_PROBE) + { +#ifdef HAVE_STAT + struct stat buf; + if(stat(GetConfigValue("oss", "capture", "/dev/dsp"), &buf) == 0) +#endif + AppendCaptureDeviceList(oss_device); + } +} diff --git a/jni/OpenAL/Alc/panning.c b/jni/OpenAL/Alc/panning.c new file mode 100644 index 0000000..54921d5 --- /dev/null +++ b/jni/OpenAL/Alc/panning.c @@ -0,0 +1,364 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2010 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" +#include "alu.h" + +static void SetSpeakerArrangement(const char *name, ALfp SpeakerAngle[MAXCHANNELS], + Channel Speaker2Chan[MAXCHANNELS], ALint chans) +{ + char layout_str[256]; + char *confkey, *next; + char *sep, *end; + Channel val; + int i; + + if(!ConfigValueExists(NULL, name)) + name = "layout"; + + strncpy(layout_str, GetConfigValue(NULL, name, ""), sizeof(layout_str)); + layout_str[sizeof(layout_str)-1] = 0; + + if(!layout_str[0]) + return; + + next = confkey = layout_str; + while(next && *next) + { + confkey = next; + next = strchr(confkey, ','); + if(next) + { + *next = 0; + do { + next++; + } while(isspace(*next) || *next == ','); + } + + sep = strchr(confkey, '='); + if(!sep || confkey == sep) + continue; + + end = sep - 1; + while(isspace(*end) && end != confkey) + end--; + *(++end) = 0; + + if(strcmp(confkey, "fl") == 0 || strcmp(confkey, "front-left") == 0) + val = FRONT_LEFT; + else if(strcmp(confkey, "fr") == 0 || strcmp(confkey, "front-right") == 0) + val = FRONT_RIGHT; + else if(strcmp(confkey, "fc") == 0 || strcmp(confkey, "front-center") == 0) + val = FRONT_CENTER; + else if(strcmp(confkey, "bl") == 0 || strcmp(confkey, "back-left") == 0) + val = BACK_LEFT; + else if(strcmp(confkey, "br") == 0 || strcmp(confkey, "back-right") == 0) + val = BACK_RIGHT; + else if(strcmp(confkey, "bc") == 0 || strcmp(confkey, "back-center") == 0) + val = BACK_CENTER; + else if(strcmp(confkey, "sl") == 0 || strcmp(confkey, "side-left") == 0) + val = SIDE_LEFT; + else if(strcmp(confkey, "sr") == 0 || strcmp(confkey, "side-right") == 0) + val = SIDE_RIGHT; + else + { + AL_PRINT("Unknown speaker for %s: \"%s\"\n", name, confkey); + continue; + } + + *(sep++) = 0; + while(isspace(*sep)) + sep++; + + for(i = 0;i < chans;i++) + { + if(Speaker2Chan[i] == val) + { + long angle = strtol(sep, NULL, 10); + if(angle >= -180 && angle <= 180) + SpeakerAngle[i] = ALfpMult(int2ALfp(angle), float2ALfp(M_PI/180.0f)); + else + AL_PRINT("Invalid angle for speaker \"%s\": %ld\n", confkey, angle); + break; + } + } + } + + for(i = 0;i < chans;i++) + { + int min = i; + int i2; + + for(i2 = i+1;i2 < chans;i2++) + { + if(SpeakerAngle[i2] < SpeakerAngle[min]) + min = i2; + } + + if(min != i) + { + ALfp tmpf; + Channel tmpc; + + tmpf = SpeakerAngle[i]; + SpeakerAngle[i] = SpeakerAngle[min]; + SpeakerAngle[min] = tmpf; + + tmpc = Speaker2Chan[i]; + Speaker2Chan[i] = Speaker2Chan[min]; + Speaker2Chan[min] = tmpc; + } + } +} + +static ALfp aluLUTpos2Angle(ALint pos) +{ + if(pos < QUADRANT_NUM) + return aluAtan(ALfpDiv(int2ALfp(pos), int2ALfp(QUADRANT_NUM - pos))); + if(pos < 2 * QUADRANT_NUM) + return (float2ALfp(M_PI_2) + aluAtan(ALfpDiv(int2ALfp(pos - QUADRANT_NUM),int2ALfp(2 * QUADRANT_NUM - pos)))); + if(pos < 3 * QUADRANT_NUM) + return (aluAtan(ALfpDiv(int2ALfp(pos - 2 * QUADRANT_NUM), int2ALfp(3 * QUADRANT_NUM - pos))) - float2ALfp(M_PI)); + return (aluAtan(ALfpDiv(int2ALfp(pos - 3 * QUADRANT_NUM), int2ALfp(4 * QUADRANT_NUM - pos))) - float2ALfp(M_PI)); +} + +ALint aluCart2LUTpos(ALfp re, ALfp im) +{ + ALint pos = 0; + ALfp denom = (aluFabs(re) + aluFabs(im)); + if(denom > int2ALfp(0)) + pos = (ALint)ALfp2int(ALfpDiv(ALfpMult(int2ALfp(QUADRANT_NUM),aluFabs(im)), (denom + float2ALfp(0.5)))); + + if(re < int2ALfp(0)) + pos = 2 * QUADRANT_NUM - pos; + if(im < int2ALfp(0)) + pos = LUT_NUM - pos; + return pos%LUT_NUM; +} + +ALvoid aluInitPanning(ALCdevice *Device) +{ + ALfp SpeakerAngle[MAXCHANNELS]; + ALfp (*Matrix)[MAXCHANNELS]; + Channel *Speaker2Chan; + ALfp Alpha, Theta; + ALfp *PanningLUT; + ALint pos, offset; + ALuint s, s2; + + for(s = 0;s < MAXCHANNELS;s++) + { + for(s2 = 0;s2 < MAXCHANNELS;s2++) + Device->ChannelMatrix[s][s2] = ((s==s2) ? int2ALfp(1) : int2ALfp(0)); + } + + Speaker2Chan = Device->Speaker2Chan; + Matrix = Device->ChannelMatrix; + switch(Device->FmtChans) + { + case DevFmtMono: + Matrix[FRONT_LEFT][FRONT_CENTER] = aluSqrt(float2ALfp(0.5)); + Matrix[FRONT_RIGHT][FRONT_CENTER] = aluSqrt(float2ALfp(0.5)); + Matrix[SIDE_LEFT][FRONT_CENTER] = aluSqrt(float2ALfp(0.5)); + Matrix[SIDE_RIGHT][FRONT_CENTER] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_LEFT][FRONT_CENTER] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_RIGHT][FRONT_CENTER] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_CENTER][FRONT_CENTER] = int2ALfp(1); + Device->NumChan = 1; + Speaker2Chan[0] = FRONT_CENTER; + SpeakerAngle[0] = int2ALfp(0); + break; + + case DevFmtStereo: +#ifdef APPORTABLE_OPTIMIZED_OUT + // Leave as identity matrix if Apportable-optimized + Matrix[FRONT_CENTER][FRONT_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[FRONT_CENTER][FRONT_RIGHT] = aluSqrt(float2ALfp(0.5)); + Matrix[SIDE_LEFT][FRONT_LEFT] = int2ALfp(1); + Matrix[SIDE_RIGHT][FRONT_RIGHT] = int2ALfp(1); + Matrix[BACK_LEFT][FRONT_LEFT] = int2ALfp(1); + Matrix[BACK_RIGHT][FRONT_RIGHT] = int2ALfp(1); + Matrix[BACK_CENTER][FRONT_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_CENTER][FRONT_RIGHT] = aluSqrt(float2ALfp(0.5)); +#endif + Device->NumChan = 2; + Speaker2Chan[0] = FRONT_LEFT; + Speaker2Chan[1] = FRONT_RIGHT; + SpeakerAngle[0] = float2ALfp(-90.0f * M_PI/180.0f); + SpeakerAngle[1] = float2ALfp( 90.0f * M_PI/180.0f); + SetSpeakerArrangement("layout_STEREO", SpeakerAngle, Speaker2Chan, Device->NumChan); + break; + + case DevFmtQuad: + Matrix[FRONT_CENTER][FRONT_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[FRONT_CENTER][FRONT_RIGHT] = aluSqrt(float2ALfp(0.5)); + Matrix[SIDE_LEFT][FRONT_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[SIDE_LEFT][BACK_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[SIDE_RIGHT][FRONT_RIGHT] = aluSqrt(float2ALfp(0.5)); + Matrix[SIDE_RIGHT][BACK_RIGHT] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_CENTER][BACK_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_CENTER][BACK_RIGHT] = aluSqrt(float2ALfp(0.5)); + Device->NumChan = 4; + Speaker2Chan[0] = BACK_LEFT; + Speaker2Chan[1] = FRONT_LEFT; + Speaker2Chan[2] = FRONT_RIGHT; + Speaker2Chan[3] = BACK_RIGHT; + SpeakerAngle[0] = float2ALfp(-135.0f * M_PI/180.0f); + SpeakerAngle[1] = float2ALfp( -45.0f * M_PI/180.0f); + SpeakerAngle[2] = float2ALfp( 45.0f * M_PI/180.0f); + SpeakerAngle[3] = float2ALfp( 135.0f * M_PI/180.0f); + SetSpeakerArrangement("layout_QUAD", SpeakerAngle, Speaker2Chan, Device->NumChan); + break; + + case DevFmtX51: + Matrix[SIDE_LEFT][FRONT_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[SIDE_LEFT][BACK_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[SIDE_RIGHT][FRONT_RIGHT] = aluSqrt(float2ALfp(0.5)); + Matrix[SIDE_RIGHT][BACK_RIGHT] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_CENTER][BACK_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_CENTER][BACK_RIGHT] = aluSqrt(float2ALfp(0.5)); + Device->NumChan = 5; + Speaker2Chan[0] = BACK_LEFT; + Speaker2Chan[1] = FRONT_LEFT; + Speaker2Chan[2] = FRONT_CENTER; + Speaker2Chan[3] = FRONT_RIGHT; + Speaker2Chan[4] = BACK_RIGHT; + SpeakerAngle[0] = float2ALfp(-110.0f * M_PI/180.0f); + SpeakerAngle[1] = float2ALfp( -30.0f * M_PI/180.0f); + SpeakerAngle[2] = float2ALfp( 0.0f * M_PI/180.0f); + SpeakerAngle[3] = float2ALfp( 30.0f * M_PI/180.0f); + SpeakerAngle[4] = float2ALfp( 110.0f * M_PI/180.0f); + SetSpeakerArrangement("layout_51CHN", SpeakerAngle, Speaker2Chan, Device->NumChan); + break; + + case DevFmtX61: + Matrix[BACK_LEFT][BACK_CENTER] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_LEFT][SIDE_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_RIGHT][BACK_CENTER] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_RIGHT][SIDE_RIGHT] = aluSqrt(float2ALfp(0.5)); + Device->NumChan = 6; + Speaker2Chan[0] = SIDE_LEFT; + Speaker2Chan[1] = FRONT_LEFT; + Speaker2Chan[2] = FRONT_CENTER; + Speaker2Chan[3] = FRONT_RIGHT; + Speaker2Chan[4] = SIDE_RIGHT; + Speaker2Chan[5] = BACK_CENTER; + SpeakerAngle[0] = float2ALfp(-90.0f * M_PI/180.0f); + SpeakerAngle[1] = float2ALfp(-30.0f * M_PI/180.0f); + SpeakerAngle[2] = float2ALfp( 0.0f * M_PI/180.0f); + SpeakerAngle[3] = float2ALfp( 30.0f * M_PI/180.0f); + SpeakerAngle[4] = float2ALfp( 90.0f * M_PI/180.0f); + SpeakerAngle[5] = float2ALfp(180.0f * M_PI/180.0f); + SetSpeakerArrangement("layout_61CHN", SpeakerAngle, Speaker2Chan, Device->NumChan); + break; + + case DevFmtX71: + Matrix[BACK_CENTER][BACK_LEFT] = aluSqrt(float2ALfp(0.5)); + Matrix[BACK_CENTER][BACK_RIGHT] = aluSqrt(float2ALfp(0.5)); + Device->NumChan = 7; + Speaker2Chan[0] = BACK_LEFT; + Speaker2Chan[1] = SIDE_LEFT; + Speaker2Chan[2] = FRONT_LEFT; + Speaker2Chan[3] = FRONT_CENTER; + Speaker2Chan[4] = FRONT_RIGHT; + Speaker2Chan[5] = SIDE_RIGHT; + Speaker2Chan[6] = BACK_RIGHT; + SpeakerAngle[0] = float2ALfp(-150.0f * M_PI/180.0f); + SpeakerAngle[1] = float2ALfp( -90.0f * M_PI/180.0f); + SpeakerAngle[2] = float2ALfp( -30.0f * M_PI/180.0f); + SpeakerAngle[3] = float2ALfp( 0.0f * M_PI/180.0f); + SpeakerAngle[4] = float2ALfp( 30.0f * M_PI/180.0f); + SpeakerAngle[5] = float2ALfp( 90.0f * M_PI/180.0f); + SpeakerAngle[6] = float2ALfp( 150.0f * M_PI/180.0f); + SetSpeakerArrangement("layout_71CHN", SpeakerAngle, Speaker2Chan, Device->NumChan); + break; + } + + if(GetConfigValueBool(NULL, "scalemix", 0)) + { + ALfp maxout = int2ALfp(1);; + for(s = 0;s < MAXCHANNELS;s++) + { + ALfp out = int2ALfp(0); + for(s2 = 0;s2 < MAXCHANNELS;s2++) + out = (out + Device->ChannelMatrix[s2][s]); + maxout = __max(maxout, out); + } + + maxout = ALfpDiv(int2ALfp(1),maxout); + for(s = 0;s < MAXCHANNELS;s++) + { + for(s2 = 0;s2 < MAXCHANNELS;s2++) + Device->ChannelMatrix[s2][s] = ALfpMult(Device->ChannelMatrix[s2][s],maxout); + } + } + + PanningLUT = Device->PanningLUT; + for(pos = 0; pos < LUT_NUM; pos++) + { + /* clear all values */ + offset = MAXCHANNELS * pos; + for(s = 0; s < MAXCHANNELS; s++) + PanningLUT[offset+s] = int2ALfp(0); + + if(Device->NumChan == 1) + { + PanningLUT[offset + Speaker2Chan[0]] = int2ALfp(1); + continue; + } + + /* source angle */ + Theta = aluLUTpos2Angle(pos); + + /* set panning values */ + for(s = 0; s < Device->NumChan - 1; s++) + { + if(Theta >= SpeakerAngle[s] && Theta < SpeakerAngle[s+1]) + { + /* source between speaker s and speaker s+1 */ + Alpha = ALfpDiv(ALfpMult(float2ALfp(M_PI_2), (Theta-SpeakerAngle[s])), + (SpeakerAngle[s+1]-SpeakerAngle[s])); + PanningLUT[offset + Speaker2Chan[s]] = __cos(Alpha); + PanningLUT[offset + Speaker2Chan[s+1]] = __sin(Alpha); + break; + } + } + if(s == Device->NumChan - 1) + { + /* source between last and first speaker */ + if(Theta < SpeakerAngle[0]) + Theta = (Theta + float2ALfp(2.0f * M_PI)); + Alpha = ALfpDiv(ALfpMult(float2ALfp(M_PI_2), (Theta-SpeakerAngle[s])), + (float2ALfp(2.0f * M_PI) + SpeakerAngle[0]-SpeakerAngle[s])); + PanningLUT[offset + Speaker2Chan[s]] = __cos(Alpha); + PanningLUT[offset + Speaker2Chan[0]] = __sin(Alpha); + } + } +} diff --git a/jni/OpenAL/Alc/portaudio.c b/jni/OpenAL/Alc/portaudio.c new file mode 100644 index 0000000..77c7236 --- /dev/null +++ b/jni/OpenAL/Alc/portaudio.c @@ -0,0 +1,442 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" +#ifdef HAVE_DLFCN_H +#include +#endif + +#include + +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_GetStreamInfo); +#undef MAKE_FUNC + + +static const ALCchar pa_device[] = "PortAudio Default"; + + +void *pa_load(void) +{ + if(!pa_handle) + { + PaError err; + +#ifdef _WIN32 + pa_handle = LoadLibrary("portaudio.dll"); +#define LOAD_FUNC(x) do { \ + p##x = (typeof(p##x))GetProcAddress(pa_handle, #x); \ + if(!(p##x)) { \ + AL_PRINT("Could not load %s from portaudio.dll\n", #x); \ + FreeLibrary(pa_handle); \ + pa_handle = NULL; \ + return NULL; \ + } \ +} while(0) + +#elif defined(HAVE_DLFCN_H) + + const char *str; +#if defined(__APPLE__) && defined(__MACH__) +# define PALIB "libportaudio.2.dylib" +#else +# define PALIB "libportaudio.so.2" +#endif + pa_handle = dlopen(PALIB, RTLD_NOW); + dlerror(); + +#define LOAD_FUNC(f) do { \ + p##f = (typeof(f)*)dlsym(pa_handle, #f); \ + if((str=dlerror()) != NULL) \ + { \ + dlclose(pa_handle); \ + pa_handle = NULL; \ + AL_PRINT("Could not load %s from "PALIB": %s\n", #f, str); \ + return NULL; \ + } \ +} while(0) + +#else + pa_handle = (void*)0xDEADBEEF; +#define LOAD_FUNC(f) p##f = f +#endif + + if(!pa_handle) + return NULL; + +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_GetStreamInfo); + +#undef LOAD_FUNC + + if((err=pPa_Initialize()) != paNoError) + { + AL_PRINT("Pa_Initialize() returned an error: %s\n", pPa_GetErrorText(err)); +#ifdef _WIN32 + FreeLibrary(pa_handle); +#elif defined(HAVE_DLFCN_H) + dlclose(pa_handle); +#endif + pa_handle = NULL; + return NULL; + } + } + return pa_handle; +} + + +typedef struct { + PaStream *stream; + ALuint update_size; + + RingBuffer *ring; +} pa_data; + + +static int pa_callback(const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData) +{ + ALCdevice *device = (ALCdevice*)userData; + + (void)inputBuffer; + (void)timeInfo; + (void)statusFlags; + + aluMixData(device, outputBuffer, framesPerBuffer); + return 0; +} + +static int pa_capture_cb(const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData) +{ + ALCdevice *device = (ALCdevice*)userData; + pa_data *data = (pa_data*)device->ExtraData; + + (void)outputBuffer; + (void)timeInfo; + (void)statusFlags; + + WriteRingBuffer(data->ring, inputBuffer, framesPerBuffer); + return 0; +} + + +static ALCboolean pa_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + const PaStreamInfo *streamInfo; + PaStreamParameters outParams; + pa_data *data; + PaError err; + + if(!deviceName) + deviceName = pa_device; + else if(strcmp(deviceName, pa_device) != 0) + return ALC_FALSE; + + if(!pa_load()) + return ALC_FALSE; + + data = (pa_data*)calloc(1, sizeof(pa_data)); + data->update_size = device->UpdateSize; + + device->ExtraData = data; + + outParams.device = GetConfigValueInt("port", "device", -1); + if(outParams.device < 0) + outParams.device = pPa_GetDefaultOutputDevice(); + outParams.suggestedLatency = (device->UpdateSize*device->NumUpdates) / + (float)device->Frequency; + outParams.hostApiSpecificStreamInfo = NULL; + + switch(device->FmtType) + { + case DevFmtByte: + outParams.sampleFormat = paInt8; + break; + case DevFmtUByte: + outParams.sampleFormat = paUInt8; + break; + case DevFmtUShort: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + outParams.sampleFormat = paInt16; + break; + case DevFmtFloat: + outParams.sampleFormat = paFloat32; + break; + } + outParams.channelCount = ChannelsFromDevFmt(device->FmtChans); + + SetDefaultChannelOrder(device); + + err = pPa_OpenStream(&data->stream, NULL, &outParams, device->Frequency, + device->UpdateSize, paNoFlag, pa_callback, device); + if(err != paNoError) + { + AL_PRINT("Pa_OpenStream() returned an error: %s\n", pPa_GetErrorText(err)); + device->ExtraData = NULL; + free(data); + return ALC_FALSE; + } + streamInfo = pPa_GetStreamInfo(data->stream); + + device->szDeviceName = strdup(deviceName); + device->Frequency = streamInfo->sampleRate; + + return ALC_TRUE; +} + +static void pa_close_playback(ALCdevice *device) +{ + pa_data *data = (pa_data*)device->ExtraData; + PaError err; + + err = pPa_CloseStream(data->stream); + if(err != paNoError) + AL_PRINT("Error closing stream: %s\n", pPa_GetErrorText(err)); + + free(data); + device->ExtraData = NULL; +} + +static ALCboolean pa_reset_playback(ALCdevice *device) +{ + pa_data *data = (pa_data*)device->ExtraData; + const PaStreamInfo *streamInfo; + PaError err; + + streamInfo = pPa_GetStreamInfo(data->stream); + device->Frequency = streamInfo->sampleRate; + device->UpdateSize = data->update_size; + + err = pPa_StartStream(data->stream); + if(err != paNoError) + { + AL_PRINT("Pa_StartStream() returned an error: %s\n", pPa_GetErrorText(err)); + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void pa_stop_playback(ALCdevice *device) +{ + pa_data *data = (pa_data*)device->ExtraData; + PaError err; + + err = pPa_StopStream(data->stream); + if(err != paNoError) + AL_PRINT("Error stopping stream: %s\n", pPa_GetErrorText(err)); +} + + +static ALCboolean pa_open_capture(ALCdevice *device, const ALCchar *deviceName) +{ + PaStreamParameters inParams; + ALuint frame_size; + pa_data *data; + PaError err; + + if(!deviceName) + deviceName = pa_device; + else if(strcmp(deviceName, pa_device) != 0) + return ALC_FALSE; + + if(!pa_load()) + return ALC_FALSE; + + data = (pa_data*)calloc(1, sizeof(pa_data)); + if(data == NULL) + { + alcSetError(device, ALC_OUT_OF_MEMORY); + return ALC_FALSE; + } + + frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + data->ring = CreateRingBuffer(frame_size, device->UpdateSize*device->NumUpdates); + if(data->ring == NULL) + { + alcSetError(device, ALC_OUT_OF_MEMORY); + goto error; + } + + inParams.device = GetConfigValueInt("port", "capture", -1); + if(inParams.device < 0) + inParams.device = pPa_GetDefaultOutputDevice(); + inParams.suggestedLatency = 0.0f; + inParams.hostApiSpecificStreamInfo = NULL; + + switch(device->FmtType) + { + case DevFmtByte: + inParams.sampleFormat = paInt8; + break; + case DevFmtUByte: + inParams.sampleFormat = paUInt8; + break; + case DevFmtShort: + inParams.sampleFormat = paInt16; + break; + case DevFmtFloat: + inParams.sampleFormat = paFloat32; + break; + case DevFmtUShort: + AL_PRINT("Unsigned short not supported\n"); + goto error; + } + inParams.channelCount = ChannelsFromDevFmt(device->FmtChans); + + err = pPa_OpenStream(&data->stream, &inParams, NULL, device->Frequency, + paFramesPerBufferUnspecified, paNoFlag, pa_capture_cb, device); + if(err != paNoError) + { + AL_PRINT("Pa_OpenStream() returned an error: %s\n", pPa_GetErrorText(err)); + goto error; + } + + device->szDeviceName = strdup(deviceName); + + device->ExtraData = data; + return ALC_TRUE; + +error: + DestroyRingBuffer(data->ring); + free(data); + return ALC_FALSE; +} + +static void pa_close_capture(ALCdevice *device) +{ + pa_data *data = (pa_data*)device->ExtraData; + PaError err; + + err = pPa_CloseStream(data->stream); + if(err != paNoError) + AL_PRINT("Error closing stream: %s\n", pPa_GetErrorText(err)); + + free(data); + device->ExtraData = NULL; +} + +static void pa_start_capture(ALCdevice *device) +{ + pa_data *data = device->ExtraData; + PaError err; + + err = pPa_StartStream(data->stream); + if(err != paNoError) + AL_PRINT("Error starting stream: %s\n", pPa_GetErrorText(err)); +} + +static void pa_stop_capture(ALCdevice *device) +{ + pa_data *data = (pa_data*)device->ExtraData; + PaError err; + + err = pPa_StopStream(data->stream); + if(err != paNoError) + AL_PRINT("Error stopping stream: %s\n", pPa_GetErrorText(err)); +} + +static void pa_capture_samples(ALCdevice *device, ALCvoid *buffer, ALCuint samples) +{ + pa_data *data = device->ExtraData; + if(samples <= (ALCuint)RingBufferSize(data->ring)) + ReadRingBuffer(data->ring, buffer, samples); + else + alcSetError(device, ALC_INVALID_VALUE); +} + +static ALCuint pa_available_samples(ALCdevice *device) +{ + pa_data *data = device->ExtraData; + return RingBufferSize(data->ring); +} + + +static const BackendFuncs pa_funcs = { + pa_open_playback, + pa_close_playback, + pa_reset_playback, + pa_stop_playback, + pa_open_capture, + pa_close_capture, + pa_start_capture, + pa_stop_capture, + pa_capture_samples, + pa_available_samples +}; + +void alc_pa_init(BackendFuncs *func_list) +{ + *func_list = pa_funcs; +} + +void alc_pa_deinit(void) +{ + if(pa_handle) + { + pPa_Terminate(); +#ifdef _WIN32 + FreeLibrary(pa_handle); +#elif defined(HAVE_DLFCN_H) + dlclose(pa_handle); +#endif + pa_handle = NULL; + } +} + +void alc_pa_probe(int type) +{ + if(!pa_load()) return; + + if(type == DEVICE_PROBE) + AppendDeviceList(pa_device); + else if(type == ALL_DEVICE_PROBE) + AppendAllDeviceList(pa_device); + else if(type == CAPTURE_DEVICE_PROBE) + AppendCaptureDeviceList(pa_device); +} diff --git a/jni/OpenAL/Alc/pulseaudio.c b/jni/OpenAL/Alc/pulseaudio.c new file mode 100644 index 0000000..348f2d5 --- /dev/null +++ b/jni/OpenAL/Alc/pulseaudio.c @@ -0,0 +1,1358 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2009 by Konstantinos Natsakis + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "alMain.h" +#ifdef HAVE_DLFCN_H +#include +#endif + +#include + +#if PA_API_VERSION == 11 +#define PA_STREAM_ADJUST_LATENCY 0x2000U +#define PA_STREAM_EARLY_REQUESTS 0x4000U +static __inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) +{ + return (x == PA_STREAM_CREATING || x == PA_STREAM_READY); +} +static __inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) +{ + return (x == PA_CONTEXT_CONNECTING || x == PA_CONTEXT_AUTHORIZING || + x == PA_CONTEXT_SETTING_NAME || x == PA_CONTEXT_READY); +} +#define PA_STREAM_IS_GOOD PA_STREAM_IS_GOOD +#define PA_CONTEXT_IS_GOOD PA_CONTEXT_IS_GOOD +#elif PA_API_VERSION != 12 +#error Invalid PulseAudio API version +#endif + +#ifndef PA_CHECK_VERSION +#define PA_CHECK_VERSION(major,minor,micro) \ + ((PA_MAJOR > (major)) || \ + (PA_MAJOR == (major) && PA_MINOR > (minor)) || \ + (PA_MAJOR == (major) && PA_MINOR == (minor) && PA_MICRO >= (micro))) +#endif + +static void *pa_handle; +#define MAKE_FUNC(x) static typeof(x) * p##x +MAKE_FUNC(pa_context_unref); +MAKE_FUNC(pa_sample_spec_valid); +MAKE_FUNC(pa_stream_drop); +MAKE_FUNC(pa_strerror); +MAKE_FUNC(pa_context_get_state); +MAKE_FUNC(pa_stream_get_state); +MAKE_FUNC(pa_threaded_mainloop_signal); +MAKE_FUNC(pa_stream_peek); +MAKE_FUNC(pa_threaded_mainloop_wait); +MAKE_FUNC(pa_threaded_mainloop_unlock); +MAKE_FUNC(pa_threaded_mainloop_in_thread); +MAKE_FUNC(pa_context_new); +MAKE_FUNC(pa_threaded_mainloop_stop); +MAKE_FUNC(pa_context_disconnect); +MAKE_FUNC(pa_threaded_mainloop_start); +MAKE_FUNC(pa_threaded_mainloop_get_api); +MAKE_FUNC(pa_context_set_state_callback); +MAKE_FUNC(pa_stream_write); +MAKE_FUNC(pa_xfree); +MAKE_FUNC(pa_stream_connect_record); +MAKE_FUNC(pa_stream_connect_playback); +MAKE_FUNC(pa_stream_readable_size); +MAKE_FUNC(pa_stream_writable_size); +MAKE_FUNC(pa_stream_cork); +MAKE_FUNC(pa_stream_is_suspended); +MAKE_FUNC(pa_stream_get_device_name); +MAKE_FUNC(pa_path_get_filename); +MAKE_FUNC(pa_get_binary_name); +MAKE_FUNC(pa_threaded_mainloop_free); +MAKE_FUNC(pa_context_errno); +MAKE_FUNC(pa_xmalloc); +MAKE_FUNC(pa_stream_unref); +MAKE_FUNC(pa_threaded_mainloop_accept); +MAKE_FUNC(pa_stream_set_write_callback); +MAKE_FUNC(pa_threaded_mainloop_new); +MAKE_FUNC(pa_context_connect); +MAKE_FUNC(pa_stream_set_buffer_attr); +MAKE_FUNC(pa_stream_get_buffer_attr); +MAKE_FUNC(pa_stream_get_sample_spec); +MAKE_FUNC(pa_stream_get_time); +MAKE_FUNC(pa_stream_set_read_callback); +MAKE_FUNC(pa_stream_set_state_callback); +MAKE_FUNC(pa_stream_set_moved_callback); +MAKE_FUNC(pa_stream_set_underflow_callback); +MAKE_FUNC(pa_stream_new); +MAKE_FUNC(pa_stream_disconnect); +MAKE_FUNC(pa_threaded_mainloop_lock); +MAKE_FUNC(pa_channel_map_init_auto); +MAKE_FUNC(pa_channel_map_parse); +MAKE_FUNC(pa_channel_map_snprint); +MAKE_FUNC(pa_channel_map_equal); +MAKE_FUNC(pa_context_get_server_info); +MAKE_FUNC(pa_context_get_sink_info_by_name); +MAKE_FUNC(pa_context_get_sink_info_list); +MAKE_FUNC(pa_context_get_source_info_list); +MAKE_FUNC(pa_operation_get_state); +MAKE_FUNC(pa_operation_unref); +#if PA_CHECK_VERSION(0,9,15) +MAKE_FUNC(pa_channel_map_superset); +MAKE_FUNC(pa_stream_set_buffer_attr_callback); +#endif +#if PA_CHECK_VERSION(0,9,16) +MAKE_FUNC(pa_stream_begin_write); +#endif +#undef MAKE_FUNC + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +typedef struct { + char *device_name; + + ALCuint samples; + ALCuint frame_size; + + RingBuffer *ring; + + pa_buffer_attr attr; + pa_sample_spec spec; + + pa_threaded_mainloop *loop; + + ALvoid *thread; + volatile ALboolean killNow; + + pa_stream *stream; + pa_context *context; +} pulse_data; + +typedef struct { + char *name; + char *device_name; +} DevMap; + + +static const ALCchar pulse_device[] = "PulseAudio Default"; +static DevMap *allDevNameMap; +static ALuint numDevNames; +static DevMap *allCaptureDevNameMap; +static ALuint numCaptureDevNames; +static pa_context_flags_t pulse_ctx_flags; + + +void *pulse_load(void) //{{{ +{ + if(!pa_handle) + { +#ifdef _WIN32 + pa_handle = LoadLibrary("libpulse-0.dll"); +#define LOAD_FUNC(x) do { \ + p##x = (typeof(p##x))GetProcAddress(pa_handle, #x); \ + if(!(p##x)) { \ + AL_PRINT("Could not load %s from libpulse-0.dll\n", #x); \ + FreeLibrary(pa_handle); \ + pa_handle = NULL; \ + return NULL; \ + } \ +} while(0) +#define LOAD_OPTIONAL_FUNC(x) do { \ + p##x = (typeof(p##x))GetProcAddress(pa_handle, #x); \ +} while(0) + +#elif defined (HAVE_DLFCN_H) + + const char *err; +#if defined(__APPLE__) && defined(__MACH__) + pa_handle = dlopen("libpulse.0.dylib", RTLD_NOW); +#else + pa_handle = dlopen("libpulse.so.0", RTLD_NOW); +#endif + dlerror(); + +#define LOAD_FUNC(x) do { \ + p##x = dlsym(pa_handle, #x); \ + if((err=dlerror()) != NULL) { \ + AL_PRINT("Could not load %s from libpulse: %s\n", #x, err); \ + dlclose(pa_handle); \ + pa_handle = NULL; \ + return NULL; \ + } \ +} while(0) +#define LOAD_OPTIONAL_FUNC(x) do { \ + p##x = dlsym(pa_handle, #x); \ + if((err=dlerror()) != NULL) { \ + p##x = NULL; \ + } \ +} while(0) + +#else + + pa_handle = (void*)0xDEADBEEF; +#define LOAD_FUNC(x) p##x = (x) +#define LOAD_OPTIONAL_FUNC(x) p##x = (x) + +#endif + if(!pa_handle) + return NULL; + +LOAD_FUNC(pa_context_unref); +LOAD_FUNC(pa_sample_spec_valid); +LOAD_FUNC(pa_stream_drop); +LOAD_FUNC(pa_strerror); +LOAD_FUNC(pa_context_get_state); +LOAD_FUNC(pa_stream_get_state); +LOAD_FUNC(pa_threaded_mainloop_signal); +LOAD_FUNC(pa_stream_peek); +LOAD_FUNC(pa_threaded_mainloop_wait); +LOAD_FUNC(pa_threaded_mainloop_unlock); +LOAD_FUNC(pa_threaded_mainloop_in_thread); +LOAD_FUNC(pa_context_new); +LOAD_FUNC(pa_threaded_mainloop_stop); +LOAD_FUNC(pa_context_disconnect); +LOAD_FUNC(pa_threaded_mainloop_start); +LOAD_FUNC(pa_threaded_mainloop_get_api); +LOAD_FUNC(pa_context_set_state_callback); +LOAD_FUNC(pa_stream_write); +LOAD_FUNC(pa_xfree); +LOAD_FUNC(pa_stream_connect_record); +LOAD_FUNC(pa_stream_connect_playback); +LOAD_FUNC(pa_stream_readable_size); +LOAD_FUNC(pa_stream_writable_size); +LOAD_FUNC(pa_stream_cork); +LOAD_FUNC(pa_stream_is_suspended); +LOAD_FUNC(pa_stream_get_device_name); +LOAD_FUNC(pa_path_get_filename); +LOAD_FUNC(pa_get_binary_name); +LOAD_FUNC(pa_threaded_mainloop_free); +LOAD_FUNC(pa_context_errno); +LOAD_FUNC(pa_xmalloc); +LOAD_FUNC(pa_stream_unref); +LOAD_FUNC(pa_threaded_mainloop_accept); +LOAD_FUNC(pa_stream_set_write_callback); +LOAD_FUNC(pa_threaded_mainloop_new); +LOAD_FUNC(pa_context_connect); +LOAD_FUNC(pa_stream_set_buffer_attr); +LOAD_FUNC(pa_stream_get_buffer_attr); +LOAD_FUNC(pa_stream_get_sample_spec); +LOAD_FUNC(pa_stream_get_time); +LOAD_FUNC(pa_stream_set_read_callback); +LOAD_FUNC(pa_stream_set_state_callback); +LOAD_FUNC(pa_stream_set_moved_callback); +LOAD_FUNC(pa_stream_set_underflow_callback); +LOAD_FUNC(pa_stream_new); +LOAD_FUNC(pa_stream_disconnect); +LOAD_FUNC(pa_threaded_mainloop_lock); +LOAD_FUNC(pa_channel_map_init_auto); +LOAD_FUNC(pa_channel_map_parse); +LOAD_FUNC(pa_channel_map_snprint); +LOAD_FUNC(pa_channel_map_equal); +LOAD_FUNC(pa_context_get_server_info); +LOAD_FUNC(pa_context_get_sink_info_by_name); +LOAD_FUNC(pa_context_get_sink_info_list); +LOAD_FUNC(pa_context_get_source_info_list); +LOAD_FUNC(pa_operation_get_state); +LOAD_FUNC(pa_operation_unref); +#if PA_CHECK_VERSION(0,9,15) +LOAD_OPTIONAL_FUNC(pa_channel_map_superset); +LOAD_OPTIONAL_FUNC(pa_stream_set_buffer_attr_callback); +#endif +#if PA_CHECK_VERSION(0,9,16) +LOAD_OPTIONAL_FUNC(pa_stream_begin_write); +#endif + +#undef LOAD_OPTIONAL_FUNC +#undef LOAD_FUNC + } + return pa_handle; +} //}}} + +// PulseAudio Event Callbacks //{{{ +static void context_state_callback(pa_context *context, void *pdata) //{{{ +{ + pa_threaded_mainloop *loop = pdata; + pa_context_state_t state; + + state = ppa_context_get_state(context); + if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) + ppa_threaded_mainloop_signal(loop, 0); +}//}}} + +static void stream_state_callback(pa_stream *stream, void *pdata) //{{{ +{ + pa_threaded_mainloop *loop = pdata; + pa_stream_state_t state; + + state = ppa_stream_get_state(stream); + if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) + ppa_threaded_mainloop_signal(loop, 0); +}//}}} + +static void stream_signal_callback(pa_stream *stream, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + (void)stream; + + ppa_threaded_mainloop_signal(data->loop, 0); +}//}}} + +static void stream_buffer_attr_callback(pa_stream *stream, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + + SuspendContext(NULL); + + data->attr = *(ppa_stream_get_buffer_attr(stream)); + Device->UpdateSize = data->attr.minreq / data->frame_size; + Device->NumUpdates = (data->attr.tlength/data->frame_size) / Device->UpdateSize; + if(Device->NumUpdates <= 1) + { + Device->NumUpdates = 1; + AL_PRINT("PulseAudio returned minreq > tlength/2; expect break up\n"); + } + + ProcessContext(NULL); +}//}}} + +static void stream_device_callback(pa_stream *stream, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + + free(data->device_name); + data->device_name = strdup(ppa_stream_get_device_name(stream)); +}//}}} + +static void context_state_callback2(pa_context *context, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + + if(ppa_context_get_state(context) == PA_CONTEXT_FAILED) + { + AL_PRINT("Received context failure!\n"); + aluHandleDisconnect(Device); + } + ppa_threaded_mainloop_signal(data->loop, 0); +}//}}} + +static void stream_state_callback2(pa_stream *stream, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + + if(ppa_stream_get_state(stream) == PA_STREAM_FAILED) + { + AL_PRINT("Received stream failure!\n"); + aluHandleDisconnect(Device); + } + ppa_threaded_mainloop_signal(data->loop, 0); +}//}}} + +static void stream_success_callback(pa_stream *stream, int success, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + (void)stream; + (void)success; + + ppa_threaded_mainloop_signal(data->loop, 0); +}//}}} + +static void sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *pdata) //{{{ +{ + ALCdevice *device = pdata; + pulse_data *data = device->ExtraData; + char chanmap_str[256] = ""; + const struct { + const char *str; + enum DevFmtChannels chans; + } chanmaps[] = { + { "front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right", + DevFmtX71 }, + { "front-left,front-right,front-center,lfe,rear-center,side-left,side-right", + DevFmtX61 }, + { "front-left,front-right,front-center,lfe,rear-left,rear-right", + DevFmtX51 }, + { "front-left,front-right,rear-left,rear-right", DevFmtQuad }, + { "front-left,front-right", DevFmtStereo }, + { "mono", DevFmtMono }, + { NULL, 0 } + }; + int i; + (void)context; + + if(eol) + { + ppa_threaded_mainloop_signal(data->loop, 0); + return; + } + + for(i = 0;chanmaps[i].str;i++) + { + pa_channel_map map; + if(!ppa_channel_map_parse(&map, chanmaps[i].str)) + continue; + + if(ppa_channel_map_equal(&info->channel_map, &map) +#if PA_CHECK_VERSION(0,9,15) + || (ppa_channel_map_superset && + ppa_channel_map_superset(&info->channel_map, &map)) +#endif + ) + { + device->FmtChans = chanmaps[i].chans; + return; + } + } + + ppa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map); + AL_PRINT("Failed to find format for channel map:\n %s\n", chanmap_str); +}//}}} + +static void sink_device_callback(pa_context *context, const pa_sink_info *info, int eol, void *pdata) //{{{ +{ + pa_threaded_mainloop *loop = pdata; + char str[1024]; + void *temp; + int count; + ALuint i; + + (void)context; + + if(eol) + { + ppa_threaded_mainloop_signal(loop, 0); + return; + } + + count = 0; + do { + if(count == 0) + snprintf(str, sizeof(str), "%s via PulseAudio", info->description); + else + snprintf(str, sizeof(str), "%s #%d via PulseAudio", info->description, count+1); + count++; + + for(i = 0;i < numDevNames;i++) + { + if(strcmp(str, allDevNameMap[i].name) == 0) + break; + } + } while(i != numDevNames); + + temp = realloc(allDevNameMap, (numDevNames+1) * sizeof(*allDevNameMap)); + if(temp) + { + allDevNameMap = temp; + allDevNameMap[numDevNames].name = strdup(str); + allDevNameMap[numDevNames].device_name = strdup(info->name); + numDevNames++; + } +}//}}} + +static void source_device_callback(pa_context *context, const pa_source_info *info, int eol, void *pdata) //{{{ +{ + pa_threaded_mainloop *loop = pdata; + char str[1024]; + void *temp; + int count; + ALuint i; + + (void)context; + + if(eol) + { + ppa_threaded_mainloop_signal(loop, 0); + return; + } + + count = 0; + do { + if(count == 0) + snprintf(str, sizeof(str), "%s via PulseAudio", info->description); + else + snprintf(str, sizeof(str), "%s #%d via PulseAudio", info->description, count+1); + count++; + + for(i = 0;i < numCaptureDevNames;i++) + { + if(strcmp(str, allCaptureDevNameMap[i].name) == 0) + break; + } + } while(i != numCaptureDevNames); + + temp = realloc(allCaptureDevNameMap, (numCaptureDevNames+1) * sizeof(*allCaptureDevNameMap)); + if(temp) + { + allCaptureDevNameMap = temp; + allCaptureDevNameMap[numCaptureDevNames].name = strdup(str); + allCaptureDevNameMap[numCaptureDevNames].device_name = strdup(info->name); + numCaptureDevNames++; + } +}//}}} +//}}} + +// PulseAudio I/O Callbacks //{{{ +static void stream_write_callback(pa_stream *stream, size_t len, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + (void)stream; + (void)len; + + ppa_threaded_mainloop_signal(data->loop, 0); +} //}}} +//}}} + +static ALuint PulseProc(ALvoid *param) +{ + ALCdevice *Device = param; + pulse_data *data = Device->ExtraData; + ssize_t len; + + SetRTPriority(); + + ppa_threaded_mainloop_lock(data->loop); + do { + len = (Device->Connected ? ppa_stream_writable_size(data->stream) : 0); + len -= len%(Device->UpdateSize*data->frame_size); + if(len == 0) + { + ppa_threaded_mainloop_wait(data->loop); + continue; + } + + while(len > 0) + { + size_t newlen = len; + void *buf; + pa_free_cb_t free_func = NULL; + +#if PA_CHECK_VERSION(0,9,16) + if(!ppa_stream_begin_write || + ppa_stream_begin_write(data->stream, &buf, &newlen) < 0) +#endif + { + buf = ppa_xmalloc(newlen); + free_func = ppa_xfree; + } + ppa_threaded_mainloop_unlock(data->loop); + + aluMixData(Device, buf, newlen/data->frame_size); + + ppa_threaded_mainloop_lock(data->loop); + ppa_stream_write(data->stream, buf, newlen, free_func, 0, PA_SEEK_RELATIVE); + len -= newlen; + } + } while(Device->Connected && !data->killNow); + ppa_threaded_mainloop_unlock(data->loop); + + return 0; +} + +static pa_context *connect_context(pa_threaded_mainloop *loop) +{ + const char *name = "OpenAL Soft"; + char path_name[PATH_MAX]; + pa_context_state_t state; + pa_context *context; + int err; + + if(ppa_get_binary_name(path_name, sizeof(path_name))) + name = ppa_path_get_filename(path_name); + + context = ppa_context_new(ppa_threaded_mainloop_get_api(loop), name); + if(!context) + { + AL_PRINT("pa_context_new() failed\n"); + return NULL; + } + + ppa_context_set_state_callback(context, context_state_callback, loop); + + if((err=ppa_context_connect(context, NULL, pulse_ctx_flags, NULL)) >= 0) + { + while((state=ppa_context_get_state(context)) != PA_CONTEXT_READY) + { + if(!PA_CONTEXT_IS_GOOD(state)) + { + err = ppa_context_errno(context); + if(err > 0) err = -err; + break; + } + + ppa_threaded_mainloop_wait(loop); + } + } + ppa_context_set_state_callback(context, NULL, NULL); + + if(err < 0) + { + AL_PRINT("Context did not connect: %s\n", ppa_strerror(err)); + ppa_context_unref(context); + return NULL; + } + + return context; +} + +static pa_stream *connect_playback_stream(ALCdevice *device, + pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, + pa_channel_map *chanmap) +{ + pulse_data *data = device->ExtraData; + pa_stream_state_t state; + pa_stream *stream; + + stream = ppa_stream_new(data->context, "Playback Stream", spec, chanmap); + if(!stream) + { + AL_PRINT("pa_stream_new() failed: %s\n", + ppa_strerror(ppa_context_errno(data->context))); + return NULL; + } + + ppa_stream_set_state_callback(stream, stream_state_callback, data->loop); + + if(ppa_stream_connect_playback(stream, data->device_name, attr, flags, NULL, NULL) < 0) + { + AL_PRINT("Stream did not connect: %s\n", + ppa_strerror(ppa_context_errno(data->context))); + ppa_stream_unref(stream); + return NULL; + } + + while((state=ppa_stream_get_state(stream)) != PA_STREAM_READY) + { + if(!PA_STREAM_IS_GOOD(state)) + { + AL_PRINT("Stream did not get ready: %s\n", + ppa_strerror(ppa_context_errno(data->context))); + ppa_stream_unref(stream); + return NULL; + } + + ppa_threaded_mainloop_wait(data->loop); + } + ppa_stream_set_state_callback(stream, NULL, NULL); + + return stream; +} + +static void probe_devices(ALboolean capture) +{ + pa_threaded_mainloop *loop; + + if(capture == AL_FALSE) + allDevNameMap = malloc(sizeof(DevMap) * 1); + else + allCaptureDevNameMap = malloc(sizeof(DevMap) * 1); + + if((loop=ppa_threaded_mainloop_new()) && + ppa_threaded_mainloop_start(loop) >= 0) + { + pa_context *context; + + ppa_threaded_mainloop_lock(loop); + context = connect_context(loop); + if(context) + { + pa_operation *o; + + if(capture == AL_FALSE) + { + allDevNameMap[0].name = strdup(pulse_device); + allDevNameMap[0].device_name = NULL; + numDevNames = 1; + + o = ppa_context_get_sink_info_list(context, sink_device_callback, loop); + } + else + { + allCaptureDevNameMap[0].name = strdup(pulse_device); + allCaptureDevNameMap[0].device_name = NULL; + numCaptureDevNames = 1; + + o = ppa_context_get_source_info_list(context, source_device_callback, loop); + } + while(ppa_operation_get_state(o) == PA_OPERATION_RUNNING) + ppa_threaded_mainloop_wait(loop); + ppa_operation_unref(o); + + ppa_context_disconnect(context); + ppa_context_unref(context); + } + ppa_threaded_mainloop_unlock(loop); + ppa_threaded_mainloop_stop(loop); + } + if(loop) + ppa_threaded_mainloop_free(loop); +} + + +static ALCboolean pulse_open(ALCdevice *device, const ALCchar *device_name) //{{{ +{ + pulse_data *data = ppa_xmalloc(sizeof(pulse_data)); + memset(data, 0, sizeof(*data)); + + if(!(data->loop = ppa_threaded_mainloop_new())) + { + AL_PRINT("pa_threaded_mainloop_new() failed!\n"); + goto out; + } + if(ppa_threaded_mainloop_start(data->loop) < 0) + { + AL_PRINT("pa_threaded_mainloop_start() failed\n"); + goto out; + } + + ppa_threaded_mainloop_lock(data->loop); + device->ExtraData = data; + + data->context = connect_context(data->loop); + if(!data->context) + { + ppa_threaded_mainloop_unlock(data->loop); + goto out; + } + ppa_context_set_state_callback(data->context, context_state_callback2, device); + + device->szDeviceName = strdup(device_name); + + ppa_threaded_mainloop_unlock(data->loop); + return ALC_TRUE; + +out: + if(data->loop) + { + ppa_threaded_mainloop_stop(data->loop); + ppa_threaded_mainloop_free(data->loop); + } + + device->ExtraData = NULL; + ppa_xfree(data); + return ALC_FALSE; +} //}}} + +static void pulse_close(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + + ppa_threaded_mainloop_lock(data->loop); + + if(data->stream) + { + ppa_stream_disconnect(data->stream); + ppa_stream_unref(data->stream); + } + + ppa_context_disconnect(data->context); + ppa_context_unref(data->context); + + ppa_threaded_mainloop_unlock(data->loop); + + ppa_threaded_mainloop_stop(data->loop); + ppa_threaded_mainloop_free(data->loop); + + DestroyRingBuffer(data->ring); + free(data->device_name); + + device->ExtraData = NULL; + ppa_xfree(data); +} //}}} +//}}} + +// OpenAL {{{ +static ALCboolean pulse_open_playback(ALCdevice *device, const ALCchar *device_name) //{{{ +{ + char *pulse_name = NULL; + pa_sample_spec spec; + pulse_data *data; + + if(!pulse_load()) + return ALC_FALSE; + + if(!allDevNameMap) + probe_devices(AL_FALSE); + + if(!device_name && numDevNames > 0) + device_name = allDevNameMap[0].name; + else + { + ALuint i; + + for(i = 0;i < numDevNames;i++) + { + if(strcmp(device_name, allDevNameMap[i].name) == 0) + { + pulse_name = allDevNameMap[i].device_name; + break; + } + } + if(i == numDevNames) + return ALC_FALSE; + } + + if(pulse_open(device, device_name) == ALC_FALSE) + return ALC_FALSE; + + data = device->ExtraData; + + ppa_threaded_mainloop_lock(data->loop); + + spec.format = PA_SAMPLE_S16NE; + spec.rate = 44100; + spec.channels = 2; + + data->device_name = pulse_name; + pa_stream *stream = connect_playback_stream(device, 0, NULL, &spec, NULL); + if(!stream) + { + ppa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + if(ppa_stream_is_suspended(stream)) + { + AL_PRINT("Device is suspended\n"); + ppa_stream_disconnect(stream); + ppa_stream_unref(stream); + ppa_threaded_mainloop_unlock(data->loop); + goto fail; + } + data->device_name = strdup(ppa_stream_get_device_name(stream)); + + ppa_stream_disconnect(stream); + ppa_stream_unref(stream); + + ppa_threaded_mainloop_unlock(data->loop); + + return ALC_TRUE; + +fail: + pulse_close(device); + return ALC_FALSE; +} //}}} + +static void pulse_close_playback(ALCdevice *device) //{{{ +{ + pulse_close(device); +} //}}} + +static ALCboolean pulse_reset_playback(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + pa_stream_flags_t flags = 0; + pa_channel_map chanmap; + + ppa_threaded_mainloop_lock(data->loop); + + if(!ConfigValueExists(NULL, "format")) + { + pa_operation *o; + o = ppa_context_get_sink_info_by_name(data->context, data->device_name, sink_info_callback, device); + while(ppa_operation_get_state(o) == PA_OPERATION_RUNNING) + ppa_threaded_mainloop_wait(data->loop); + ppa_operation_unref(o); + } + if(!ConfigValueExists(NULL, "frequency")) + flags |= PA_STREAM_FIX_RATE; + + data->frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + data->attr.prebuf = -1; + data->attr.fragsize = -1; + data->attr.minreq = device->UpdateSize * data->frame_size; + data->attr.tlength = data->attr.minreq * device->NumUpdates; + if(data->attr.tlength < data->attr.minreq*2) + data->attr.tlength = data->attr.minreq*2; + data->attr.maxlength = data->attr.tlength; + flags |= PA_STREAM_EARLY_REQUESTS; + flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE; + + switch(device->FmtType) + { + case DevFmtByte: + device->FmtType = DevFmtUByte; + /* fall-through */ + case DevFmtUByte: + data->spec.format = PA_SAMPLE_U8; + break; + case DevFmtUShort: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + data->spec.format = PA_SAMPLE_S16NE; + break; + case DevFmtFloat: + data->spec.format = PA_SAMPLE_FLOAT32NE; + break; + } + data->spec.rate = device->Frequency; + data->spec.channels = ChannelsFromDevFmt(device->FmtChans); + + if(ppa_sample_spec_valid(&data->spec) == 0) + { + AL_PRINT("Invalid sample format\n"); + ppa_threaded_mainloop_unlock(data->loop); + return ALC_FALSE; + } + + if(!ppa_channel_map_init_auto(&chanmap, data->spec.channels, PA_CHANNEL_MAP_WAVEEX)) + { + AL_PRINT("Couldn't build map for channel count (%d)!\n", data->spec.channels); + ppa_threaded_mainloop_unlock(data->loop); + return ALC_FALSE; + } + SetDefaultWFXChannelOrder(device); + + data->stream = connect_playback_stream(device, flags, &data->attr, &data->spec, &chanmap); + if(!data->stream) + { + ppa_threaded_mainloop_unlock(data->loop); + return ALC_FALSE; + } + + ppa_stream_set_state_callback(data->stream, stream_state_callback2, device); + + data->spec = *(ppa_stream_get_sample_spec(data->stream)); + if(device->Frequency != data->spec.rate) + { + pa_operation *o; + + /* Server updated our playback rate, so modify the buffer attribs + * accordingly. */ + data->attr.minreq = (ALuint64)(data->attr.minreq/data->frame_size) * + data->spec.rate / device->Frequency * data->frame_size; + data->attr.tlength = data->attr.minreq * device->NumUpdates; + data->attr.maxlength = data->attr.tlength; + + o = ppa_stream_set_buffer_attr(data->stream, &data->attr, + stream_success_callback, device); + while(ppa_operation_get_state(o) == PA_OPERATION_RUNNING) + ppa_threaded_mainloop_wait(data->loop); + ppa_operation_unref(o); + + device->Frequency = data->spec.rate; + } + + stream_buffer_attr_callback(data->stream, device); +#if PA_CHECK_VERSION(0,9,15) + if(ppa_stream_set_buffer_attr_callback) + ppa_stream_set_buffer_attr_callback(data->stream, stream_buffer_attr_callback, device); +#endif + ppa_stream_set_moved_callback(data->stream, stream_device_callback, device); + ppa_stream_set_write_callback(data->stream, stream_write_callback, device); + ppa_stream_set_underflow_callback(data->stream, stream_signal_callback, device); + + data->thread = StartThread(PulseProc, device); + if(!data->thread) + { +#if PA_CHECK_VERSION(0,9,15) + if(ppa_stream_set_buffer_attr_callback) + ppa_stream_set_buffer_attr_callback(data->stream, NULL, NULL); +#endif + ppa_stream_set_moved_callback(data->stream, NULL, NULL); + ppa_stream_set_write_callback(data->stream, NULL, NULL); + ppa_stream_set_underflow_callback(data->stream, NULL, NULL); + ppa_stream_disconnect(data->stream); + ppa_stream_unref(data->stream); + data->stream = NULL; + + ppa_threaded_mainloop_unlock(data->loop); + return ALC_FALSE; + } + + ppa_threaded_mainloop_unlock(data->loop); + return ALC_TRUE; +} //}}} + +static void pulse_stop_playback(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + + if(!data->stream) + return; + + data->killNow = AL_TRUE; + if(data->thread) + { + ppa_threaded_mainloop_signal(data->loop, 0); + StopThread(data->thread); + data->thread = NULL; + } + data->killNow = AL_FALSE; + + ppa_threaded_mainloop_lock(data->loop); + +#if PA_CHECK_VERSION(0,9,15) + if(ppa_stream_set_buffer_attr_callback) + ppa_stream_set_buffer_attr_callback(data->stream, NULL, NULL); +#endif + ppa_stream_set_moved_callback(data->stream, NULL, NULL); + ppa_stream_set_write_callback(data->stream, NULL, NULL); + ppa_stream_set_underflow_callback(data->stream, NULL, NULL); + ppa_stream_disconnect(data->stream); + ppa_stream_unref(data->stream); + data->stream = NULL; + + ppa_threaded_mainloop_unlock(data->loop); +} //}}} + + +static ALCboolean pulse_open_capture(ALCdevice *device, const ALCchar *device_name) //{{{ +{ + char *pulse_name = NULL; + pulse_data *data; + pa_stream_flags_t flags = 0; + pa_stream_state_t state; + pa_channel_map chanmap; + + if(!pulse_load()) + return ALC_FALSE; + + if(!allCaptureDevNameMap) + probe_devices(AL_TRUE); + + if(!device_name && numCaptureDevNames > 0) + device_name = allCaptureDevNameMap[0].name; + else + { + ALuint i; + + for(i = 0;i < numCaptureDevNames;i++) + { + if(strcmp(device_name, allCaptureDevNameMap[i].name) == 0) + { + pulse_name = allCaptureDevNameMap[i].device_name; + break; + } + } + if(i == numCaptureDevNames) + return ALC_FALSE; + } + + if(pulse_open(device, device_name) == ALC_FALSE) + return ALC_FALSE; + + data = device->ExtraData; + ppa_threaded_mainloop_lock(data->loop); + + data->samples = device->UpdateSize * device->NumUpdates; + data->frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + if(data->samples < 100 * device->Frequency / 1000) + data->samples = 100 * device->Frequency / 1000; + + if(!(data->ring = CreateRingBuffer(data->frame_size, data->samples))) + { + ppa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + data->attr.minreq = -1; + data->attr.prebuf = -1; + data->attr.maxlength = data->samples * data->frame_size; + data->attr.tlength = -1; + data->attr.fragsize = min(data->samples, 50 * device->Frequency / 1000) * + data->frame_size; + + data->spec.rate = device->Frequency; + data->spec.channels = ChannelsFromDevFmt(device->FmtChans); + + switch(device->FmtType) + { + case DevFmtUByte: + data->spec.format = PA_SAMPLE_U8; + break; + case DevFmtShort: + data->spec.format = PA_SAMPLE_S16NE; + break; + case DevFmtFloat: + data->spec.format = PA_SAMPLE_FLOAT32NE; + break; + case DevFmtByte: + case DevFmtUShort: + AL_PRINT("Capture format type %#x capture not supported on PulseAudio\n", device->FmtType); + ppa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + if(ppa_sample_spec_valid(&data->spec) == 0) + { + AL_PRINT("Invalid sample format\n"); + ppa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + if(!ppa_channel_map_init_auto(&chanmap, data->spec.channels, PA_CHANNEL_MAP_WAVEEX)) + { + AL_PRINT("Couldn't build map for channel count (%d)!\n", data->spec.channels); + ppa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + data->stream = ppa_stream_new(data->context, "Capture Stream", &data->spec, &chanmap); + if(!data->stream) + { + AL_PRINT("pa_stream_new() failed: %s\n", + ppa_strerror(ppa_context_errno(data->context))); + + ppa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + ppa_stream_set_state_callback(data->stream, stream_state_callback, data->loop); + + flags |= PA_STREAM_START_CORKED|PA_STREAM_ADJUST_LATENCY; + if(ppa_stream_connect_record(data->stream, pulse_name, &data->attr, flags) < 0) + { + AL_PRINT("Stream did not connect: %s\n", + ppa_strerror(ppa_context_errno(data->context))); + + ppa_stream_unref(data->stream); + data->stream = NULL; + + ppa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + while((state=ppa_stream_get_state(data->stream)) != PA_STREAM_READY) + { + if(!PA_STREAM_IS_GOOD(state)) + { + AL_PRINT("Stream did not get ready: %s\n", + ppa_strerror(ppa_context_errno(data->context))); + + ppa_stream_unref(data->stream); + data->stream = NULL; + + ppa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + ppa_threaded_mainloop_wait(data->loop); + } + ppa_stream_set_state_callback(data->stream, stream_state_callback2, device); + + ppa_threaded_mainloop_unlock(data->loop); + return ALC_TRUE; + +fail: + pulse_close(device); + return ALC_FALSE; +} //}}} + +static void pulse_close_capture(ALCdevice *device) //{{{ +{ + pulse_close(device); +} //}}} + +static void pulse_start_capture(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + pa_operation *o; + + ppa_threaded_mainloop_lock(data->loop); + o = ppa_stream_cork(data->stream, 0, stream_success_callback, device); + while(ppa_operation_get_state(o) == PA_OPERATION_RUNNING) + ppa_threaded_mainloop_wait(data->loop); + ppa_operation_unref(o); + ppa_threaded_mainloop_unlock(data->loop); +} //}}} + +static void pulse_stop_capture(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + pa_operation *o; + + ppa_threaded_mainloop_lock(data->loop); + o = ppa_stream_cork(data->stream, 1, stream_success_callback, device); + while(ppa_operation_get_state(o) == PA_OPERATION_RUNNING) + ppa_threaded_mainloop_wait(data->loop); + ppa_operation_unref(o); + ppa_threaded_mainloop_unlock(data->loop); +} //}}} + +static ALCuint pulse_available_samples(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + size_t samples; + + ppa_threaded_mainloop_lock(data->loop); + /* Capture is done in fragment-sized chunks, so we loop until we get all + * that's available */ + samples = (device->Connected ? ppa_stream_readable_size(data->stream) : 0); + while(samples > 0) + { + const void *buf; + size_t length; + + if(ppa_stream_peek(data->stream, &buf, &length) < 0) + { + AL_PRINT("pa_stream_peek() failed: %s\n", + ppa_strerror(ppa_context_errno(data->context))); + break; + } + + WriteRingBuffer(data->ring, buf, length/data->frame_size); + samples -= length; + + ppa_stream_drop(data->stream); + } + ppa_threaded_mainloop_unlock(data->loop); + + return RingBufferSize(data->ring); +} //}}} + +static void pulse_capture_samples(ALCdevice *device, ALCvoid *buffer, ALCuint samples) //{{{ +{ + pulse_data *data = device->ExtraData; + + if(pulse_available_samples(device) >= samples) + ReadRingBuffer(data->ring, buffer, samples); + else + alcSetError(device, ALC_INVALID_VALUE); +} //}}} + + +BackendFuncs pulse_funcs = { //{{{ + pulse_open_playback, + pulse_close_playback, + pulse_reset_playback, + pulse_stop_playback, + pulse_open_capture, + pulse_close_capture, + pulse_start_capture, + pulse_stop_capture, + pulse_capture_samples, + pulse_available_samples +}; //}}} + +void alc_pulse_init(BackendFuncs *func_list) //{{{ +{ + *func_list = pulse_funcs; + + pulse_ctx_flags = 0; + if(!GetConfigValueBool("pulse", "spawn-server", 0)) + pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; +} //}}} + +void alc_pulse_deinit(void) //{{{ +{ + ALuint i; + + for(i = 0;i < numDevNames;++i) + { + free(allDevNameMap[i].name); + free(allDevNameMap[i].device_name); + } + free(allDevNameMap); + allDevNameMap = NULL; + numDevNames = 0; + + for(i = 0;i < numCaptureDevNames;++i) + { + free(allCaptureDevNameMap[i].name); + free(allCaptureDevNameMap[i].device_name); + } + free(allCaptureDevNameMap); + allCaptureDevNameMap = NULL; + numCaptureDevNames = 0; + + if(pa_handle) + { +#ifdef _WIN32 + FreeLibrary(pa_handle); +#elif defined (HAVE_DLFCN_H) + dlclose(pa_handle); +#endif + pa_handle = NULL; + } +} //}}} + +void alc_pulse_probe(int type) //{{{ +{ + if(!pulse_load()) return; + + if(type == DEVICE_PROBE) + { + pa_threaded_mainloop *loop; + + if((loop=ppa_threaded_mainloop_new()) && + ppa_threaded_mainloop_start(loop) >= 0) + { + pa_context *context; + + ppa_threaded_mainloop_lock(loop); + context = connect_context(loop); + if(context) + { + AppendDeviceList(pulse_device); + + ppa_context_disconnect(context); + ppa_context_unref(context); + } + ppa_threaded_mainloop_unlock(loop); + ppa_threaded_mainloop_stop(loop); + } + if(loop) + ppa_threaded_mainloop_free(loop); + } + else if(type == ALL_DEVICE_PROBE) + { + ALuint i; + + for(i = 0;i < numDevNames;++i) + { + free(allDevNameMap[i].name); + free(allDevNameMap[i].device_name); + } + free(allDevNameMap); + allDevNameMap = NULL; + numDevNames = 0; + + probe_devices(AL_FALSE); + + for(i = 0;i < numDevNames;i++) + AppendAllDeviceList(allDevNameMap[i].name); + } + else if(type == CAPTURE_DEVICE_PROBE) + { + ALuint i; + + for(i = 0;i < numCaptureDevNames;++i) + { + free(allCaptureDevNameMap[i].name); + free(allCaptureDevNameMap[i].device_name); + } + free(allCaptureDevNameMap); + allCaptureDevNameMap = NULL; + numCaptureDevNames = 0; + + probe_devices(AL_TRUE); + + for(i = 0;i < numCaptureDevNames;i++) + AppendCaptureDeviceList(allCaptureDevNameMap[i].name); + } +} //}}} +//}}} diff --git a/jni/OpenAL/Alc/solaris.c b/jni/OpenAL/Alc/solaris.c new file mode 100644 index 0000000..18c7334 --- /dev/null +++ b/jni/OpenAL/Alc/solaris.c @@ -0,0 +1,304 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#include + + +static const ALCchar solaris_device[] = "Solaris Default"; + +typedef struct { + int fd; + volatile int killNow; + ALvoid *thread; + + ALubyte *mix_data; + int data_size; +} solaris_data; + + +static ALuint SolarisProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + solaris_data *data = (solaris_data*)pDevice->ExtraData; + ALint frameSize; + int wrote; + + SetRTPriority(); + + frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + while(!data->killNow && pDevice->Connected) + { + ALint len = data->data_size; + ALubyte *WritePtr = data->mix_data; + + aluMixData(pDevice, WritePtr, len/frameSize); + while(len > 0 && !data->killNow) + { + wrote = write(data->fd, WritePtr, len); + if(wrote < 0) + { + if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) + { + AL_PRINT("write failed: %s\n", strerror(errno)); + aluHandleDisconnect(pDevice); + break; + } + + Sleep(1); + continue; + } + + len -= wrote; + WritePtr += wrote; + } + } + + return 0; +} + + +static ALCboolean solaris_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + char driver[64]; + solaris_data *data; + + strncpy(driver, GetConfigValue("solaris", "device", "/dev/audio"), sizeof(driver)-1); + driver[sizeof(driver)-1] = 0; + + if(!deviceName) + deviceName = solaris_device; + else if(strcmp(deviceName, solaris_device) != 0) + return ALC_FALSE; + + data = (solaris_data*)calloc(1, sizeof(solaris_data)); + data->killNow = 0; + + data->fd = open(driver, O_WRONLY); + if(data->fd == -1) + { + free(data); + AL_PRINT("Could not open %s: %s\n", driver, strerror(errno)); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void solaris_close_playback(ALCdevice *device) +{ + solaris_data *data = (solaris_data*)device->ExtraData; + + close(data->fd); + free(data); + device->ExtraData = NULL; +} + +static ALCboolean solaris_reset_playback(ALCdevice *device) +{ + solaris_data *data = (solaris_data*)device->ExtraData; + audio_info_t info; + ALuint frameSize; + int numChannels; + + AUDIO_INITINFO(&info); + + info.play.sample_rate = device->Frequency; + + if(device->FmtChans != DevFmtMono) + device->FmtChans = DevFmtStereo; + numChannels = ChannelsFromDevFmt(device->FmtChans); + 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 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(data->fd, AUDIO_SETINFO, &info) < 0) + { + AL_PRINT("ioctl failed: %s\n", strerror(errno)); + return ALC_FALSE; + } + + if(ChannelsFromDevFmt(device->FmtChans) != info.play.channels) + { + AL_PRINT("Could not set %d channels, got %d instead\n", ChannelsFromDevFmt(device->FmtChans), info.play.channels); + return ALC_FALSE; + } + + if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && + device->FmtType == DevFmtByte) || + (info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && + device->FmtType == DevFmtUByte) || + (info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && + device->FmtType == DevFmtShort))) + { + AL_PRINT("Could not set %#x sample type, got %d (%#x)\n", + 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; + + data->data_size = device->UpdateSize * frameSize; + data->mix_data = calloc(1, data->data_size); + + SetDefaultChannelOrder(device); + + data->thread = StartThread(SolarisProc, device); + if(data->thread == NULL) + { + free(data->mix_data); + data->mix_data = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void solaris_stop_playback(ALCdevice *device) +{ + solaris_data *data = (solaris_data*)device->ExtraData; + + if(!data->thread) + return; + + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + + data->killNow = 0; + if(ioctl(data->fd, AUDIO_DRAIN) < 0) + AL_PRINT("Error draining device: %s\n", strerror(errno)); + + free(data->mix_data); + data->mix_data = NULL; +} + + +static ALCboolean solaris_open_capture(ALCdevice *device, const ALCchar *deviceName) +{ + (void)device; + (void)deviceName; + return ALC_FALSE; +} + +static void solaris_close_capture(ALCdevice *device) +{ + (void)device; +} + +static void solaris_start_capture(ALCdevice *pDevice) +{ + (void)pDevice; +} + +static void solaris_stop_capture(ALCdevice *pDevice) +{ + (void)pDevice; +} + +static void solaris_capture_samples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples) +{ + (void)pDevice; + (void)pBuffer; + (void)lSamples; +} + +static ALCuint solaris_available_samples(ALCdevice *pDevice) +{ + (void)pDevice; + return 0; +} + + +BackendFuncs solaris_funcs = { + solaris_open_playback, + solaris_close_playback, + solaris_reset_playback, + solaris_stop_playback, + solaris_open_capture, + solaris_close_capture, + solaris_start_capture, + solaris_stop_capture, + solaris_capture_samples, + solaris_available_samples +}; + +void alc_solaris_init(BackendFuncs *func_list) +{ + *func_list = solaris_funcs; +} + +void alc_solaris_deinit(void) +{ +} + +void alc_solaris_probe(int type) +{ +#ifdef HAVE_STAT + struct stat buf; + if(stat(GetConfigValue("solaris", "device", "/dev/audio"), &buf) != 0) + return; +#endif + + if(type == DEVICE_PROBE) + AppendDeviceList(solaris_device); + else if(type == ALL_DEVICE_PROBE) + AppendAllDeviceList(solaris_device); +} diff --git a/jni/OpenAL/Alc/wave.c b/jni/OpenAL/Alc/wave.c new file mode 100644 index 0000000..6ba662c --- /dev/null +++ b/jni/OpenAL/Alc/wave.c @@ -0,0 +1,355 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + + +typedef struct { + FILE *f; + long DataStart; + + ALvoid *buffer; + ALuint size; + + volatile int killNow; + ALvoid *thread; +} wave_data; + + +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 ALuint channel_masks[] = { + 0, /* invalid */ + 0x4, /* Mono */ + 0x1 | 0x2, /* Stereo */ + 0, /* 3 channel */ + 0x1 | 0x2 | 0x10 | 0x20, /* Quad */ + 0, /* 5 channel */ + 0x1 | 0x2 | 0x4 | 0x8 | 0x10 | 0x20, /* 5.1 */ + 0x1 | 0x2 | 0x4 | 0x8 | 0x100 | 0x200 | 0x400, /* 6.1 */ + 0x1 | 0x2 | 0x4 | 0x8 | 0x10 | 0x20 | 0x200 | 0x400, /* 7.1 */ +}; + + +static void fwrite16le(ALushort val, FILE *f) +{ + fputc(val&0xff, f); + fputc((val>>8)&0xff, f); +} + +static void fwrite32le(ALuint val, FILE *f) +{ + fputc(val&0xff, f); + fputc((val>>8)&0xff, f); + fputc((val>>16)&0xff, f); + fputc((val>>24)&0xff, f); +} + + +static ALuint WaveProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + wave_data *data = (wave_data*)pDevice->ExtraData; + ALuint frameSize; + ALuint now, start; + ALuint64 avail, done; + size_t fs; + union { + short s; + char b[sizeof(short)]; + } uSB; + const ALuint restTime = (ALuint64)pDevice->UpdateSize * 1000 / + pDevice->Frequency / 2; + + uSB.s = 1; + frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + done = 0; + start = timeGetTime(); + while(!data->killNow && pDevice->Connected) + { + now = timeGetTime(); + + avail = (ALuint64)(now-start) * pDevice->Frequency / 1000; + if(avail < done) + { + /* Timer wrapped. Add the remainder of the cycle to the available + * count and reset the number of samples done */ + avail += (ALuint64)0xFFFFFFFFu*pDevice->Frequency/1000 - done; + done = 0; + } + if(avail-done < pDevice->UpdateSize) + { + Sleep(restTime); + continue; + } + + while(avail-done >= pDevice->UpdateSize) + { + aluMixData(pDevice, data->buffer, pDevice->UpdateSize); + done += pDevice->UpdateSize; + + if(uSB.b[0] != 1) + { + ALuint bytesize = BytesFromDevFmt(pDevice->FmtType); + ALubyte *bytes = data->buffer; + ALuint i; + + if(bytesize == 1) + { + for(i = 0;i < data->size;i++) + fputc(bytes[i], data->f); + } + else if(bytesize == 2) + { + for(i = 0;i < data->size;i++) + fputc(bytes[i^1], data->f); + } + else if(bytesize == 4) + { + for(i = 0;i < data->size;i++) + fputc(bytes[i^3], data->f); + } + } + else + fs = fwrite(data->buffer, frameSize, pDevice->UpdateSize, + data->f); + if(ferror(data->f)) + { + AL_PRINT("Error writing to file\n"); + aluHandleDisconnect(pDevice); + break; + } + } + } + + return 0; +} + +static ALCboolean wave_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + wave_data *data; + const char *fname; + + fname = GetConfigValue("wave", "file", ""); + if(!fname[0]) + return ALC_FALSE; + + if(!deviceName) + deviceName = waveDevice; + else if(strcmp(deviceName, waveDevice) != 0) + return ALC_FALSE; + + data = (wave_data*)calloc(1, sizeof(wave_data)); + + data->f = fopen(fname, "wb"); + if(!data->f) + { + free(data); + AL_PRINT("Could not open file '%s': %s\n", fname, strerror(errno)); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void wave_close_playback(ALCdevice *device) +{ + wave_data *data = (wave_data*)device->ExtraData; + + fclose(data->f); + free(data); + device->ExtraData = NULL; +} + +static ALCboolean wave_reset_playback(ALCdevice *device) +{ + wave_data *data = (wave_data*)device->ExtraData; + ALuint channels=0, bits=0; + size_t val; + + fseek(data->f, 0, SEEK_SET); + clearerr(data->f); + + switch(device->FmtType) + { + case DevFmtByte: + device->FmtType = DevFmtUByte; + break; + case DevFmtUShort: + device->FmtType = DevFmtShort; + break; + case DevFmtUByte: + case DevFmtShort: + case DevFmtFloat: + break; + } + bits = BytesFromDevFmt(device->FmtType) * 8; + channels = ChannelsFromDevFmt(device->FmtChans); + + fprintf(data->f, "RIFF"); + fwrite32le(0xFFFFFFFF, data->f); // 'RIFF' header len; filled in at close + + fprintf(data->f, "WAVE"); + + fprintf(data->f, "fmt "); + fwrite32le(40, data->f); // 'fmt ' header len; 40 bytes for EXTENSIBLE + + // 16-bit val, format type id (extensible: 0xFFFE) + fwrite16le(0xFFFE, data->f); + // 16-bit val, channel count + fwrite16le(channels, data->f); + // 32-bit val, frequency + fwrite32le(device->Frequency, data->f); + // 32-bit val, bytes per second + fwrite32le(device->Frequency * channels * bits / 8, data->f); + // 16-bit val, frame size + fwrite16le(channels * bits / 8, data->f); + // 16-bit val, bits per sample + fwrite16le(bits, data->f); + // 16-bit val, extra byte count + fwrite16le(22, data->f); + // 16-bit val, valid bits per sample + fwrite16le(bits, data->f); + // 32-bit val, channel mask + fwrite32le(channel_masks[channels], data->f); + // 16 byte GUID, sub-type format + val = fwrite(((bits==32) ? SUBTYPE_FLOAT : SUBTYPE_PCM), 1, 16, data->f); + + fprintf(data->f, "data"); + fwrite32le(0xFFFFFFFF, data->f); // 'data' header len; filled in at close + + if(ferror(data->f)) + { + AL_PRINT("Error writing header: %s\n", strerror(errno)); + return ALC_FALSE; + } + + data->DataStart = ftell(data->f); + + data->size = device->UpdateSize * channels * bits / 8; + data->buffer = malloc(data->size); + if(!data->buffer) + { + AL_PRINT("buffer malloc failed\n"); + return ALC_FALSE; + } + + SetDefaultWFXChannelOrder(device); + + data->thread = StartThread(WaveProc, device); + if(data->thread == NULL) + { + free(data->buffer); + data->buffer = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void wave_stop_playback(ALCdevice *device) +{ + wave_data *data = (wave_data*)device->ExtraData; + ALuint dataLen; + long size; + + if(!data->thread) + return; + + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + + data->killNow = 0; + + free(data->buffer); + data->buffer = NULL; + + size = ftell(data->f); + if(size > 0) + { + dataLen = size - data->DataStart; + if(fseek(data->f, data->DataStart-4, SEEK_SET) == 0) + fwrite32le(dataLen, data->f); // 'data' header len + if(fseek(data->f, 4, SEEK_SET) == 0) + fwrite32le(size-8, data->f); // 'WAVE' header len + } +} + + +static ALCboolean wave_open_capture(ALCdevice *pDevice, const ALCchar *deviceName) +{ + (void)pDevice; + (void)deviceName; + return ALC_FALSE; +} + + +BackendFuncs wave_funcs = { + wave_open_playback, + wave_close_playback, + wave_reset_playback, + wave_stop_playback, + wave_open_capture, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +void alc_wave_init(BackendFuncs *func_list) +{ + *func_list = wave_funcs; +} + +void alc_wave_deinit(void) +{ +} + +void alc_wave_probe(int type) +{ + if(!ConfigValueExists("wave", "file")) + return; + + if(type == DEVICE_PROBE) + AppendDeviceList(waveDevice); + else if(type == ALL_DEVICE_PROBE) + AppendAllDeviceList(waveDevice); +} diff --git a/jni/OpenAL/Alc/winmm.c b/jni/OpenAL/Alc/winmm.c new file mode 100644 index 0000000..10d0c28 --- /dev/null +++ b/jni/OpenAL/Alc/winmm.c @@ -0,0 +1,784 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#define _WIN32_WINNT 0x0500 +#include +#include +#include + +#include +#include + +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + + +typedef struct { + // MMSYSTEM Device + volatile ALboolean bWaveShutdown; + HANDLE hWaveHdrEvent; + HANDLE hWaveThreadEvent; + HANDLE hWaveThread; + DWORD ulWaveThreadID; + LONG lWaveBuffersCommitted; + WAVEHDR WaveBuffer[4]; + + union { + HWAVEIN In; + HWAVEOUT Out; + } hWaveHandle; + + ALsizei Frequency; + + RingBuffer *pRing; +} WinMMData; + + +static const ALCchar woDefault[] = "WaveOut Default"; + +static ALCchar **PlaybackDeviceList; +static ALuint NumPlaybackDevices; +static ALCchar **CaptureDeviceList; +static ALuint NumCaptureDevices; + + +static void ProbePlaybackDevices(void) +{ + ALuint i; + + for(i = 0;i < NumPlaybackDevices;i++) + free(PlaybackDeviceList[i]); + + NumPlaybackDevices = waveOutGetNumDevs(); + PlaybackDeviceList = realloc(PlaybackDeviceList, sizeof(ALCchar*) * NumPlaybackDevices); + for(i = 0;i < NumPlaybackDevices;i++) + { + WAVEOUTCAPS WaveCaps; + + PlaybackDeviceList[i] = NULL; + if(waveOutGetDevCaps(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) + { + char name[1024]; + ALuint count, j; + + count = 0; + do { + if(count == 0) + snprintf(name, sizeof(name), "%s via WaveOut", WaveCaps.szPname); + else + snprintf(name, sizeof(name), "%s #%d via WaveOut", WaveCaps.szPname, count+1); + count++; + + for(j = 0;j < i;j++) + { + if(strcmp(name, PlaybackDeviceList[j]) == 0) + break; + } + } while(j != i); + + PlaybackDeviceList[i] = strdup(name); + } + } +} + +static void ProbeCaptureDevices(void) +{ + ALuint i; + + for(i = 0;i < NumCaptureDevices;i++) + free(CaptureDeviceList[i]); + + NumCaptureDevices = waveInGetNumDevs(); + CaptureDeviceList = realloc(CaptureDeviceList, sizeof(ALCchar*) * NumCaptureDevices); + for(i = 0;i < NumCaptureDevices;i++) + { + WAVEINCAPS WaveInCaps; + + CaptureDeviceList[i] = NULL; + if(waveInGetDevCaps(i, &WaveInCaps, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR) + { + char name[1024]; + ALuint count, j; + + count = 0; + do { + if(count == 0) + snprintf(name, sizeof(name), "%s via WaveIn", WaveInCaps.szPname); + else + snprintf(name, sizeof(name), "%s #%d via WaveIn", WaveInCaps.szPname, count+1); + count++; + + for(j = 0;j < i;j++) + { + if(strcmp(name, CaptureDeviceList[j]) == 0) + break; + } + } while(j != i); + + CaptureDeviceList[i] = strdup(name); + } + } +} + + +/* + WaveOutProc + + Posts a message to 'PlaybackThreadProc' everytime a WaveOut Buffer is completed and + returns to the application (for more data) +*/ +static void CALLBACK WaveOutProc(HWAVEOUT hDevice,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2) +{ + ALCdevice *pDevice = (ALCdevice*)dwInstance; + WinMMData *pData = pDevice->ExtraData; + + (void)hDevice; + (void)dwParam2; + + if(uMsg != WOM_DONE) + return; + + // Decrement number of buffers in use + InterlockedDecrement(&pData->lWaveBuffersCommitted); + + if(pData->bWaveShutdown == AL_FALSE) + { + // Notify Wave Processor Thread that a Wave Header has returned + PostThreadMessage(pData->ulWaveThreadID, uMsg, 0, dwParam1); + } + else + { + if(pData->lWaveBuffersCommitted == 0) + { + // Signal Wave Buffers Returned event + if(pData->hWaveHdrEvent) + SetEvent(pData->hWaveHdrEvent); + + // Post 'Quit' Message to WaveOut Processor Thread + PostThreadMessage(pData->ulWaveThreadID, WM_QUIT, 0, 0); + } + } +} + +/* + PlaybackThreadProc + + Used by "MMSYSTEM" Device. Called when a WaveOut buffer has used up its + audio data. +*/ +static DWORD WINAPI PlaybackThreadProc(LPVOID lpParameter) +{ + ALCdevice *pDevice = (ALCdevice*)lpParameter; + WinMMData *pData = pDevice->ExtraData; + LPWAVEHDR pWaveHdr; + ALuint FrameSize; + MSG msg; + + FrameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + while(GetMessage(&msg, NULL, 0, 0)) + { + if(msg.message != WOM_DONE || pData->bWaveShutdown) + continue; + + pWaveHdr = ((LPWAVEHDR)msg.lParam); + + aluMixData(pDevice, pWaveHdr->lpData, pWaveHdr->dwBufferLength/FrameSize); + + // Send buffer back to play more data + waveOutWrite(pData->hWaveHandle.Out, pWaveHdr, sizeof(WAVEHDR)); + InterlockedIncrement(&pData->lWaveBuffersCommitted); + } + + // Signal Wave Thread completed event + if(pData->hWaveThreadEvent) + SetEvent(pData->hWaveThreadEvent); + + ExitThread(0); + + return 0; +} + +/* + WaveInProc + + Posts a message to 'CaptureThreadProc' everytime a WaveIn Buffer is completed and + returns to the application (with more data) +*/ +static void CALLBACK WaveInProc(HWAVEIN hDevice,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2) +{ + ALCdevice *pDevice = (ALCdevice*)dwInstance; + WinMMData *pData = pDevice->ExtraData; + + (void)hDevice; + (void)dwParam2; + + if(uMsg != WIM_DATA) + return; + + // Decrement number of buffers in use + InterlockedDecrement(&pData->lWaveBuffersCommitted); + + if(pData->bWaveShutdown == AL_FALSE) + { + // Notify Wave Processor Thread that a Wave Header has returned + PostThreadMessage(pData->ulWaveThreadID,uMsg,0,dwParam1); + } + else + { + if(pData->lWaveBuffersCommitted == 0) + { + // Signal Wave Buffers Returned event + if(pData->hWaveHdrEvent) + SetEvent(pData->hWaveHdrEvent); + + // Post 'Quit' Message to WaveIn Processor Thread + PostThreadMessage(pData->ulWaveThreadID,WM_QUIT,0,0); + } + } +} + +/* + CaptureThreadProc + + Used by "MMSYSTEM" Device. Called when a WaveIn buffer had been filled with new + audio data. +*/ +static DWORD WINAPI CaptureThreadProc(LPVOID lpParameter) +{ + ALCdevice *pDevice = (ALCdevice*)lpParameter; + WinMMData *pData = pDevice->ExtraData; + LPWAVEHDR pWaveHdr; + ALuint FrameSize; + MSG msg; + + FrameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + while(GetMessage(&msg, NULL, 0, 0)) + { + if(msg.message != WIM_DATA || pData->bWaveShutdown) + continue; + + pWaveHdr = ((LPWAVEHDR)msg.lParam); + + WriteRingBuffer(pData->pRing, (ALubyte*)pWaveHdr->lpData, + pWaveHdr->dwBytesRecorded/FrameSize); + + // Send buffer back to capture more data + waveInAddBuffer(pData->hWaveHandle.In,pWaveHdr,sizeof(WAVEHDR)); + InterlockedIncrement(&pData->lWaveBuffersCommitted); + } + + // Signal Wave Thread completed event + if(pData->hWaveThreadEvent) + SetEvent(pData->hWaveThreadEvent); + + ExitThread(0); + + return 0; +} + + +static ALCboolean WinMMOpenPlayback(ALCdevice *pDevice, const ALCchar *deviceName) +{ + WAVEFORMATEX wfexFormat; + WinMMData *pData = NULL; + UINT lDeviceID = 0; + MMRESULT res; + ALuint i = 0; + + // Find the Device ID matching the deviceName if valid + if(!deviceName || strcmp(deviceName, woDefault) == 0) + lDeviceID = WAVE_MAPPER; + else + { + if(!PlaybackDeviceList) + ProbePlaybackDevices(); + + for(i = 0;i < NumPlaybackDevices;i++) + { + if(PlaybackDeviceList[i] && + strcmp(deviceName, PlaybackDeviceList[i]) == 0) + { + lDeviceID = i; + break; + } + } + if(i == NumPlaybackDevices) + return ALC_FALSE; + } + + pData = calloc(1, sizeof(*pData)); + if(!pData) + { + alcSetError(pDevice, ALC_OUT_OF_MEMORY); + return ALC_FALSE; + } + pDevice->ExtraData = pData; + + if(pDevice->FmtChans != DevFmtMono) + pDevice->FmtChans = DevFmtStereo; + switch(pDevice->FmtType) + { + case DevFmtByte: + pDevice->FmtType = DevFmtUByte; + break; + case DevFmtUShort: + case DevFmtFloat: + pDevice->FmtType = DevFmtShort; + break; + case DevFmtUByte: + case DevFmtShort: + break; + } + + memset(&wfexFormat, 0, sizeof(WAVEFORMATEX)); + wfexFormat.wFormatTag = WAVE_FORMAT_PCM; + wfexFormat.nChannels = ChannelsFromDevFmt(pDevice->FmtChans); + wfexFormat.wBitsPerSample = BytesFromDevFmt(pDevice->FmtType) * 8; + wfexFormat.nBlockAlign = wfexFormat.wBitsPerSample * + wfexFormat.nChannels / 8; + wfexFormat.nSamplesPerSec = pDevice->Frequency; + wfexFormat.nAvgBytesPerSec = wfexFormat.nSamplesPerSec * + wfexFormat.nBlockAlign; + wfexFormat.cbSize = 0; + + if((res=waveOutOpen(&pData->hWaveHandle.Out, lDeviceID, &wfexFormat, (DWORD_PTR)&WaveOutProc, (DWORD_PTR)pDevice, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) + { + AL_PRINT("waveInOpen failed: %u\n", res); + goto failure; + } + + pData->hWaveHdrEvent = CreateEvent(NULL, AL_TRUE, AL_FALSE, "WaveOutAllHeadersReturned"); + pData->hWaveThreadEvent = CreateEvent(NULL, AL_TRUE, AL_FALSE, "WaveOutThreadDestroyed"); + if(pData->hWaveHdrEvent == NULL || pData->hWaveThreadEvent == NULL) + { + AL_PRINT("CreateEvent failed: %lu\n", GetLastError()); + goto failure; + } + + pData->Frequency = pDevice->Frequency; + + pDevice->szDeviceName = strdup((lDeviceID==WAVE_MAPPER) ? woDefault : + PlaybackDeviceList[lDeviceID]); + return ALC_TRUE; + +failure: + if(pData->hWaveThreadEvent) + CloseHandle(pData->hWaveThreadEvent); + if(pData->hWaveHdrEvent) + CloseHandle(pData->hWaveHdrEvent); + + if(pData->hWaveHandle.Out) + waveOutClose(pData->hWaveHandle.Out); + + free(pData); + pDevice->ExtraData = NULL; + return ALC_FALSE; +} + +static void WinMMClosePlayback(ALCdevice *device) +{ + WinMMData *pData = (WinMMData*)device->ExtraData; + + // Close the Wave device + CloseHandle(pData->hWaveThreadEvent); + pData->hWaveThreadEvent = 0; + + CloseHandle(pData->hWaveHdrEvent); + pData->hWaveHdrEvent = 0; + + waveInClose(pData->hWaveHandle.In); + pData->hWaveHandle.In = 0; + + free(pData); + device->ExtraData = NULL; +} + +static ALCboolean WinMMResetPlayback(ALCdevice *device) +{ + WinMMData *pData = (WinMMData*)device->ExtraData; + ALbyte *BufferData; + ALint lBufferSize; + ALuint i; + + pData->hWaveThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PlaybackThreadProc, (LPVOID)device, 0, &pData->ulWaveThreadID); + if(pData->hWaveThread == NULL) + return ALC_FALSE; + + device->UpdateSize = (ALuint)((ALuint64)device->UpdateSize * + pData->Frequency / device->Frequency); + device->Frequency = pData->Frequency; + + pData->lWaveBuffersCommitted = 0; + + // Create 4 Buffers + lBufferSize = device->UpdateSize*device->NumUpdates / 4; + lBufferSize *= FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + + BufferData = calloc(4, lBufferSize); + for(i = 0;i < 4;i++) + { + memset(&pData->WaveBuffer[i], 0, sizeof(WAVEHDR)); + pData->WaveBuffer[i].dwBufferLength = lBufferSize; + pData->WaveBuffer[i].lpData = ((i==0) ? (LPSTR)BufferData : + (pData->WaveBuffer[i-1].lpData + + pData->WaveBuffer[i-1].dwBufferLength)); + waveOutPrepareHeader(pData->hWaveHandle.Out, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + waveOutWrite(pData->hWaveHandle.Out, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + InterlockedIncrement(&pData->lWaveBuffersCommitted); + } + + return ALC_TRUE; +} + +static void WinMMStopPlayback(ALCdevice *device) +{ + WinMMData *pData = (WinMMData*)device->ExtraData; + int i; + + if(pData->hWaveThread == NULL) + return; + + // Set flag to stop processing headers + pData->bWaveShutdown = AL_TRUE; + + // Wait for signal that all Wave Buffers have returned + WaitForSingleObjectEx(pData->hWaveHdrEvent, 5000, FALSE); + + // Wait for signal that Wave Thread has been destroyed + WaitForSingleObjectEx(pData->hWaveThreadEvent, 5000, FALSE); + + CloseHandle(pData->hWaveThread); + pData->hWaveThread = 0; + + pData->bWaveShutdown = AL_FALSE; + + // Release the wave buffers + for(i = 0;i < 4;i++) + { + waveOutUnprepareHeader(pData->hWaveHandle.Out, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + if(i == 0) + free(pData->WaveBuffer[i].lpData); + pData->WaveBuffer[i].lpData = NULL; + } +} + + +static ALCboolean WinMMOpenCapture(ALCdevice *pDevice, const ALCchar *deviceName) +{ + WAVEFORMATEX wfexCaptureFormat; + DWORD ulCapturedDataSize; + WinMMData *pData = NULL; + UINT lDeviceID = 0; + ALbyte *BufferData; + ALint lBufferSize; + MMRESULT res; + ALuint i; + + if(!CaptureDeviceList) + ProbeCaptureDevices(); + + // Find the Device ID matching the deviceName if valid + if(deviceName) + { + for(i = 0;i < NumCaptureDevices;i++) + { + if(CaptureDeviceList[i] && + strcmp(deviceName, CaptureDeviceList[i]) == 0) + { + lDeviceID = i; + break; + } + } + } + else + { + for(i = 0;i < NumCaptureDevices;i++) + { + if(CaptureDeviceList[i]) + { + lDeviceID = i; + break; + } + } + } + if(i == NumCaptureDevices) + return ALC_FALSE; + + pData = calloc(1, sizeof(*pData)); + if(!pData) + { + alcSetError(pDevice, ALC_OUT_OF_MEMORY); + return ALC_FALSE; + } + pDevice->ExtraData = pData; + + if((pDevice->FmtChans != DevFmtMono && pDevice->FmtChans != DevFmtStereo) || + (pDevice->FmtType != DevFmtUByte && pDevice->FmtType != DevFmtShort)) + { + alcSetError(pDevice, ALC_INVALID_ENUM); + goto failure; + } + + memset(&wfexCaptureFormat, 0, sizeof(WAVEFORMATEX)); + wfexCaptureFormat.wFormatTag = WAVE_FORMAT_PCM; + wfexCaptureFormat.nChannels = ChannelsFromDevFmt(pDevice->FmtChans); + wfexCaptureFormat.wBitsPerSample = BytesFromDevFmt(pDevice->FmtType) * 8; + wfexCaptureFormat.nBlockAlign = wfexCaptureFormat.wBitsPerSample * + wfexCaptureFormat.nChannels / 8; + wfexCaptureFormat.nSamplesPerSec = pDevice->Frequency; + wfexCaptureFormat.nAvgBytesPerSec = wfexCaptureFormat.nSamplesPerSec * + wfexCaptureFormat.nBlockAlign; + wfexCaptureFormat.cbSize = 0; + + if((res=waveInOpen(&pData->hWaveHandle.In, lDeviceID, &wfexCaptureFormat, (DWORD_PTR)&WaveInProc, (DWORD_PTR)pDevice, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) + { + AL_PRINT("waveInOpen failed: %u\n", res); + goto failure; + } + + pData->hWaveHdrEvent = CreateEvent(NULL, AL_TRUE, AL_FALSE, "WaveInAllHeadersReturned"); + pData->hWaveThreadEvent = CreateEvent(NULL, AL_TRUE, AL_FALSE, "WaveInThreadDestroyed"); + if(pData->hWaveHdrEvent == NULL || pData->hWaveThreadEvent == NULL) + { + AL_PRINT("CreateEvent failed: %lu\n", GetLastError()); + goto failure; + } + + pData->Frequency = pDevice->Frequency; + + // Allocate circular memory buffer for the captured audio + ulCapturedDataSize = pDevice->UpdateSize*pDevice->NumUpdates; + + // Make sure circular buffer is at least 100ms in size + if(ulCapturedDataSize < (wfexCaptureFormat.nSamplesPerSec / 10)) + ulCapturedDataSize = wfexCaptureFormat.nSamplesPerSec / 10; + + pData->pRing = CreateRingBuffer(wfexCaptureFormat.nBlockAlign, ulCapturedDataSize); + if(!pData->pRing) + goto failure; + + pData->lWaveBuffersCommitted = 0; + + // Create 4 Buffers of 50ms each + lBufferSize = wfexCaptureFormat.nAvgBytesPerSec / 20; + lBufferSize -= (lBufferSize % wfexCaptureFormat.nBlockAlign); + + BufferData = calloc(4, lBufferSize); + if(!BufferData) + goto failure; + + for(i = 0;i < 4;i++) + { + memset(&pData->WaveBuffer[i], 0, sizeof(WAVEHDR)); + pData->WaveBuffer[i].dwBufferLength = lBufferSize; + pData->WaveBuffer[i].lpData = ((i==0) ? (LPSTR)BufferData : + (pData->WaveBuffer[i-1].lpData + + pData->WaveBuffer[i-1].dwBufferLength)); + pData->WaveBuffer[i].dwFlags = 0; + pData->WaveBuffer[i].dwLoops = 0; + waveInPrepareHeader(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + waveInAddBuffer(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + InterlockedIncrement(&pData->lWaveBuffersCommitted); + } + + pData->hWaveThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CaptureThreadProc, (LPVOID)pDevice, 0, &pData->ulWaveThreadID); + if (pData->hWaveThread == NULL) + goto failure; + + pDevice->szDeviceName = strdup(CaptureDeviceList[lDeviceID]); + return ALC_TRUE; + +failure: + if(pData->hWaveThread) + CloseHandle(pData->hWaveThread); + + for(i = 0;i < 4;i++) + { + if(pData->WaveBuffer[i].lpData) + { + waveInUnprepareHeader(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + if(i == 0) + free(pData->WaveBuffer[i].lpData); + } + } + + if(pData->pRing) + DestroyRingBuffer(pData->pRing); + + if(pData->hWaveThreadEvent) + CloseHandle(pData->hWaveThreadEvent); + if(pData->hWaveHdrEvent) + CloseHandle(pData->hWaveHdrEvent); + + if(pData->hWaveHandle.In) + waveInClose(pData->hWaveHandle.In); + + free(pData); + pDevice->ExtraData = NULL; + return ALC_FALSE; +} + +static void WinMMCloseCapture(ALCdevice *pDevice) +{ + WinMMData *pData = (WinMMData*)pDevice->ExtraData; + int i; + + // Call waveOutReset to shutdown wave device + pData->bWaveShutdown = AL_TRUE; + waveInReset(pData->hWaveHandle.In); + + // Wait for signal that all Wave Buffers have returned + WaitForSingleObjectEx(pData->hWaveHdrEvent, 5000, FALSE); + + // Wait for signal that Wave Thread has been destroyed + WaitForSingleObjectEx(pData->hWaveThreadEvent, 5000, FALSE); + + CloseHandle(pData->hWaveThread); + pData->hWaveThread = 0; + + // Release the wave buffers + for(i = 0;i < 4;i++) + { + waveInUnprepareHeader(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + if(i == 0) + free(pData->WaveBuffer[i].lpData); + pData->WaveBuffer[i].lpData = NULL; + } + + DestroyRingBuffer(pData->pRing); + pData->pRing = NULL; + + // Close the Wave device + CloseHandle(pData->hWaveThreadEvent); + pData->hWaveThreadEvent = 0; + + CloseHandle(pData->hWaveHdrEvent); + pData->hWaveHdrEvent = 0; + + waveInClose(pData->hWaveHandle.In); + pData->hWaveHandle.In = 0; + + free(pData); + pDevice->ExtraData = NULL; +} + +static void WinMMStartCapture(ALCdevice *pDevice) +{ + WinMMData *pData = (WinMMData*)pDevice->ExtraData; + waveInStart(pData->hWaveHandle.In); +} + +static void WinMMStopCapture(ALCdevice *pDevice) +{ + WinMMData *pData = (WinMMData*)pDevice->ExtraData; + waveInStop(pData->hWaveHandle.In); +} + +static ALCuint WinMMAvailableSamples(ALCdevice *pDevice) +{ + WinMMData *pData = (WinMMData*)pDevice->ExtraData; + return RingBufferSize(pData->pRing); +} + +static void WinMMCaptureSamples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples) +{ + WinMMData *pData = (WinMMData*)pDevice->ExtraData; + + if(WinMMAvailableSamples(pDevice) >= lSamples) + ReadRingBuffer(pData->pRing, pBuffer, lSamples); + else + alcSetError(pDevice, ALC_INVALID_VALUE); +} + + +static BackendFuncs WinMMFuncs = { + WinMMOpenPlayback, + WinMMClosePlayback, + WinMMResetPlayback, + WinMMStopPlayback, + WinMMOpenCapture, + WinMMCloseCapture, + WinMMStartCapture, + WinMMStopCapture, + WinMMCaptureSamples, + WinMMAvailableSamples +}; + +void alcWinMMInit(BackendFuncs *FuncList) +{ + *FuncList = WinMMFuncs; +} + +void alcWinMMDeinit() +{ + ALuint lLoop; + + for(lLoop = 0;lLoop < NumPlaybackDevices;lLoop++) + free(PlaybackDeviceList[lLoop]); + free(PlaybackDeviceList); + PlaybackDeviceList = NULL; + + NumPlaybackDevices = 0; + + + for(lLoop = 0; lLoop < NumCaptureDevices; lLoop++) + free(CaptureDeviceList[lLoop]); + free(CaptureDeviceList); + CaptureDeviceList = NULL; + + NumCaptureDevices = 0; +} + +void alcWinMMProbe(int type) +{ + ALuint i; + + if(type == DEVICE_PROBE) + { + ProbePlaybackDevices(); + if(NumPlaybackDevices > 0) + AppendDeviceList(woDefault); + } + else if(type == ALL_DEVICE_PROBE) + { + ProbePlaybackDevices(); + if(NumPlaybackDevices > 0) + AppendAllDeviceList(woDefault); + for(i = 0;i < NumPlaybackDevices;i++) + { + if(PlaybackDeviceList[i]) + AppendAllDeviceList(PlaybackDeviceList[i]); + } + } + else if(type == CAPTURE_DEVICE_PROBE) + { + ProbeCaptureDevices(); + for(i = 0;i < NumCaptureDevices;i++) + { + if(CaptureDeviceList[i]) + AppendCaptureDeviceList(CaptureDeviceList[i]); + } + } +} diff --git a/jni/OpenAL/Makefile b/jni/OpenAL/Makefile new file mode 100644 index 0000000..1f84440 --- /dev/null +++ b/jni/OpenAL/Makefile @@ -0,0 +1,5 @@ +ROOTDIR = ../.. +TARGET = System/OpenAL +DEPS = + +include $(ROOTDIR)/library.mk diff --git a/jni/OpenAL/OpenAL32/Include/alAuxEffectSlot.h b/jni/OpenAL/OpenAL32/Include/alAuxEffectSlot.h new file mode 100644 index 0000000..1c592ac --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alAuxEffectSlot.h @@ -0,0 +1,63 @@ +#ifndef _AL_AUXEFFECTSLOT_H_ +#define _AL_AUXEFFECTSLOT_H_ + +#include "AL/al.h" +#include "alEffect.h" +#include "alFilter.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ALeffectState ALeffectState; + +typedef struct ALeffectslot +{ + ALeffect effect; + + ALfp Gain; + ALboolean AuxSendAuto; + + ALeffectState *EffectState; + + ALfp WetBuffer[BUFFERSIZE]; + + ALfp ClickRemoval[1]; + ALfp PendingClicks[1]; + + ALuint refcount; + + // Index to itself + ALuint effectslot; + + struct ALeffectslot *next; +} ALeffectslot; + + +ALvoid ReleaseALAuxiliaryEffectSlots(ALCcontext *Context); + + +struct ALeffectState { + ALvoid (*Destroy)(ALeffectState *State); + ALboolean (*DeviceUpdate)(ALeffectState *State, ALCdevice *Device); + ALvoid (*Update)(ALeffectState *State, ALCcontext *Context, const ALeffect *Effect); + ALvoid (*Process)(ALeffectState *State, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfp *SamplesIn, ALfp (*SamplesOut)[MAXCHANNELS]); +}; + +ALeffectState *NoneCreate(void); +ALeffectState *EAXVerbCreate(void); +ALeffectState *VerbCreate(void); +ALeffectState *EchoCreate(void); +ALeffectState *ModulatorCreate(void); + +#define ALEffect_Destroy(a) ((a)->Destroy((a))) +#define ALEffect_DeviceUpdate(a,b) ((a)->DeviceUpdate((a),(b))) +#define ALEffect_Update(a,b,c) ((a)->Update((a),(b),(c))) +#define ALEffect_Process(a,b,c,d,e) ((a)->Process((a),(b),(c),(d),(e))) + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/OpenAL32/Include/alBuffer.h b/jni/OpenAL/OpenAL32/Include/alBuffer.h new file mode 100644 index 0000000..e22d839 --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alBuffer.h @@ -0,0 +1,98 @@ +#ifndef _AL_BUFFER_H_ +#define _AL_BUFFER_H_ + +#include "AL/al.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Input formats (some are currently theoretical) */ +enum UserFmtType { + UserFmtByte, /* AL_BYTE */ + UserFmtUByte, /* AL_UNSIGNED_BYTE */ + UserFmtShort, /* AL_SHORT */ + UserFmtUShort, /* AL_UNSIGNED_SHORT */ + UserFmtInt, /* AL_INT */ + UserFmtUInt, /* AL_UNSIGNED_INT */ + UserFmtFloat, /* AL_FLOAT */ + UserFmtDouble, /* AL_DOUBLE */ + UserFmtMulaw, /* AL_MULAW */ + UserFmtIMA4, /* AL_IMA4 */ +}; +enum UserFmtChannels { + UserFmtMono, /* AL_MONO */ + UserFmtStereo, /* AL_STEREO */ + UserFmtRear, /* AL_REAR */ + UserFmtQuad, /* AL_QUAD */ + UserFmtX51, /* AL_5POINT1 (WFX order) */ + UserFmtX61, /* AL_6POINT1 (WFX order) */ + UserFmtX71, /* AL_7POINT1 (WFX order) */ +}; + +ALboolean DecomposeUserFormat(ALenum format, enum UserFmtChannels *chans, + enum UserFmtType *type); +ALuint BytesFromUserFmt(enum UserFmtType type); +ALuint ChannelsFromUserFmt(enum UserFmtChannels chans); +static __inline ALuint FrameSizeFromUserFmt(enum UserFmtChannels chans, + enum UserFmtType type) +{ + return ChannelsFromUserFmt(chans) * BytesFromUserFmt(type); +} + + +/* Storable formats */ +enum FmtType { + FmtUByte = UserFmtUByte, + FmtShort = UserFmtShort, + FmtFloat = UserFmtFloat, +}; +enum FmtChannels { + FmtMono = UserFmtMono, + FmtStereo = UserFmtStereo, + FmtRear = UserFmtRear, + FmtQuad = UserFmtQuad, + FmtX51 = UserFmtX51, + FmtX61 = UserFmtX61, + FmtX71 = UserFmtX71, +}; + +ALboolean DecomposeFormat(ALenum format, enum FmtChannels *chans, enum FmtType *type); +ALuint BytesFromFmt(enum FmtType type); +ALuint ChannelsFromFmt(enum FmtChannels chans); +static __inline ALuint FrameSizeFromFmt(enum FmtChannels chans, enum FmtType type) +{ + return ChannelsFromFmt(chans) * BytesFromFmt(type); +} + + +typedef struct ALbuffer +{ + ALvoid *data; + ALsizei size; + + ALsizei Frequency; + enum FmtChannels FmtChannels; + enum FmtType FmtType; + + enum UserFmtChannels OriginalChannels; + enum UserFmtType OriginalType; + ALsizei OriginalSize; + ALsizei OriginalAlign; + + ALsizei LoopStart; + ALsizei LoopEnd; + + ALuint refcount; // Number of sources using this buffer (deletion can only occur when this is 0) + + // Index to itself + ALuint buffer; +} ALbuffer; + +ALvoid ReleaseALBuffers(ALCdevice *device); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/OpenAL32/Include/alDatabuffer.h b/jni/OpenAL/OpenAL32/Include/alDatabuffer.h new file mode 100644 index 0000000..2218552 --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alDatabuffer.h @@ -0,0 +1,33 @@ +#ifndef _AL_DATABUFFER_H_ +#define _AL_DATABUFFER_H_ + +#include "AL/al.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define UNMAPPED 0 +#define MAPPED 1 + +typedef struct ALdatabuffer +{ + ALubyte *data; + ALintptrEXT size; + + ALenum state; + ALenum usage; + + /* Index to self */ + ALuint databuffer; + + struct ALdatabuffer *next; +} ALdatabuffer; + +ALvoid ReleaseALDatabuffers(ALCdevice *device); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/OpenAL32/Include/alEffect.h b/jni/OpenAL/OpenAL32/Include/alEffect.h new file mode 100644 index 0000000..041aa3e --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alEffect.h @@ -0,0 +1,83 @@ +// NOTE: The effect structure is getting too large, it may be a good idea to +// start using a union or another form of unified storage. +#ifndef _AL_EFFECT_H_ +#define _AL_EFFECT_H_ + +#include "AL/al.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + EAXREVERB = 0, + REVERB, + ECHO, + MODULATOR, + + MAX_EFFECTS +}; +extern ALboolean DisabledEffects[MAX_EFFECTS]; + +typedef struct ALeffect +{ + // Effect type (AL_EFFECT_NULL, ...) + ALenum type; + + struct { + // Shared Reverb Properties + ALfp Density; + ALfp Diffusion; + ALfp Gain; + ALfp GainHF; + ALfp DecayTime; + ALfp DecayHFRatio; + ALfp ReflectionsGain; + ALfp ReflectionsDelay; + ALfp LateReverbGain; + ALfp LateReverbDelay; + ALfp AirAbsorptionGainHF; + ALfp RoomRolloffFactor; + ALboolean DecayHFLimit; + + // Additional EAX Reverb Properties + ALfp GainLF; + ALfp DecayLFRatio; + ALfp ReflectionsPan[3]; + ALfp LateReverbPan[3]; + ALfp EchoTime; + ALfp EchoDepth; + ALfp ModulationTime; + ALfp ModulationDepth; + ALfp HFReference; + ALfp LFReference; + } Reverb; + + struct { + ALfp Delay; + ALfp LRDelay; + + ALfp Damping; + ALfp Feedback; + + ALfp Spread; + } Echo; + + struct { + ALfp Frequency; + ALfp HighPassCutoff; + ALint Waveform; + } Modulator; + + // Index to itself + ALuint effect; +} ALeffect; + + +ALvoid ReleaseALEffects(ALCdevice *device); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/OpenAL32/Include/alError.h b/jni/OpenAL/OpenAL32/Include/alError.h new file mode 100644 index 0000000..7976e50 --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alError.h @@ -0,0 +1,17 @@ +#ifndef _AL_ERROR_H_ +#define _AL_ERROR_H_ + +#include "AL/al.h" +#include "AL/alc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +ALvoid alSetError(ALCcontext *Context, ALenum errorCode); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/OpenAL32/Include/alFilter.h b/jni/OpenAL/OpenAL32/Include/alFilter.h new file mode 100644 index 0000000..3b17b1f --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alFilter.h @@ -0,0 +1,139 @@ +#ifndef _AL_FILTER_H_ +#define _AL_FILTER_H_ + +#include "AL/al.h" +#include "alu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + ALfp coeff; +#ifndef _MSC_VER + ALfp history[0]; +#else + ALfp history[1]; +#endif +} FILTER; + +static __inline ALfp lpFilter4P(FILTER *iir, ALuint offset, ALfp input) +{ + ALfp *history = &iir->history[offset]; + ALfp a = iir->coeff; + ALfp output = input; + + output = output + ALfpMult((history[0]-output),a); + history[0] = output; + output = output + ALfpMult((history[1]-output),a); + history[1] = output; + output = output + ALfpMult((history[2]-output),a); + history[2] = output; + output = output + ALfpMult((history[3]-output),a); + history[3] = output; + + return output; +} + +static __inline ALfp lpFilter2P(FILTER *iir, ALuint offset, ALfp input) +{ + ALfp *history = &iir->history[offset]; + ALfp a = iir->coeff; + ALfp output = input; + + output = output + ALfpMult((history[0]-output),a); + history[0] = output; + output = output + ALfpMult((history[1]-output),a); + history[1] = output; + + return output; +} + +static __inline ALfp lpFilter1P(FILTER *iir, ALuint offset, ALfp input) +{ + ALfp *history = &iir->history[offset]; + ALfp a = iir->coeff; + ALfp output = input; + + output = output + ALfpMult((history[0]-output),a); + history[0] = output; + + return output; +} + +static __inline ALfp lpFilter4PC(const FILTER *iir, ALuint offset, ALfp input) +{ + const ALfp *history = &iir->history[offset]; + ALfp a = iir->coeff; + ALfp output = input; + + output = output + ALfpMult((history[0]-output),a); + output = output + ALfpMult((history[1]-output),a); + output = output + ALfpMult((history[2]-output),a); + output = output + ALfpMult((history[3]-output),a); + + return output; +} + +static __inline ALfp lpFilter2PC(const FILTER *iir, ALuint offset, ALfp input) +{ + const ALfp *history = &iir->history[offset]; + ALfp a = iir->coeff; + ALfp output = input; + + output = output + ALfpMult((history[0]-output),a); + output = output + ALfpMult((history[1]-output),a); + + return output; +} + +static __inline ALfp lpFilter1PC(FILTER *iir, ALuint offset, ALfp input) +{ + const ALfp *history = &iir->history[offset]; + ALfp a = iir->coeff; + ALfp output = input; + + output = output + ALfpMult((history[0]-output),a); + + return output; +} + +/* Calculates the low-pass filter coefficient given the pre-scaled gain and + * cos(w) value. Note that g should be pre-scaled (sqr(gain) for one-pole, + * sqrt(gain) for four-pole, etc) */ +static __inline ALfp lpCoeffCalc(ALfp g, ALfp cw) +{ + ALfp a = int2ALfp(0); + + /* Be careful with gains < 0.01, as that causes the coefficient + * head towards 1, which will flatten the signal */ + g = __max(g, float2ALfp(0.01f)); + if(g < float2ALfp(0.9999f)) /* 1-epsilon */ { + ALfp tmp; tmp = ALfpMult(ALfpMult(int2ALfp(2),g),(int2ALfp(1)-cw)) - ALfpMult(ALfpMult(g,g),(int2ALfp(1) - ALfpMult(cw,cw))); + a = ALfpDiv((int2ALfp(1) - ALfpMult(g,cw) - aluSqrt(tmp)), (int2ALfp(1) - g)); + } + + return a; +} + + +typedef struct ALfilter +{ + // Filter type (AL_FILTER_NULL, ...) + ALenum type; + + ALfp Gain; + ALfp GainHF; + + // Index to itself + ALuint filter; +} ALfilter; + + +ALvoid ReleaseALFilters(ALCdevice *device); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/OpenAL32/Include/alListener.h b/jni/OpenAL/OpenAL32/Include/alListener.h new file mode 100644 index 0000000..a2fc3ba --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alListener.h @@ -0,0 +1,24 @@ +#ifndef _AL_LISTENER_H_ +#define _AL_LISTENER_H_ + +#include "AL/al.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ALlistener_struct +{ + ALfp Position[3]; + ALfp Velocity[3]; + ALfp Forward[3]; + ALfp Up[3]; + ALfp Gain; + ALfp MetersPerUnit; +} ALlistener; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/OpenAL32/Include/alMain.h b/jni/OpenAL/OpenAL32/Include/alMain.h new file mode 100644 index 0000000..53497b4 --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alMain.h @@ -0,0 +1,500 @@ +#ifndef AL_MAIN_H +#define AL_MAIN_H + +#include +#include +#include + +#ifdef HAVE_FENV_H +#include +#endif + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#ifndef AL_EXT_sample_buffer_object +#define AL_EXT_sample_buffer_object 1 +typedef ptrdiff_t ALintptrEXT; +typedef ptrdiff_t ALsizeiptrEXT; +#define AL_SAMPLE_SOURCE_EXT 0x1040 +#define AL_SAMPLE_SINK_EXT 0x1041 +#define AL_READ_ONLY_EXT 0x1042 +#define AL_WRITE_ONLY_EXT 0x1043 +#define AL_READ_WRITE_EXT 0x1044 +#define AL_STREAM_WRITE_EXT 0x1045 +#define AL_STREAM_READ_EXT 0x1046 +#define AL_STREAM_COPY_EXT 0x1047 +#define AL_STATIC_WRITE_EXT 0x1048 +#define AL_STATIC_READ_EXT 0x1049 +#define AL_STATIC_COPY_EXT 0x104A +#define AL_DYNAMIC_WRITE_EXT 0x104B +#define AL_DYNAMIC_READ_EXT 0x104C +#define AL_DYNAMIC_COPY_EXT 0x104D +typedef ALvoid (AL_APIENTRY*PFNALGENDATABUFFERSEXTPROC)(ALsizei n,ALuint *puiBuffers); +typedef ALvoid (AL_APIENTRY*PFNALDELETEDATABUFFERSEXTPROC)(ALsizei n, const ALuint *puiBuffers); +typedef ALboolean (AL_APIENTRY*PFNALISDATABUFFEREXTPROC)(ALuint uiBuffer); +typedef ALvoid (AL_APIENTRY*PFNALDATABUFFERDATAEXTPROC)(ALuint buffer,const ALvoid *data,ALsizeiptrEXT size,ALenum usage); +typedef ALvoid (AL_APIENTRY*PFNALDATABUFFERSUBDATAEXTPROC)(ALuint buffer, ALintptrEXT start, ALsizeiptrEXT length, const ALvoid *); +typedef ALvoid (AL_APIENTRY*PFNALGETDATABUFFERSUBDATAEXTPROC)(ALuint buffer, ALintptrEXT start, ALsizeiptrEXT length, ALvoid *); +typedef ALvoid (AL_APIENTRY*PFNALDATABUFFERFEXTPROC)(ALuint buffer, ALenum eParam, ALfloat flValue); +typedef ALvoid (AL_APIENTRY*PFNALDATABUFFERFVEXTPROC)(ALuint buffer, ALenum eParam, const ALfloat* flValues); +typedef ALvoid (AL_APIENTRY*PFNALDATABUFFERIEXTPROC)(ALuint buffer, ALenum eParam, ALint lValue); +typedef ALvoid (AL_APIENTRY*PFNALDATABUFFERIVEXTPROC)(ALuint buffer, ALenum eParam, const ALint* plValues); +typedef ALvoid (AL_APIENTRY*PFNALGETDATABUFFERFEXTPROC)(ALuint buffer, ALenum eParam, ALfloat *pflValue); +typedef ALvoid (AL_APIENTRY*PFNALGETDATABUFFERFVEXTPROC)(ALuint buffer, ALenum eParam, ALfloat* pflValues); +typedef ALvoid (AL_APIENTRY*PFNALGETDATABUFFERIEXTPROC)(ALuint buffer, ALenum eParam, ALint *plValue); +typedef ALvoid (AL_APIENTRY*PFNALGETDATABUFFERIVEXTPROC)(ALuint buffer, ALenum eParam, ALint* plValues); +typedef ALvoid (AL_APIENTRY*PFNALSELECTDATABUFFEREXTPROC)(ALenum target, ALuint uiBuffer); +typedef ALvoid* (AL_APIENTRY*PFNALMAPDATABUFFEREXTPROC)(ALuint uiBuffer, ALintptrEXT start, ALsizeiptrEXT length, ALenum access); +typedef ALvoid (AL_APIENTRY*PFNALUNMAPDATABUFFEREXTPROC)(ALuint uiBuffer); +#ifdef AL_ALEXT_PROTOTYPES +AL_API ALvoid AL_APIENTRY alGenDatabuffersEXT(ALsizei n,ALuint *puiBuffers); +AL_API ALvoid AL_APIENTRY alDeleteDatabuffersEXT(ALsizei n, const ALuint *puiBuffers); +AL_API ALboolean AL_APIENTRY alIsDatabufferEXT(ALuint uiBuffer); +AL_API ALvoid AL_APIENTRY alDatabufferDataEXT(ALuint buffer,const ALvoid *data,ALsizeiptrEXT size,ALenum usage); +AL_API ALvoid AL_APIENTRY alDatabufferSubDataEXT(ALuint buffer, ALintptrEXT start, ALsizeiptrEXT length, const ALvoid *data); +AL_API ALvoid AL_APIENTRY alGetDatabufferSubDataEXT(ALuint buffer, ALintptrEXT start, ALsizeiptrEXT length, ALvoid *data); +AL_API ALvoid AL_APIENTRY alDatabufferfEXT(ALuint buffer, ALenum eParam, ALfloat flValue); +AL_API ALvoid AL_APIENTRY alDatabufferfvEXT(ALuint buffer, ALenum eParam, const ALfloat* flValues); +AL_API ALvoid AL_APIENTRY alDatabufferiEXT(ALuint buffer, ALenum eParam, ALint lValue); +AL_API ALvoid AL_APIENTRY alDatabufferivEXT(ALuint buffer, ALenum eParam, const ALint* plValues); +AL_API ALvoid AL_APIENTRY alGetDatabufferfEXT(ALuint buffer, ALenum eParam, ALfloat *pflValue); +AL_API ALvoid AL_APIENTRY alGetDatabufferfvEXT(ALuint buffer, ALenum eParam, ALfloat* pflValues); +AL_API ALvoid AL_APIENTRY alGetDatabufferiEXT(ALuint buffer, ALenum eParam, ALint *plValue); +AL_API ALvoid AL_APIENTRY alGetDatabufferivEXT(ALuint buffer, ALenum eParam, ALint* plValues); +AL_API ALvoid AL_APIENTRY alSelectDatabufferEXT(ALenum target, ALuint uiBuffer); +AL_API ALvoid* AL_APIENTRY alMapDatabufferEXT(ALuint uiBuffer, ALintptrEXT start, ALsizeiptrEXT length, ALenum access); +AL_API ALvoid AL_APIENTRY alUnmapDatabufferEXT(ALuint uiBuffer); +#endif +#endif + + +#if defined(HAVE_STDINT_H) +#include +typedef int64_t ALint64; +typedef uint64_t ALuint64; +#elif defined(HAVE___INT64) +typedef __int64 ALint64; +typedef unsigned __int64 ALuint64; +#elif (SIZEOF_LONG == 8) +typedef long ALint64; +typedef unsigned long ALuint64; +#elif (SIZEOF_LONG_LONG == 8) +typedef long long ALint64; +typedef unsigned long long ALuint64; +#endif + +#ifdef HAVE_GCC_FORMAT +#define PRINTF_STYLE(x, y) __attribute__((format(printf, (x), (y)))) +#else +#define PRINTF_STYLE(x, y) +#endif + +#ifdef _WIN32 + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif +#include + +typedef DWORD tls_type; +#define tls_create(x) (*(x) = TlsAlloc()) +#define tls_delete(x) TlsFree((x)) +#define tls_get(x) TlsGetValue((x)) +#define tls_set(x, a) TlsSetValue((x), (a)) + +#else + +#include +#include +#include +#ifdef HAVE_PTHREAD_NP_H +#include +#endif +#include +#include +#include + +#define IsBadWritePtr(a,b) ((a) == NULL && (b) != 0) + +typedef pthread_key_t tls_type; +#define tls_create(x) pthread_key_create((x), NULL) +#define tls_delete(x) pthread_key_delete((x)) +#define tls_get(x) pthread_getspecific((x)) +#define tls_set(x, a) pthread_setspecific((x), (a)) + +typedef pthread_mutex_t CRITICAL_SECTION; +static __inline void EnterCriticalSection(CRITICAL_SECTION *cs) +{ + int ret; + ret = pthread_mutex_lock(cs); + assert(ret == 0); +} +static __inline void LeaveCriticalSection(CRITICAL_SECTION *cs) +{ + int ret; + ret = pthread_mutex_unlock(cs); + assert(ret == 0); +} +static __inline void InitializeCriticalSection(CRITICAL_SECTION *cs) +{ + pthread_mutexattr_t attrib; + int ret; + + ret = pthread_mutexattr_init(&attrib); + assert(ret == 0); + + ret = pthread_mutexattr_settype(&attrib, PTHREAD_MUTEX_RECURSIVE); +#ifdef HAVE_PTHREAD_NP_H + if(ret != 0) + ret = pthread_mutexattr_setkind_np(&attrib, PTHREAD_MUTEX_RECURSIVE); +#endif + assert(ret == 0); + ret = pthread_mutex_init(cs, &attrib); + assert(ret == 0); + + pthread_mutexattr_destroy(&attrib); +} + +static __inline void DeleteCriticalSection(CRITICAL_SECTION *cs) +{ + int ret; + ret = pthread_mutex_destroy(cs); + assert(ret == 0); +} + +/* NOTE: This wrapper isn't quite accurate as it returns an ALuint, as opposed + * to the expected DWORD. Both are defined as unsigned 32-bit types, however. + * Additionally, Win32 is supposed to measure the time since Windows started, + * as opposed to the actual time. */ +static __inline ALuint timeGetTime(void) +{ +#if _POSIX_TIMERS > 0 + struct timespec ts; + int ret = -1; + +#if defined(_POSIX_MONOTONIC_CLOCK) && (_POSIX_MONOTONIC_CLOCK >= 0) +#if _POSIX_MONOTONIC_CLOCK == 0 + static int hasmono = 0; + if(hasmono > 0 || (hasmono == 0 && + (hasmono=sysconf(_SC_MONOTONIC_CLOCK)) > 0)) +#endif + ret = clock_gettime(CLOCK_MONOTONIC, &ts); +#endif + if(ret != 0) + ret = clock_gettime(CLOCK_REALTIME, &ts); + assert(ret == 0); + + return ts.tv_nsec/1000000 + ts.tv_sec*1000; +#else + struct timeval tv; + int ret; + + ret = gettimeofday(&tv, NULL); + assert(ret == 0); + + return tv.tv_usec/1000 + tv.tv_sec*1000; +#endif +} + +static __inline void Sleep(ALuint t) +{ + struct timespec tv, rem; + tv.tv_nsec = (t*1000000)%1000000000; + tv.tv_sec = t/1000; + + while(nanosleep(&tv, &rem) == -1 && errno == EINTR) + tv = rem; +} +#define min(x,y) (((x)<(y))?(x):(y)) +#define max(x,y) (((x)>(y))?(x):(y)) +#endif + +#include "alListener.h" +#include "alu.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +#define SWMIXER_OUTPUT_RATE 44100 + +#define SPEEDOFSOUNDMETRESPERSEC (343.3f) +#define AIRABSORBGAINDBHF (-0.05f) + +#define LOWPASSFREQCUTOFF (5000) + +#define DEFAULT_HEAD_DAMPEN (0.25f) + + +// Find the next power-of-2 for non-power-of-2 numbers. +static __inline ALuint NextPowerOf2(ALuint value) +{ + ALuint powerOf2 = 1; + + if(value) + { + value--; + while(value) + { + value >>= 1; + powerOf2 <<= 1; + } + } + return powerOf2; +} + + +typedef struct { + ALCboolean (*OpenPlayback)(ALCdevice*, const ALCchar*); + void (*ClosePlayback)(ALCdevice*); + ALCboolean (*ResetPlayback)(ALCdevice*); + void (*StopPlayback)(ALCdevice*); + + ALCboolean (*OpenCapture)(ALCdevice*, const ALCchar*); + void (*CloseCapture)(ALCdevice*); + void (*StartCapture)(ALCdevice*); + void (*StopCapture)(ALCdevice*); + void (*CaptureSamples)(ALCdevice*, void*, ALCuint); + ALCuint (*AvailableSamples)(ALCdevice*); +} BackendFuncs; + +enum { + DEVICE_PROBE, + ALL_DEVICE_PROBE, + CAPTURE_DEVICE_PROBE +}; + +void alc_alsa_init(BackendFuncs *func_list); +void alc_alsa_deinit(void); +void alc_alsa_probe(int type); +void alc_oss_init(BackendFuncs *func_list); +void alc_oss_deinit(void); +void alc_oss_probe(int type); +void alc_solaris_init(BackendFuncs *func_list); +void alc_solaris_deinit(void); +void alc_solaris_probe(int type); +void alcDSoundInit(BackendFuncs *func_list); +void alcDSoundDeinit(void); +void alcDSoundProbe(int type); +void alcWinMMInit(BackendFuncs *FuncList); +void alcWinMMDeinit(void); +void alcWinMMProbe(int type); +void alc_pa_init(BackendFuncs *func_list); +void alc_pa_deinit(void); +void alc_pa_probe(int type); +void alc_wave_init(BackendFuncs *func_list); +void alc_wave_deinit(void); +void alc_wave_probe(int type); +void alc_pulse_init(BackendFuncs *func_list); +void alc_pulse_deinit(void); +void alc_pulse_probe(int type); +void alc_audiotrack_init(BackendFuncs *func_list); +void alc_audiotrack_deinit(void); +void alc_audiotrack_probe(int type); +void alc_opensles_init(BackendFuncs *func_list); +void alc_opensles_deinit(void); +void alc_opensles_probe(int type); +void alc_null_init(BackendFuncs *func_list); +void alc_null_deinit(void); +void alc_null_probe(int type); + + +typedef struct UIntMap { + struct { + ALuint key; + ALvoid *value; + } *array; + ALsizei size; + ALsizei maxsize; +} UIntMap; + +void InitUIntMap(UIntMap *map); +void ResetUIntMap(UIntMap *map); +ALenum InsertUIntMapEntry(UIntMap *map, ALuint key, ALvoid *value); +void RemoveUIntMapKey(UIntMap *map, ALuint key); +ALvoid *LookupUIntMapKey(UIntMap *map, ALuint key); + +/* Device formats */ +enum DevFmtType { + DevFmtByte, /* AL_BYTE */ + DevFmtUByte, /* AL_UNSIGNED_BYTE */ + DevFmtShort, /* AL_SHORT */ + DevFmtUShort, /* AL_UNSIGNED_SHORT */ + DevFmtFloat, /* AL_FLOAT */ +}; +enum DevFmtChannels { + DevFmtMono, /* AL_MONO */ + DevFmtStereo, /* AL_STEREO */ + DevFmtQuad, /* AL_QUAD */ + DevFmtX51, /* AL_5POINT1 */ + DevFmtX61, /* AL_6POINT1 */ + DevFmtX71, /* AL_7POINT1 */ +}; + +ALuint BytesFromDevFmt(enum DevFmtType type); +ALuint ChannelsFromDevFmt(enum DevFmtChannels chans); +static __inline ALuint FrameSizeFromDevFmt(enum DevFmtChannels chans, + enum DevFmtType type) +{ + return ChannelsFromDevFmt(chans) * BytesFromDevFmt(type); +} + + +struct ALCdevice_struct +{ + ALCboolean Connected; + ALboolean IsCaptureDevice; + + ALuint Frequency; + ALuint UpdateSize; + ALuint NumUpdates; + enum DevFmtChannels FmtChans; + enum DevFmtType FmtType; + + ALCchar *szDeviceName; + + ALCenum LastError; + + // Maximum number of sources that can be created + ALuint MaxNoOfSources; + // Maximum number of slots that can be created + ALuint AuxiliaryEffectSlotMax; + + ALCuint NumMonoSources; + ALCuint NumStereoSources; + ALuint NumAuxSends; + + // Map of Buffers for this device + UIntMap BufferMap; + + // Map of Effects for this device + UIntMap EffectMap; + + // Map of Filters for this device + UIntMap FilterMap; + + // Map of Databuffers for this device + UIntMap DatabufferMap; + + // Stereo-to-binaural filter + struct bs2b *Bs2b; + ALCint Bs2bLevel; + + // Simulated dampening from head occlusion + ALfp HeadDampen; + + // Duplicate stereo sources on the side/rear channels + ALboolean DuplicateStereo; + + // Dry path buffer mix + ALfp DryBuffer[BUFFERSIZE][MAXCHANNELS]; + + ALuint DevChannels[MAXCHANNELS]; + + ALfp ChannelMatrix[MAXCHANNELS][MAXCHANNELS]; + + Channel Speaker2Chan[MAXCHANNELS]; + ALfp PanningLUT[MAXCHANNELS * LUT_NUM]; + ALuint NumChan; + + ALfp ClickRemoval[MAXCHANNELS]; + ALfp PendingClicks[MAXCHANNELS]; + + // Contexts created on this device + ALCcontext **Contexts; + ALuint NumContexts; + + BackendFuncs *Funcs; + void *ExtraData; // For the backend's use + + ALCdevice *next; +}; + +#define ALCdevice_OpenPlayback(a,b) ((a)->Funcs->OpenPlayback((a), (b))) +#define ALCdevice_ClosePlayback(a) ((a)->Funcs->ClosePlayback((a))) +#define ALCdevice_ResetPlayback(a) ((a)->Funcs->ResetPlayback((a))) +#define ALCdevice_StopPlayback(a) ((a)->Funcs->StopPlayback((a))) +#define ALCdevice_OpenCapture(a,b) ((a)->Funcs->OpenCapture((a), (b))) +#define ALCdevice_CloseCapture(a) ((a)->Funcs->CloseCapture((a))) +#define ALCdevice_StartCapture(a) ((a)->Funcs->StartCapture((a))) +#define ALCdevice_StopCapture(a) ((a)->Funcs->StopCapture((a))) +#define ALCdevice_CaptureSamples(a,b,c) ((a)->Funcs->CaptureSamples((a), (b), (c))) +#define ALCdevice_AvailableSamples(a) ((a)->Funcs->AvailableSamples((a))) + +struct ALCcontext_struct +{ + ALlistener Listener; + + UIntMap SourceMap; + UIntMap EffectSlotMap; + + struct ALdatabuffer *SampleSource; + struct ALdatabuffer *SampleSink; + + ALenum LastError; + + ALboolean Suspended; + + ALenum DistanceModel; + ALboolean SourceDistanceModel; + + ALfp DopplerFactor; + ALfp DopplerVelocity; + ALfp flSpeedOfSound; + + struct ALsource **ActiveSources; + ALsizei ActiveSourceCount; + ALsizei MaxActiveSources; + + ALCdevice *Device; + const ALCchar *ExtensionList; + + ALCcontext *next; +}; + +void AppendDeviceList(const ALCchar *name); +void AppendAllDeviceList(const ALCchar *name); +void AppendCaptureDeviceList(const ALCchar *name); + +ALCvoid alcSetError(ALCdevice *device, ALenum errorCode); + +ALCvoid SuspendContext(ALCcontext *context); +ALCvoid ProcessContext(ALCcontext *context); + +ALvoid *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr); +ALuint StopThread(ALvoid *thread); + +ALCcontext *GetContextSuspended(void); + +typedef struct RingBuffer RingBuffer; +RingBuffer *CreateRingBuffer(ALsizei frame_size, ALsizei length); +void DestroyRingBuffer(RingBuffer *ring); +ALsizei RingBufferSize(RingBuffer *ring); +void WriteRingBuffer(RingBuffer *ring, const ALubyte *data, ALsizei len); +void ReadRingBuffer(RingBuffer *ring, ALubyte *data, ALsizei len); + +void ReadALConfig(void); +void FreeALConfig(void); +int ConfigValueExists(const char *blockName, const char *keyName); +const char *GetConfigValue(const char *blockName, const char *keyName, const char *def); +int GetConfigValueInt(const char *blockName, const char *keyName, int def); +float GetConfigValueFloat(const char *blockName, const char *keyName, float def); +int GetConfigValueBool(const char *blockName, const char *keyName, int def); + +void SetRTPriority(void); + +void SetDefaultChannelOrder(ALCdevice *device); +void SetDefaultWFXChannelOrder(ALCdevice *device); + +void al_print(const char *fname, unsigned int line, const char *fmt, ...) + PRINTF_STYLE(3,4); +#define AL_PRINT(...) al_print(__FILE__, __LINE__, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/OpenAL32/Include/alSource.h b/jni/OpenAL/OpenAL32/Include/alSource.h new file mode 100644 index 0000000..c802212 --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alSource.h @@ -0,0 +1,121 @@ +#ifndef _AL_SOURCE_H_ +#define _AL_SOURCE_H_ + +#define MAX_SENDS 4 + +#include "alFilter.h" +#include "alu.h" +#include "AL/al.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + POINT_RESAMPLER = 0, + LINEAR_RESAMPLER, + CUBIC_RESAMPLER, + + RESAMPLER_MAX, + RESAMPLER_MIN = -1, + RESAMPLER_DEFAULT = LINEAR_RESAMPLER +} resampler_t; +extern resampler_t DefaultResampler; + +extern const ALsizei ResamplerPadding[RESAMPLER_MAX]; +extern const ALsizei ResamplerPrePadding[RESAMPLER_MAX]; + + +typedef struct ALbufferlistitem +{ + struct ALbuffer *buffer; + struct ALbufferlistitem *next; + struct ALbufferlistitem *prev; +} ALbufferlistitem; + +typedef struct ALsource +{ + ALfp flPitch; + ALfp flGain; + ALfp flOuterGain; + ALfp flMinGain; + ALfp flMaxGain; + ALfp flInnerAngle; + ALfp flOuterAngle; + ALfp flRefDistance; + ALfp flMaxDistance; + ALfp flRollOffFactor; + ALfp vPosition[3]; + ALfp vVelocity[3]; + ALfp vOrientation[3]; + ALboolean bHeadRelative; + ALboolean bLooping; + ALenum DistanceModel; + + resampler_t Resampler; + + ALenum state; + ALuint position; + ALuint position_fraction; + + struct ALbuffer *Buffer; + + ALbufferlistitem *queue; // Linked list of buffers in queue + ALuint BuffersInQueue; // Number of buffers in queue + ALuint BuffersPlayed; // Number of buffers played on this loop + + ALfilter DirectFilter; + + struct { + struct ALeffectslot *Slot; + ALfilter WetFilter; + } Send[MAX_SENDS]; + + ALboolean DryGainHFAuto; + ALboolean WetGainAuto; + ALboolean WetGainHFAuto; + ALfp OuterGainHF; + + ALfp AirAbsorptionFactor; + ALfp RoomRolloffFactor; + ALfp DopplerFactor; + + ALint lOffset; + ALint lOffsetType; + + // Source Type (Static, Streaming, or Undetermined) + ALint lSourceType; + + // Current target parameters used for mixing + ALboolean NeedsUpdate; + struct { + ALint Step; + + /* A mixing matrix. First subscript is the channel number of the input + * data (regardless of channel configuration) and the second is the + * channel target (eg. FRONT_LEFT) */ + ALfp DryGains[MAXCHANNELS][MAXCHANNELS]; + FILTER iirFilter; + ALfp history[MAXCHANNELS*2]; + + struct { + ALfp WetGain; + FILTER iirFilter; + ALfp history[MAXCHANNELS]; + } Send[MAX_SENDS]; + } Params; + + ALvoid (*Update)(struct ALsource *self, const ALCcontext *context); + + // Index to itself + ALuint source; +} ALsource; +#define ALsource_Update(s,a) ((s)->Update(s,a)) + +ALvoid ReleaseALSources(ALCcontext *Context); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/OpenAL32/Include/alState.h b/jni/OpenAL/OpenAL32/Include/alState.h new file mode 100644 index 0000000..332176b --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alState.h @@ -0,0 +1,14 @@ +#ifndef _AL_STATE_H_ +#define _AL_STATE_H_ + +#include "AL/al.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/OpenAL32/Include/alThunk.h b/jni/OpenAL/OpenAL32/Include/alThunk.h new file mode 100644 index 0000000..902f00e --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alThunk.h @@ -0,0 +1,42 @@ +#ifndef _AL_THUNK_H_ +#define _AL_THUNK_H_ + +#include "config.h" + +#include "AL/al.h" +#include "AL/alc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void alThunkInit(void); +void alThunkExit(void); +ALuint alThunkAddEntry(ALvoid *ptr); +void alThunkRemoveEntry(ALuint index); +ALvoid *alThunkLookupEntry(ALuint index); + +#if (SIZEOF_VOIDP > SIZEOF_UINT) + +#define ALTHUNK_INIT() alThunkInit() +#define ALTHUNK_EXIT() alThunkExit() +#define ALTHUNK_ADDENTRY(p) alThunkAddEntry(p) +#define ALTHUNK_REMOVEENTRY(i) alThunkRemoveEntry(i) +#define ALTHUNK_LOOKUPENTRY(i) alThunkLookupEntry(i) + +#else + +#define ALTHUNK_INIT() +#define ALTHUNK_EXIT() +#define ALTHUNK_ADDENTRY(p) ((ALuint)p) +#define ALTHUNK_REMOVEENTRY(i) ((ALvoid)i) +#define ALTHUNK_LOOKUPENTRY(i) ((ALvoid*)(i)) + +#endif // (SIZEOF_VOIDP > SIZEOF_INT) + +#ifdef __cplusplus +} +#endif + +#endif //_AL_THUNK_H_ + diff --git a/jni/OpenAL/OpenAL32/Include/alu.h b/jni/OpenAL/OpenAL32/Include/alu.h new file mode 100644 index 0000000..89e51fb --- /dev/null +++ b/jni/OpenAL/OpenAL32/Include/alu.h @@ -0,0 +1,135 @@ +#ifndef _ALU_H_ +#define _ALU_H_ + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include +#include +#ifdef HAVE_FLOAT_H +#include +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif + +#ifdef HAVE_POWF +#define aluPow(x,y) (float2ALfp(powf(ALfp2float(x), ALfp2float(y)))) +#else +#define aluPow(x,y) (float2ALfp((float)pow((double)ALfp2float(x), (double)ALfp2float(y)))) +#endif + +#ifdef HAVE_SQRTF +#define aluSqrt(x) (float2ALfp(sqrtf(ALfp2float(x)))) +#else +#define aluSqrt(x) (float2ALfp((float)sqrt((double)ALfp2float(x)))) +#endif + +#ifdef HAVE_ACOSF +#define aluAcos(x) (float2ALfp(acosf(ALfp2float(x)))) +#else +#define aluAcos(x) (float2ALfp((float)acos((double)ALfp2float(x)))) +#endif + +#ifdef HAVE_ATANF +#define aluAtan(x) (float2ALfp(atanf(ALfp2float(x)))) +#else +#define aluAtan(x) (float2ALfp((float)atan((double)ALfp2float(x)))) +#endif + +#ifdef HAVE_FABSF +#define aluFabs(x) (float2ALfp(fabsf(ALfp2float(x)))) +#else +#define aluFabs(x) (float2ALfp((float)fabs((double)ALfp2float(x)))) +#endif + +// FIXME make this better +#if defined(max) && !defined(__max) +#define __max(x,y) float2ALfp(max(ALfp2float(x),ALfp2float(y))) +#endif +#if defined(min) && !defined(__min) +#define __min(x,y) float2ALfp(min(ALfp2float(x),ALfp2float(y))) +#endif + +#define QUADRANT_NUM 128 +#define LUT_NUM (4 * QUADRANT_NUM) + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + FRONT_LEFT = 0, + FRONT_RIGHT, + FRONT_CENTER, + LFE, + BACK_LEFT, + BACK_RIGHT, + BACK_CENTER, + SIDE_LEFT, + SIDE_RIGHT, + + MAXCHANNELS +} Channel; + +#define BUFFERSIZE 4096 + +#define FRACTIONBITS (14) +#define FRACTIONONE (1< +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "alMain.h" +#include "alAuxEffectSlot.h" +#include "alThunk.h" +#include "alError.h" +#include "alSource.h" + + +static ALvoid InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect); + +#define LookupEffectSlot(m, k) ((ALeffectslot*)LookupUIntMapKey(&(m), (k))) +#define LookupEffect(m, k) ((ALeffect*)LookupUIntMapKey(&(m), (k))) + +AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) +{ + ALCcontext *Context; + ALCdevice *Device; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if(n < 0 || IsBadWritePtr((void*)effectslots, n * sizeof(ALuint))) + alSetError(Context, AL_INVALID_VALUE); + else if((ALuint)n > Device->AuxiliaryEffectSlotMax - Context->EffectSlotMap.size) + alSetError(Context, AL_INVALID_VALUE); + else + { + ALenum err; + ALsizei i, j; + + i = 0; + while(i < n) + { + ALeffectslot *slot = calloc(1, sizeof(ALeffectslot)); + if(!slot || !(slot->EffectState=NoneCreate())) + { + free(slot); + // We must have run out or memory + alSetError(Context, AL_OUT_OF_MEMORY); + alDeleteAuxiliaryEffectSlots(i, effectslots); + break; + } + + slot->effectslot = (ALuint)ALTHUNK_ADDENTRY(slot); + err = InsertUIntMapEntry(&Context->EffectSlotMap, + slot->effectslot, slot); + if(err != AL_NO_ERROR) + { + ALTHUNK_REMOVEENTRY(slot->effectslot); + ALEffect_Destroy(slot->EffectState); + free(slot); + + alSetError(Context, err); + alDeleteAuxiliaryEffectSlots(i, effectslots); + break; + } + + effectslots[i++] = slot->effectslot; + + slot->Gain = int2ALfp(1); + slot->AuxSendAuto = AL_TRUE; + for(j = 0;j < BUFFERSIZE;j++) + slot->WetBuffer[j] = int2ALfp(0); + for(j = 0;j < 1;j++) + { + slot->ClickRemoval[j] = int2ALfp(0); + slot->PendingClicks[j] = int2ALfp(0); + } + slot->refcount = 0; + } + } + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) +{ + ALCcontext *Context; + ALeffectslot *EffectSlot; + ALboolean SlotsValid = AL_FALSE; + ALsizei i; + + Context = GetContextSuspended(); + if(!Context) return; + + if(n < 0) + alSetError(Context, AL_INVALID_VALUE); + else + { + SlotsValid = AL_TRUE; + // Check that all effectslots are valid + for(i = 0;i < n;i++) + { + if((EffectSlot=LookupEffectSlot(Context->EffectSlotMap, effectslots[i])) == NULL) + { + alSetError(Context, AL_INVALID_NAME); + SlotsValid = AL_FALSE; + break; + } + else if(EffectSlot->refcount > 0) + { + alSetError(Context, AL_INVALID_NAME); + SlotsValid = AL_FALSE; + break; + } + } + } + + if(SlotsValid) + { + // All effectslots are valid + for(i = 0;i < n;i++) + { + // Recheck that the effectslot is valid, because there could be duplicated names + if((EffectSlot=LookupEffectSlot(Context->EffectSlotMap, effectslots[i])) == NULL) + continue; + + ALEffect_Destroy(EffectSlot->EffectState); + + RemoveUIntMapKey(&Context->EffectSlotMap, EffectSlot->effectslot); + ALTHUNK_REMOVEENTRY(EffectSlot->effectslot); + + memset(EffectSlot, 0, sizeof(ALeffectslot)); + free(EffectSlot); + } + } + + ProcessContext(Context); +} + +AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot) +{ + ALCcontext *Context; + ALboolean result; + + Context = GetContextSuspended(); + if(!Context) return AL_FALSE; + + result = (LookupEffectSlot(Context->EffectSlotMap, effectslot) ? + AL_TRUE : AL_FALSE); + + ProcessContext(Context); + + return result; +} + +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue) +{ + ALCdevice *Device; + ALCcontext *Context; + ALboolean updateSources = AL_FALSE; + ALeffectslot *EffectSlot; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((EffectSlot=LookupEffectSlot(Context->EffectSlotMap, effectslot)) != NULL) + { + switch(param) + { + case AL_EFFECTSLOT_EFFECT: { + ALeffect *effect = NULL; + + if(iValue == 0 || + (effect=LookupEffect(Device->EffectMap, iValue)) != NULL) + { + InitializeEffect(Context, EffectSlot, effect); + updateSources = AL_TRUE; + } + else + alSetError(Context, AL_INVALID_VALUE); + } break; + + case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: + if(iValue == AL_TRUE || iValue == AL_FALSE) + { + EffectSlot->AuxSendAuto = iValue; + updateSources = AL_TRUE; + } + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + // Force updating the sources that use this slot, since it affects the + // sending parameters + if(updateSources) + { + ALsizei pos; + for(pos = 0;pos < Context->SourceMap.size;pos++) + { + ALsource *source = Context->SourceMap.array[pos].value; + ALuint i; + for(i = 0;i < Device->NumAuxSends;i++) + { + if(!source->Send[i].Slot || + source->Send[i].Slot->effectslot != effectslot) + continue; + source->NeedsUpdate = AL_TRUE; + break; + } + } + } + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues) +{ + ALCcontext *Context; + + Context = GetContextSuspended(); + if(!Context) return; + + if(LookupEffectSlot(Context->EffectSlotMap, effectslot) != NULL) + { + switch(param) + { + case AL_EFFECTSLOT_EFFECT: + case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: + alAuxiliaryEffectSloti(effectslot, param, piValues[0]); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flArg) +{ + ALCcontext *Context; + ALeffectslot *EffectSlot; + ALfp flValue = float2ALfp(flArg); + + Context = GetContextSuspended(); + if(!Context) return; + + if((EffectSlot=LookupEffectSlot(Context->EffectSlotMap, effectslot)) != NULL) + { + switch(param) + { + case AL_EFFECTSLOT_GAIN: + if(flValue >= int2ALfp(0) && flValue <= int2ALfp(1)) + EffectSlot->Gain = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues) +{ + ALCcontext *Context; + + Context = GetContextSuspended(); + if(!Context) return; + + if(LookupEffectSlot(Context->EffectSlotMap, effectslot) != NULL) + { + switch(param) + { + case AL_EFFECTSLOT_GAIN: + alAuxiliaryEffectSlotf(effectslot, param, pflValues[0]); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue) +{ + ALCcontext *Context; + ALeffectslot *EffectSlot; + + Context = GetContextSuspended(); + if(!Context) return; + + if((EffectSlot=LookupEffectSlot(Context->EffectSlotMap, effectslot)) != NULL) + { + switch(param) + { + case AL_EFFECTSLOT_EFFECT: + *piValue = EffectSlot->effect.effect; + break; + + case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: + *piValue = EffectSlot->AuxSendAuto; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues) +{ + ALCcontext *Context; + + Context = GetContextSuspended(); + if(!Context) return; + + if(LookupEffectSlot(Context->EffectSlotMap, effectslot) != NULL) + { + switch(param) + { + case AL_EFFECTSLOT_EFFECT: + case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: + alGetAuxiliaryEffectSloti(effectslot, param, piValues); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue) +{ + ALCcontext *Context; + ALeffectslot *EffectSlot; + + Context = GetContextSuspended(); + if(!Context) return; + + if((EffectSlot=LookupEffectSlot(Context->EffectSlotMap, effectslot)) != NULL) + { + switch(param) + { + case AL_EFFECTSLOT_GAIN: + *pflValue = ALfp2float(EffectSlot->Gain); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues) +{ + ALCcontext *Context; + + Context = GetContextSuspended(); + if(!Context) return; + + if(LookupEffectSlot(Context->EffectSlotMap, effectslot) != NULL) + { + switch(param) + { + case AL_EFFECTSLOT_GAIN: + alGetAuxiliaryEffectSlotf(effectslot, param, pflValues); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + + +static ALvoid NoneDestroy(ALeffectState *State) +{ free(State); } +static ALboolean NoneDeviceUpdate(ALeffectState *State, ALCdevice *Device) +{ + return AL_TRUE; + (void)State; + (void)Device; +} +static ALvoid NoneUpdate(ALeffectState *State, ALCcontext *Context, const ALeffect *Effect) +{ + (void)State; + (void)Context; + (void)Effect; +} +static ALvoid NoneProcess(ALeffectState *State, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfp *SamplesIn, ALfp (*SamplesOut)[MAXCHANNELS]) +{ + (void)State; + (void)Slot; + (void)SamplesToDo; + (void)SamplesIn; + (void)SamplesOut; +} +ALeffectState *NoneCreate(void) +{ + ALeffectState *state; + + state = calloc(1, sizeof(*state)); + if(!state) + return NULL; + + state->Destroy = NoneDestroy; + state->DeviceUpdate = NoneDeviceUpdate; + state->Update = NoneUpdate; + state->Process = NoneProcess; + + return state; +} + +static ALvoid InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect) +{ + if(EffectSlot->effect.type != (effect?effect->type:AL_EFFECT_NULL)) + { + ALeffectState *NewState = NULL; + if(!effect || effect->type == AL_EFFECT_NULL) + NewState = NoneCreate(); + else if(effect->type == AL_EFFECT_EAXREVERB) + NewState = EAXVerbCreate(); + else if(effect->type == AL_EFFECT_REVERB) + NewState = VerbCreate(); + else if(effect->type == AL_EFFECT_ECHO) + NewState = EchoCreate(); + else if(effect->type == AL_EFFECT_RING_MODULATOR) + NewState = ModulatorCreate(); + /* No new state? An error occured.. */ + if(NewState == NULL || + ALEffect_DeviceUpdate(NewState, Context->Device) == AL_FALSE) + { + if(NewState) + ALEffect_Destroy(NewState); + alSetError(Context, AL_OUT_OF_MEMORY); + return; + } + if(EffectSlot->EffectState) + ALEffect_Destroy(EffectSlot->EffectState); + EffectSlot->EffectState = NewState; + } + if(!effect) + memset(&EffectSlot->effect, 0, sizeof(EffectSlot->effect)); + else + memcpy(&EffectSlot->effect, effect, sizeof(*effect)); + ALEffect_Update(EffectSlot->EffectState, Context, effect); +} + + +ALvoid ReleaseALAuxiliaryEffectSlots(ALCcontext *Context) +{ + ALsizei pos; + for(pos = 0;pos < Context->EffectSlotMap.size;pos++) + { + ALeffectslot *temp = Context->EffectSlotMap.array[pos].value; + Context->EffectSlotMap.array[pos].value = NULL; + + // Release effectslot structure + ALEffect_Destroy(temp->EffectState); + + ALTHUNK_REMOVEENTRY(temp->effectslot); + memset(temp, 0, sizeof(ALeffectslot)); + free(temp); + } +} diff --git a/jni/OpenAL/OpenAL32/alBuffer.c b/jni/OpenAL/OpenAL32/alBuffer.c new file mode 100644 index 0000000..fafd974 --- /dev/null +++ b/jni/OpenAL/OpenAL32/alBuffer.c @@ -0,0 +1,1898 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" +#include "alError.h" +#include "alBuffer.h" +#include "alDatabuffer.h" +#include "alThunk.h" + + +static ALenum LoadData(ALbuffer *ALBuf, ALuint freq, ALenum NewFormat, ALsizei size, enum UserFmtChannels chans, enum UserFmtType type, const ALvoid *data); +static void ConvertData(ALvoid *dst, enum FmtType dstType, const ALvoid *src, enum UserFmtType srcType, ALsizei len); +static void ConvertDataIMA4(ALvoid *dst, enum FmtType dstType, const ALvoid *src, ALint chans, ALsizei len); + +#define LookupBuffer(m, k) ((ALbuffer*)LookupUIntMapKey(&(m), (k))) + + +/* + * Global Variables + */ + +/* IMA ADPCM Stepsize table */ +static const long IMAStep_size[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, + 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, + 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, + 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, + 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, + 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, + 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442, + 11487,12635,13899,15289,16818,18500,20350,22358,24633,27086,29794, + 32767 +}; + +/* IMA4 ADPCM Codeword decode table */ +static const long IMA4Codeword[16] = { + 1, 3, 5, 7, 9, 11, 13, 15, + -1,-3,-5,-7,-9,-11,-13,-15, +}; + +/* IMA4 ADPCM Step index adjust decode table */ +static const long IMA4Index_adjust[16] = { + -1,-1,-1,-1, 2, 4, 6, 8, + -1,-1,-1,-1, 2, 4, 6, 8 +}; + +/* A quick'n'dirty lookup table to decode a muLaw-encoded byte sample into a + * signed 16-bit sample */ +static const ALshort muLawDecompressionTable[256] = { + -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, + -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, + -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, + -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 +}; + +/* Values used when encoding a muLaw sample */ +static const int muLawBias = 0x84; +static const int muLawClip = 32635; +static const char muLawCompressTable[256] = +{ + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; + +/* + * alGenBuffers(ALsizei n, ALuint *buffers) + * + * Generates n AL Buffers, and stores the Buffers Names in the array pointed + * to by buffers + */ +AL_API ALvoid AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers) +{ + ALCcontext *Context; + ALsizei i=0; + + Context = GetContextSuspended(); + if(!Context) return; + + /* Check that we are actually generating some Buffers */ + if(n < 0 || IsBadWritePtr((void*)buffers, n * sizeof(ALuint))) + alSetError(Context, AL_INVALID_VALUE); + else + { + ALCdevice *device = Context->Device; + ALenum err; + + // Create all the new Buffers + while(i < n) + { + ALbuffer *buffer = calloc(1, sizeof(ALbuffer)); + if(!buffer) + { + alSetError(Context, AL_OUT_OF_MEMORY); + alDeleteBuffers(i, buffers); + break; + } + + buffer->buffer = (ALuint)ALTHUNK_ADDENTRY(buffer); + err = InsertUIntMapEntry(&device->BufferMap, buffer->buffer, buffer); + if(err != AL_NO_ERROR) + { + ALTHUNK_REMOVEENTRY(buffer->buffer); + memset(buffer, 0, sizeof(ALbuffer)); + free(buffer); + + alSetError(Context, err); + alDeleteBuffers(i, buffers); + break; + } + buffers[i++] = buffer->buffer; + } + } + + ProcessContext(Context); +} + +/* + * alDeleteBuffers(ALsizei n, ALuint *buffers) + * + * Deletes the n AL Buffers pointed to by buffers + */ +AL_API ALvoid AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers) +{ + ALCcontext *Context; + ALCdevice *device; + ALboolean Failed; + ALbuffer *ALBuf; + ALsizei i; + + Context = GetContextSuspended(); + if(!Context) return; + + Failed = AL_TRUE; + device = Context->Device; + /* Check we are actually Deleting some Buffers */ + if(n < 0) + alSetError(Context, AL_INVALID_VALUE); + else + { + Failed = AL_FALSE; + + /* Check that all the buffers are valid and can actually be deleted */ + for(i = 0;i < n;i++) + { + if(!buffers[i]) + continue; + + /* Check for valid Buffer ID */ + if((ALBuf=LookupBuffer(device->BufferMap, buffers[i])) == NULL) + { + alSetError(Context, AL_INVALID_NAME); + Failed = AL_TRUE; + break; + } + else if(ALBuf->refcount != 0) + { + /* Buffer still in use, cannot be deleted */ + alSetError(Context, AL_INVALID_OPERATION); + Failed = AL_TRUE; + break; + } + } + } + + /* If all the Buffers were valid (and have Reference Counts of 0), then we + * can delete them */ + if(!Failed) + { + for(i = 0;i < n;i++) + { + if((ALBuf=LookupBuffer(device->BufferMap, buffers[i])) == NULL) + continue; + + /* Release the memory used to store audio data */ + free(ALBuf->data); + + /* Release buffer structure */ + RemoveUIntMapKey(&device->BufferMap, ALBuf->buffer); + ALTHUNK_REMOVEENTRY(ALBuf->buffer); + + memset(ALBuf, 0, sizeof(ALbuffer)); + free(ALBuf); + } + } + + ProcessContext(Context); +} + +/* + * alIsBuffer(ALuint buffer) + * + * Checks if buffer is a valid Buffer Name + */ +AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer) +{ + ALCcontext *Context; + ALboolean result; + + Context = GetContextSuspended(); + if(!Context) return AL_FALSE; + + result = ((!buffer || LookupBuffer(Context->Device->BufferMap, buffer)) ? + AL_TRUE : AL_FALSE); + + ProcessContext(Context); + + return result; +} + +/* + * alBufferData(ALuint buffer, ALenum format, const ALvoid *data, + * ALsizei size, ALsizei freq) + * + * Fill buffer with audio data + */ +AL_API ALvoid AL_APIENTRY alBufferData(ALuint buffer,ALenum format,const ALvoid *data,ALsizei size,ALsizei freq) +{ + enum UserFmtChannels SrcChannels; + enum UserFmtType SrcType; + ALCcontext *Context; + ALCdevice *device; + ALbuffer *ALBuf; + ALenum err; + + Context = GetContextSuspended(); + if(!Context) return; + + if(Context->SampleSource) + { + ALintptrEXT offset; + + if(Context->SampleSource->state == MAPPED) + { + alSetError(Context, AL_INVALID_OPERATION); + ProcessContext(Context); + return; + } + + offset = (const ALubyte*)data - (ALubyte*)NULL; + data = Context->SampleSource->data + offset; + } + + device = Context->Device; + if((ALBuf=LookupBuffer(device->BufferMap, buffer)) == NULL) + alSetError(Context, AL_INVALID_NAME); + else if(ALBuf->refcount != 0) + alSetError(Context, AL_INVALID_VALUE); + else if(size < 0 || freq < 0) + alSetError(Context, AL_INVALID_VALUE); + else if(DecomposeUserFormat(format, &SrcChannels, &SrcType) == AL_FALSE) + alSetError(Context, AL_INVALID_ENUM); + else switch(SrcType) + { + case UserFmtByte: + case UserFmtUByte: + case UserFmtShort: + case UserFmtUShort: + case UserFmtInt: + case UserFmtUInt: + case UserFmtFloat: + err = LoadData(ALBuf, freq, format, size, SrcChannels, SrcType, data); + if(err != AL_NO_ERROR) + alSetError(Context, err); + break; + + case UserFmtDouble: { + ALenum NewFormat = AL_FORMAT_MONO_FLOAT32; + switch(SrcChannels) + { + case UserFmtMono: NewFormat = AL_FORMAT_MONO_FLOAT32; break; + case UserFmtStereo: NewFormat = AL_FORMAT_STEREO_FLOAT32; break; + case UserFmtRear: NewFormat = AL_FORMAT_REAR32; break; + case UserFmtQuad: NewFormat = AL_FORMAT_QUAD32; break; + case UserFmtX51: NewFormat = AL_FORMAT_51CHN32; break; + case UserFmtX61: NewFormat = AL_FORMAT_61CHN32; break; + case UserFmtX71: NewFormat = AL_FORMAT_71CHN32; break; + } + err = LoadData(ALBuf, freq, NewFormat, size, SrcChannels, SrcType, data); + if(err != AL_NO_ERROR) + alSetError(Context, err); + } break; + + case UserFmtMulaw: + case UserFmtIMA4: { + ALenum NewFormat = AL_FORMAT_MONO16; + switch(SrcChannels) + { + case UserFmtMono: NewFormat = AL_FORMAT_MONO16; break; + case UserFmtStereo: NewFormat = AL_FORMAT_STEREO16; break; + case UserFmtRear: NewFormat = AL_FORMAT_REAR16; break; + case UserFmtQuad: NewFormat = AL_FORMAT_QUAD16; break; + case UserFmtX51: NewFormat = AL_FORMAT_51CHN16; break; + case UserFmtX61: NewFormat = AL_FORMAT_61CHN16; break; + case UserFmtX71: NewFormat = AL_FORMAT_71CHN16; break; + } + err = LoadData(ALBuf, freq, NewFormat, size, SrcChannels, SrcType, data); + if(err != AL_NO_ERROR) + alSetError(Context, err); + } break; + } + + ProcessContext(Context); +} + +/* + * alBufferSubDataSOFT(ALuint buffer, ALenum format, const ALvoid *data, + * ALsizei offset, ALsizei length) + * + * Update buffer's audio data + */ +AL_API ALvoid AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length) +{ + enum UserFmtChannels SrcChannels; + enum UserFmtType SrcType; + ALCcontext *Context; + ALCdevice *device; + ALbuffer *ALBuf; + + Context = GetContextSuspended(); + if(!Context) return; + + if(Context->SampleSource) + { + ALintptrEXT offset; + + if(Context->SampleSource->state == MAPPED) + { + alSetError(Context, AL_INVALID_OPERATION); + ProcessContext(Context); + return; + } + + offset = (const ALubyte*)data - (ALubyte*)NULL; + data = Context->SampleSource->data + offset; + } + + device = Context->Device; + if((ALBuf=LookupBuffer(device->BufferMap, buffer)) == NULL) + alSetError(Context, AL_INVALID_NAME); + else if(length < 0 || offset < 0 || (length > 0 && data == NULL)) + alSetError(Context, AL_INVALID_VALUE); + else if(DecomposeUserFormat(format, &SrcChannels, &SrcType) == AL_FALSE || + SrcChannels != ALBuf->OriginalChannels || + SrcType != ALBuf->OriginalType) + alSetError(Context, AL_INVALID_ENUM); + else if(offset > ALBuf->OriginalSize || + length > ALBuf->OriginalSize-offset || + (offset%ALBuf->OriginalAlign) != 0 || + (length%ALBuf->OriginalAlign) != 0) + alSetError(Context, AL_INVALID_VALUE); + else + { + if(SrcType == UserFmtIMA4) + { + ALuint Channels = ChannelsFromFmt(ALBuf->FmtChannels); + ALuint Bytes = BytesFromFmt(ALBuf->FmtType); + + /* offset -> byte offset, length -> block count */ + offset /= 36; + offset *= 65; + offset *= Bytes; + length /= ALBuf->OriginalAlign; + + ConvertDataIMA4(&((ALubyte*)ALBuf->data)[offset], ALBuf->FmtType, + data, Channels, length); + } + else + { + ALuint OldBytes = BytesFromUserFmt(SrcType); + ALuint Bytes = BytesFromFmt(ALBuf->FmtType); + + offset /= OldBytes; + offset *= Bytes; + length /= OldBytes; + + ConvertData(&((ALubyte*)ALBuf->data)[offset], ALBuf->FmtType, + data, SrcType, length); + } + } + + ProcessContext(Context); +} + + +AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum eParam, ALfloat flValue) +{ + ALCcontext *pContext; + ALCdevice *device; + + (void)flValue; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(LookupBuffer(device->BufferMap, buffer) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum eParam, ALfloat flValue1, ALfloat flValue2, ALfloat flValue3) +{ + ALCcontext *pContext; + ALCdevice *device; + + (void)flValue1; + (void)flValue2; + (void)flValue3; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(LookupBuffer(device->BufferMap, buffer) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum eParam, const ALfloat* flValues) +{ + ALCcontext *pContext; + ALCdevice *device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(!flValues) + alSetError(pContext, AL_INVALID_VALUE); + else if(LookupBuffer(device->BufferMap, buffer) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum eParam, ALint lValue) +{ + ALCcontext *pContext; + ALCdevice *device; + + (void)lValue; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(LookupBuffer(device->BufferMap, buffer) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alBuffer3i( ALuint buffer, ALenum eParam, ALint lValue1, ALint lValue2, ALint lValue3) +{ + ALCcontext *pContext; + ALCdevice *device; + + (void)lValue1; + (void)lValue2; + (void)lValue3; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(LookupBuffer(device->BufferMap, buffer) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum eParam, const ALint* plValues) +{ + ALCcontext *pContext; + ALCdevice *device; + ALbuffer *ALBuf; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(!plValues) + alSetError(pContext, AL_INVALID_VALUE); + else if((ALBuf=LookupBuffer(device->BufferMap, buffer)) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + case AL_LOOP_POINTS_SOFT: + if(ALBuf->refcount > 0) + alSetError(pContext, AL_INVALID_OPERATION); + else if(plValues[0] < 0 || plValues[1] < 0 || + plValues[0] >= plValues[1] || ALBuf->size == 0) + alSetError(pContext, AL_INVALID_VALUE); + else + { + ALint maxlen = ALBuf->size / + FrameSizeFromFmt(ALBuf->FmtChannels, ALBuf->FmtType); + if(plValues[0] > maxlen || plValues[1] > maxlen) + alSetError(pContext, AL_INVALID_VALUE); + else + { + ALBuf->LoopStart = plValues[0]; + ALBuf->LoopEnd = plValues[1]; + } + } + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetBufferf(ALuint buffer, ALenum eParam, ALfloat *pflValue) +{ + ALCcontext *pContext; + ALCdevice *device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(!pflValue) + alSetError(pContext, AL_INVALID_VALUE); + else if(LookupBuffer(device->BufferMap, buffer) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum eParam, ALfloat* pflValue1, ALfloat* pflValue2, ALfloat* pflValue3) +{ + ALCcontext *pContext; + ALCdevice *device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(!pflValue1 || !pflValue2 || !pflValue3) + alSetError(pContext, AL_INVALID_VALUE); + else if(LookupBuffer(device->BufferMap, buffer) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum eParam, ALfloat* pflValues) +{ + ALCcontext *pContext; + ALCdevice *device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(!pflValues) + alSetError(pContext, AL_INVALID_VALUE); + else if(LookupBuffer(device->BufferMap, buffer) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetBufferi(ALuint buffer, ALenum eParam, ALint *plValue) +{ + ALCcontext *pContext; + ALbuffer *pBuffer; + ALCdevice *device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(!plValue) + alSetError(pContext, AL_INVALID_VALUE); + else if((pBuffer=LookupBuffer(device->BufferMap, buffer)) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + case AL_FREQUENCY: + *plValue = pBuffer->Frequency; + break; + + case AL_BITS: + *plValue = BytesFromFmt(pBuffer->FmtType) * 8; + break; + + case AL_CHANNELS: + *plValue = ChannelsFromFmt(pBuffer->FmtChannels); + break; + + case AL_SIZE: + *plValue = pBuffer->size; + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum eParam, ALint* plValue1, ALint* plValue2, ALint* plValue3) +{ + ALCcontext *pContext; + ALCdevice *device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(!plValue1 || !plValue2 || !plValue3) + alSetError(pContext, AL_INVALID_VALUE); + else if(LookupBuffer(device->BufferMap, buffer) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum eParam, ALint* plValues) +{ + ALCcontext *pContext; + ALCdevice *device; + ALbuffer *ALBuf; + + pContext = GetContextSuspended(); + if(!pContext) return; + + device = pContext->Device; + if(!plValues) + alSetError(pContext, AL_INVALID_VALUE); + else if((ALBuf=LookupBuffer(device->BufferMap, buffer)) == NULL) + alSetError(pContext, AL_INVALID_NAME); + else + { + switch(eParam) + { + case AL_FREQUENCY: + case AL_BITS: + case AL_CHANNELS: + case AL_SIZE: + alGetBufferi(buffer, eParam, plValues); + break; + + case AL_LOOP_POINTS_SOFT: + plValues[0] = ALBuf->LoopStart; + plValues[1] = ALBuf->LoopEnd; + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + + ProcessContext(pContext); +} + + +typedef ALubyte ALmulaw; + +static __inline ALshort DecodeMuLaw(ALmulaw val) +{ return muLawDecompressionTable[val]; } + +static ALmulaw EncodeMuLaw(ALshort val) +{ + ALint mant, exp, sign; + + sign = (val>>8) & 0x80; + if(sign) + { + /* -32768 doesn't properly negate on a short; it results in itself. + * So clamp to -32767 */ + val = max(val, -32767); + val = -val; + } + + val = min(val, muLawClip); + val += muLawBias; + + exp = muLawCompressTable[(val>>7) & 0xff]; + mant = (val >> (exp+3)) & 0x0f; + + return ~(sign | (exp<<4) | mant); +} + +static void DecodeIMA4Block(ALshort *dst, const ALubyte *src, ALint numchans) +{ + ALint sample[MAXCHANNELS], index[MAXCHANNELS]; + ALuint code[MAXCHANNELS]; + ALsizei j,k,c; + + for(c = 0;c < numchans;c++) + { + sample[c] = *(src++); + sample[c] |= *(src++) << 8; + sample[c] = (sample[c]^0x8000) - 32768; + index[c] = *(src++); + index[c] |= *(src++) << 8; + index[c] = (index[c]^0x8000) - 32768; + + index[c] = max(0, index[c]); + index[c] = min(index[c], 88); + + dst[c] = sample[c]; + } + + j = 1; + while(j < 65) + { + for(c = 0;c < numchans;c++) + { + code[c] = *(src++); + code[c] |= *(src++) << 8; + code[c] |= *(src++) << 16; + code[c] |= *(src++) << 24; + } + + for(k = 0;k < 8;k++,j++) + { + for(c = 0;c < numchans;c++) + { + int nibble = code[c]&0xf; + code[c] >>= 4; + + sample[c] += IMA4Codeword[nibble] * IMAStep_size[index[c]] / 8; + sample[c] = max(-32768, sample[c]); + sample[c] = min(sample[c], 32767); + + index[c] += IMA4Index_adjust[nibble]; + index[c] = max(0, index[c]); + index[c] = min(index[c], 88); + + dst[j*numchans + c] = sample[c]; + } + } + } +} + +static void EncodeIMA4Block(ALubyte *dst, const ALshort *src, ALint *sample, ALint *index, ALint numchans) +{ + ALsizei j,k,c; + + for(c = 0;c < numchans;c++) + { + int diff = src[c] - sample[c]; + int step = IMAStep_size[index[c]]; + int nibble; + + nibble = 0; + if(diff < 0) + { + nibble = 0x8; + diff = -diff; + } + + diff = min(step*2, diff); + nibble |= (diff*8/step - 1) / 2; + + sample[c] += IMA4Codeword[nibble] * step / 8; + sample[c] = max(-32768, sample[c]); + sample[c] = min(sample[c], 32767); + + index[c] += IMA4Index_adjust[nibble]; + index[c] = max(0, index[c]); + index[c] = min(index[c], 88); + + *(dst++) = sample[c] & 0xff; + *(dst++) = (sample[c]>>8) & 0xff; + *(dst++) = index[c] & 0xff; + *(dst++) = (index[c]>>8) & 0xff; + } + + j = 1; + while(j < 65) + { + for(c = 0;c < numchans;c++) + { + for(k = 0;k < 8;k++) + { + int diff = src[(j+k)*numchans + c] - sample[c]; + int step = IMAStep_size[index[c]]; + int nibble; + + nibble = 0; + if(diff < 0) + { + nibble = 0x8; + diff = -diff; + } + + diff = min(step*2, diff); + nibble |= (diff*8/step - 1) / 2; + + sample[c] += IMA4Codeword[nibble] * step / 8; + sample[c] = max(-32768, sample[c]); + sample[c] = min(sample[c], 32767); + + index[c] += IMA4Index_adjust[nibble]; + index[c] = max(0, index[c]); + index[c] = min(index[c], 88); + + if(!(k&1)) *dst = nibble; + else *(dst++) |= nibble<<4; + } + } + j += 8; + } +} + + +static __inline ALbyte Conv_ALbyte_ALbyte(ALbyte val) +{ return val; } +static __inline ALbyte Conv_ALbyte_ALubyte(ALubyte val) +{ return val-128; } +static __inline ALbyte Conv_ALbyte_ALshort(ALshort val) +{ return val>>8; } +static __inline ALbyte Conv_ALbyte_ALushort(ALushort val) +{ return (val>>8)-128; } +static __inline ALbyte Conv_ALbyte_ALint(ALint val) +{ return val>>24; } +static __inline ALbyte Conv_ALbyte_ALuint(ALuint val) +{ return (val>>24)-128; } +static __inline ALbyte Conv_ALbyte_ALfp(ALfp val) +{ + if(val > int2ALfp(1)) return 127; + if(val < int2ALfp(-1)) return -128; + return ALfp2int(ALfpMult(val, int2ALfp(127))); +} +static __inline ALbyte Conv_ALbyte_ALdfp(ALdfp val) +{ + if(val > int2ALdfp(1)) return 127; + if(val < int2ALdfp(-1)) return -128; + return ALdfp2int(ALdfpMult(val, int2ALdfp(127))); +} +static __inline ALbyte Conv_ALbyte_ALmulaw(ALmulaw val) +{ return Conv_ALbyte_ALshort(DecodeMuLaw(val)); } + +static __inline ALubyte Conv_ALubyte_ALbyte(ALbyte val) +{ return val+128; } +static __inline ALubyte Conv_ALubyte_ALubyte(ALubyte val) +{ return val; } +static __inline ALubyte Conv_ALubyte_ALshort(ALshort val) +{ return (val>>8)+128; } +static __inline ALubyte Conv_ALubyte_ALushort(ALushort val) +{ return val>>8; } +static __inline ALubyte Conv_ALubyte_ALint(ALint val) +{ return (val>>24)+128; } +static __inline ALubyte Conv_ALubyte_ALuint(ALuint val) +{ return val>>24; } +static __inline ALubyte Conv_ALubyte_ALfp(ALfp val) +{ + if(val > int2ALfp(1)) return 255; + if(val < int2ALfp(-1)) return 0; + return ALfp2int(ALfpMult(val, int2ALfp(127))) + 128; +} +static __inline ALubyte Conv_ALubyte_ALdfp(ALdfp val) +{ + if(val > int2ALdfp(1)) return 255; + if(val < int2ALdfp(-1)) return 0; + return ALdfp2int(ALdfpMult(val, int2ALdfp(127))) + 128; +} +static __inline ALubyte Conv_ALubyte_ALmulaw(ALmulaw val) +{ return Conv_ALubyte_ALshort(DecodeMuLaw(val)); } + +static __inline ALshort Conv_ALshort_ALbyte(ALbyte val) +{ return val<<8; } +static __inline ALshort Conv_ALshort_ALubyte(ALubyte val) +{ return (val-128)<<8; } +static __inline ALshort Conv_ALshort_ALshort(ALshort val) +{ return val; } +static __inline ALshort Conv_ALshort_ALushort(ALushort val) +{ return val-32768; } +static __inline ALshort Conv_ALshort_ALint(ALint val) +{ return val>>16; } +static __inline ALshort Conv_ALshort_ALuint(ALuint val) +{ return (val>>16)-32768; } +static __inline ALshort Conv_ALshort_ALfp(ALfp val) +{ + if(val > int2ALfp(1)) return 32767; + if(val < int2ALfp(-1)) return -32768; + return ALfp2int(ALfpMult(val, int2ALfp(32767))); +} +static __inline ALshort Conv_ALshort_ALdfp(ALdfp val) +{ + if(val > int2ALdfp(1)) return 32767; + if(val < int2ALdfp(-1)) return -32768; + return ALdfp2int(ALdfpMult(val, int2ALdfp(32767))); +} +static __inline ALshort Conv_ALshort_ALmulaw(ALmulaw val) +{ return Conv_ALshort_ALshort(DecodeMuLaw(val)); } + +static __inline ALushort Conv_ALushort_ALbyte(ALbyte val) +{ return (val+128)<<8; } +static __inline ALushort Conv_ALushort_ALubyte(ALubyte val) +{ return val<<8; } +static __inline ALushort Conv_ALushort_ALshort(ALshort val) +{ return val+32768; } +static __inline ALushort Conv_ALushort_ALushort(ALushort val) +{ return val; } +static __inline ALushort Conv_ALushort_ALint(ALint val) +{ return (val>>16)+32768; } +static __inline ALushort Conv_ALushort_ALuint(ALuint val) +{ return val>>16; } +static __inline ALushort Conv_ALushort_ALfp(ALfp val) +{ + if(val > int2ALfp(1)) return 65535; + if(val < int2ALfp(-1)) return 0; + return ALfp2int(ALfpMult(val, int2ALfp(32767))) + 32768; +} +static __inline ALushort Conv_ALushort_ALdfp(ALdfp val) +{ + if(val > int2ALdfp(1)) return 65535; + if(val < int2ALdfp(-1)) return 0; + return ALdfp2int(ALdfpMult(val, int2ALdfp(32767))) + 32768; +} +static __inline ALushort Conv_ALushort_ALmulaw(ALmulaw val) +{ return Conv_ALushort_ALshort(DecodeMuLaw(val)); } + +static __inline ALint Conv_ALint_ALbyte(ALbyte val) +{ return val<<24; } +static __inline ALint Conv_ALint_ALubyte(ALubyte val) +{ return (val-128)<<24; } +static __inline ALint Conv_ALint_ALshort(ALshort val) +{ return val<<16; } +static __inline ALint Conv_ALint_ALushort(ALushort val) +{ return (val-32768)<<16; } +static __inline ALint Conv_ALint_ALint(ALint val) +{ return val; } +static __inline ALint Conv_ALint_ALuint(ALuint val) +{ return val-2147483648u; } +static __inline ALint Conv_ALint_ALfp(ALfp val) +{ + if(val > int2ALfp(1)) return 2147483647; + if(val < int2ALfp(-1)) return -2147483647-1; + return ALfp2int(ALfpMult(val, int2ALfp(2147483647))); +} +static __inline ALint Conv_ALint_ALdfp(ALdfp val) +{ + if(val > int2ALdfp(1)) return 2147483647; + if(val < int2ALdfp(-1)) return -2147483647-1; + return ALdfp2int(ALdfpMult(val, int2ALdfp(2147483647))); +} +static __inline ALint Conv_ALint_ALmulaw(ALmulaw val) +{ return Conv_ALint_ALshort(DecodeMuLaw(val)); } + +static __inline ALuint Conv_ALuint_ALbyte(ALbyte val) +{ return (val+128)<<24; } +static __inline ALuint Conv_ALuint_ALubyte(ALubyte val) +{ return val<<24; } +static __inline ALuint Conv_ALuint_ALshort(ALshort val) +{ return (val+32768)<<16; } +static __inline ALuint Conv_ALuint_ALushort(ALushort val) +{ return val<<16; } +static __inline ALuint Conv_ALuint_ALint(ALint val) +{ return val+2147483648u; } +static __inline ALuint Conv_ALuint_ALuint(ALuint val) +{ return val; } +static __inline ALuint Conv_ALuint_ALfp(ALfp val) +{ + if(val > int2ALfp(1)) return 4294967295u; + if(val < int2ALfp(-1)) return 0; + return ALfp2int(ALfpMult(val, int2ALfp(2147483647))) + 2147483648u; +} +static __inline ALuint Conv_ALuint_ALdfp(ALdfp val) +{ + if(val > int2ALdfp(1)) return 4294967295u; + if(val < int2ALdfp(-1)) return 0; + return ALdfp2int(ALdfpMult(val, int2ALdfp(2147483647))) + 2147483648u; +} +static __inline ALuint Conv_ALuint_ALmulaw(ALmulaw val) +{ return Conv_ALuint_ALshort(DecodeMuLaw(val)); } + +// FIXME(apportable) make this more efficient with shifts for integer input +static __inline ALfp Conv_ALfp_ALbyte(ALbyte val) +{ return float2ALfp(val * (1.0f/127.0f)); } +static __inline ALfp Conv_ALfp_ALubyte(ALubyte val) +{ return float2ALfp((val-128) * (1.0f/127.0f)); } +static __inline ALfp Conv_ALfp_ALshort(ALshort val) +{ return float2ALfp(val * (1.0f/32767.0f)); } +static __inline ALfp Conv_ALfp_ALushort(ALushort val) +{ return float2ALfp((val-32768) * (1.0f/32767.0f)); } +static __inline ALfp Conv_ALfp_ALint(ALint val) +{ return float2ALfp(val * (1.0/2147483647.0)); } +static __inline ALfp Conv_ALfp_ALuint(ALuint val) +{ return float2ALfp((ALint)(val-2147483648u) * (1.0/2147483647.0)); } +static __inline ALfp Conv_ALfp_ALfp(ALfp val) +{ return val; } +static __inline ALfp Conv_ALfp_ALdfp(ALdfp val) +{ return (ALfp)val; } +static __inline ALfp Conv_ALfp_ALmulaw(ALmulaw val) +{ return Conv_ALfp_ALshort(DecodeMuLaw(val)); } + +// FIXME replace with shifts for integer args +static __inline ALdfp Conv_ALdfp_ALbyte(ALbyte val) +{ return double2ALdfp(val * (1.0/127.0)); } +static __inline ALdfp Conv_ALdfp_ALubyte(ALubyte val) +{ return double2ALdfp((val-128) * (1.0/127.0)); } +static __inline ALdfp Conv_ALdfp_ALshort(ALshort val) +{ return double2ALdfp(val * (1.0/32767.0)); } +static __inline ALdfp Conv_ALdfp_ALushort(ALushort val) +{ return double2ALdfp((val-32768) * (1.0/32767.0)); } +static __inline ALdfp Conv_ALdfp_ALint(ALint val) +{ return double2ALdfp(val * (1.0/2147483647.0)); } +static __inline ALdfp Conv_ALdfp_ALuint(ALuint val) +{ return double2ALdfp((ALint)(val-2147483648u) * (1.0/2147483647.0)); } +static __inline ALdfp Conv_ALdfp_ALfp(ALfp val) +{ return (ALdfp)val; } +static __inline ALdfp Conv_ALdfp_ALdfp(ALdfp val) +{ return val; } +static __inline ALdfp Conv_ALdfp_ALmulaw(ALmulaw val) +{ return Conv_ALdfp_ALshort(DecodeMuLaw(val)); } + +#define DECL_TEMPLATE(T) \ +static __inline ALmulaw Conv_ALmulaw_##T(T val) \ +{ return EncodeMuLaw(Conv_ALshort_##T(val)); } + +DECL_TEMPLATE(ALbyte) +DECL_TEMPLATE(ALubyte) +DECL_TEMPLATE(ALshort) +DECL_TEMPLATE(ALushort) +DECL_TEMPLATE(ALint) +DECL_TEMPLATE(ALuint) +DECL_TEMPLATE(ALfp) +DECL_TEMPLATE(ALdfp) +static __inline ALmulaw Conv_ALmulaw_ALmulaw(ALmulaw val) +{ return val; } + +#undef DECL_TEMPLATE + +#define DECL_TEMPLATE(T1, T2) \ +static void Convert_##T1##_##T2(T1 *dst, const T2 *src, ALuint len) \ +{ \ + ALuint i; \ + for(i = 0;i < len;i++) \ + *(dst++) = Conv_##T1##_##T2(*(src++)); \ +} + +DECL_TEMPLATE(ALbyte, ALbyte) +DECL_TEMPLATE(ALbyte, ALubyte) +DECL_TEMPLATE(ALbyte, ALshort) +DECL_TEMPLATE(ALbyte, ALushort) +DECL_TEMPLATE(ALbyte, ALint) +DECL_TEMPLATE(ALbyte, ALuint) +DECL_TEMPLATE(ALbyte, ALfp) +DECL_TEMPLATE(ALbyte, ALdfp) +DECL_TEMPLATE(ALbyte, ALmulaw) + +DECL_TEMPLATE(ALubyte, ALbyte) +DECL_TEMPLATE(ALubyte, ALubyte) +DECL_TEMPLATE(ALubyte, ALshort) +DECL_TEMPLATE(ALubyte, ALushort) +DECL_TEMPLATE(ALubyte, ALint) +DECL_TEMPLATE(ALubyte, ALuint) +DECL_TEMPLATE(ALubyte, ALfp) +DECL_TEMPLATE(ALubyte, ALdfp) +DECL_TEMPLATE(ALubyte, ALmulaw) + +DECL_TEMPLATE(ALshort, ALbyte) +DECL_TEMPLATE(ALshort, ALubyte) +DECL_TEMPLATE(ALshort, ALshort) +DECL_TEMPLATE(ALshort, ALushort) +DECL_TEMPLATE(ALshort, ALint) +DECL_TEMPLATE(ALshort, ALuint) +DECL_TEMPLATE(ALshort, ALfp) +DECL_TEMPLATE(ALshort, ALdfp) +DECL_TEMPLATE(ALshort, ALmulaw) + +DECL_TEMPLATE(ALushort, ALbyte) +DECL_TEMPLATE(ALushort, ALubyte) +DECL_TEMPLATE(ALushort, ALshort) +DECL_TEMPLATE(ALushort, ALushort) +DECL_TEMPLATE(ALushort, ALint) +DECL_TEMPLATE(ALushort, ALuint) +DECL_TEMPLATE(ALushort, ALfp) +DECL_TEMPLATE(ALushort, ALdfp) +DECL_TEMPLATE(ALushort, ALmulaw) + +DECL_TEMPLATE(ALint, ALbyte) +DECL_TEMPLATE(ALint, ALubyte) +DECL_TEMPLATE(ALint, ALshort) +DECL_TEMPLATE(ALint, ALushort) +DECL_TEMPLATE(ALint, ALint) +DECL_TEMPLATE(ALint, ALuint) +DECL_TEMPLATE(ALint, ALfp) +DECL_TEMPLATE(ALint, ALdfp) +DECL_TEMPLATE(ALint, ALmulaw) + +DECL_TEMPLATE(ALuint, ALbyte) +DECL_TEMPLATE(ALuint, ALubyte) +DECL_TEMPLATE(ALuint, ALshort) +DECL_TEMPLATE(ALuint, ALushort) +DECL_TEMPLATE(ALuint, ALint) +DECL_TEMPLATE(ALuint, ALuint) +DECL_TEMPLATE(ALuint, ALfp) +DECL_TEMPLATE(ALuint, ALdfp) +DECL_TEMPLATE(ALuint, ALmulaw) + +DECL_TEMPLATE(ALfp, ALbyte) +DECL_TEMPLATE(ALfp, ALubyte) +DECL_TEMPLATE(ALfp, ALshort) +DECL_TEMPLATE(ALfp, ALushort) +DECL_TEMPLATE(ALfp, ALint) +DECL_TEMPLATE(ALfp, ALuint) +DECL_TEMPLATE(ALfp, ALfp) +DECL_TEMPLATE(ALfp, ALdfp) +DECL_TEMPLATE(ALfp, ALmulaw) + +DECL_TEMPLATE(ALdfp, ALbyte) +DECL_TEMPLATE(ALdfp, ALubyte) +DECL_TEMPLATE(ALdfp, ALshort) +DECL_TEMPLATE(ALdfp, ALushort) +DECL_TEMPLATE(ALdfp, ALint) +DECL_TEMPLATE(ALdfp, ALuint) +DECL_TEMPLATE(ALdfp, ALfp) +DECL_TEMPLATE(ALdfp, ALdfp) +DECL_TEMPLATE(ALdfp, ALmulaw) + +DECL_TEMPLATE(ALmulaw, ALbyte) +DECL_TEMPLATE(ALmulaw, ALubyte) +DECL_TEMPLATE(ALmulaw, ALshort) +DECL_TEMPLATE(ALmulaw, ALushort) +DECL_TEMPLATE(ALmulaw, ALint) +DECL_TEMPLATE(ALmulaw, ALuint) +DECL_TEMPLATE(ALmulaw, ALfp) +DECL_TEMPLATE(ALmulaw, ALdfp) +DECL_TEMPLATE(ALmulaw, ALmulaw) + +#undef DECL_TEMPLATE + +#define DECL_TEMPLATE(T) \ +static void Convert_##T##_IMA4(T *dst, const ALubyte *src, ALuint numchans, \ + ALuint numblocks) \ +{ \ + ALuint i, j; \ + ALshort tmp[65*MAXCHANNELS]; /* Max samples an IMA4 frame can be */ \ + for(i = 0;i < numblocks;i++) \ + { \ + DecodeIMA4Block(tmp, src, numchans); \ + src += 36*numchans; \ + for(j = 0;j < 65*numchans;j++) \ + *(dst++) = Conv_##T##_ALshort(tmp[j]); \ + } \ +} + +DECL_TEMPLATE(ALbyte) +DECL_TEMPLATE(ALubyte) +DECL_TEMPLATE(ALshort) +DECL_TEMPLATE(ALushort) +DECL_TEMPLATE(ALint) +DECL_TEMPLATE(ALuint) +DECL_TEMPLATE(ALfp) +DECL_TEMPLATE(ALdfp) +DECL_TEMPLATE(ALmulaw) + +#undef DECL_TEMPLATE + +#define DECL_TEMPLATE(T) \ +static void Convert_IMA4_##T(ALubyte *dst, const T *src, ALuint numchans, \ + ALuint numblocks) \ +{ \ + ALuint i, j; \ + ALshort tmp[65*MAXCHANNELS]; /* Max samples an IMA4 frame can be */ \ + ALint sample[MAXCHANNELS] = {0,0,0,0,0,0,0,0}; \ + ALint index[MAXCHANNELS] = {0,0,0,0,0,0,0,0}; \ + for(i = 0;i < numblocks;i++) \ + { \ + for(j = 0;j < 65*numchans;j++) \ + tmp[j] = Conv_ALshort_##T(*(src++)); \ + EncodeIMA4Block(dst, tmp, sample, index, numchans); \ + dst += 36*numchans; \ + } \ +} + +DECL_TEMPLATE(ALbyte) +DECL_TEMPLATE(ALubyte) +DECL_TEMPLATE(ALshort) +DECL_TEMPLATE(ALushort) +DECL_TEMPLATE(ALint) +DECL_TEMPLATE(ALuint) +DECL_TEMPLATE(ALfp) +DECL_TEMPLATE(ALdfp) +DECL_TEMPLATE(ALmulaw) + +#undef DECL_TEMPLATE + +static void Convert_IMA4_IMA4(ALubyte *dst, const ALubyte *src, ALuint numchans, + ALuint numblocks) +{ + memcpy(dst, src, numblocks*36*numchans); +} + +#define DECL_TEMPLATE(T) \ +static void Convert_##T(T *dst, const ALvoid *src, enum UserFmtType srcType, \ + ALsizei len) \ +{ \ + switch(srcType) \ + { \ + case UserFmtByte: \ + Convert_##T##_ALbyte(dst, src, len); \ + break; \ + case UserFmtUByte: \ + Convert_##T##_ALubyte(dst, src, len); \ + break; \ + case UserFmtShort: \ + Convert_##T##_ALshort(dst, src, len); \ + break; \ + case UserFmtUShort: \ + Convert_##T##_ALushort(dst, src, len); \ + break; \ + case UserFmtInt: \ + Convert_##T##_ALint(dst, src, len); \ + break; \ + case UserFmtUInt: \ + Convert_##T##_ALuint(dst, src, len); \ + break; \ + case UserFmtFloat: \ + Convert_##T##_ALfp(dst, src, len); \ + break; \ + case UserFmtDouble: \ + Convert_##T##_ALdfp(dst, src, len); \ + break; \ + case UserFmtMulaw: \ + Convert_##T##_ALmulaw(dst, src, len); \ + break; \ + case UserFmtIMA4: \ + break; /* not handled here */ \ + } \ +} + +DECL_TEMPLATE(ALbyte) +DECL_TEMPLATE(ALubyte) +DECL_TEMPLATE(ALshort) +DECL_TEMPLATE(ALushort) +DECL_TEMPLATE(ALint) +DECL_TEMPLATE(ALuint) +DECL_TEMPLATE(ALfp) +DECL_TEMPLATE(ALdfp) +DECL_TEMPLATE(ALmulaw) + +#undef DECL_TEMPLATE + +static void Convert_IMA4(ALubyte *dst, const ALvoid *src, enum UserFmtType srcType, + ALint chans, ALsizei len) +{ + switch(srcType) + { + case UserFmtByte: + Convert_IMA4_ALbyte(dst, src, chans, len); + break; + case UserFmtUByte: + Convert_IMA4_ALubyte(dst, src, chans, len); + break; + case UserFmtShort: + Convert_IMA4_ALshort(dst, src, chans, len); + break; + case UserFmtUShort: + Convert_IMA4_ALushort(dst, src, chans, len); + break; + case UserFmtInt: + Convert_IMA4_ALint(dst, src, chans, len); + break; + case UserFmtUInt: + Convert_IMA4_ALuint(dst, src, chans, len); + break; + case UserFmtFloat: + Convert_IMA4_ALfp(dst, src, chans, len); + break; + case UserFmtDouble: + Convert_IMA4_ALdfp(dst, src, chans, len); + break; + case UserFmtMulaw: + Convert_IMA4_ALmulaw(dst, src, chans, len); + break; + case UserFmtIMA4: + Convert_IMA4_IMA4(dst, src, chans, len); + break; + } +} + + +static void ConvertData(ALvoid *dst, enum FmtType dstType, const ALvoid *src, enum UserFmtType srcType, ALsizei len) +{ + switch(dstType) + { + (void)Convert_ALbyte; + case FmtUByte: + Convert_ALubyte(dst, src, srcType, len); + break; + case FmtShort: + Convert_ALshort(dst, src, srcType, len); + break; + (void)Convert_ALushort; + (void)Convert_ALint; + (void)Convert_ALuint; + case FmtFloat: + Convert_ALfp(dst, src, srcType, len); + break; + (void)Convert_ALdfp; + (void)Convert_ALmulaw; + (void)Convert_IMA4; + } +} + +static void ConvertDataIMA4(ALvoid *dst, enum FmtType dstType, const ALvoid *src, ALint chans, ALsizei len) +{ + switch(dstType) + { + (void)Convert_ALbyte_IMA4; + case FmtUByte: + Convert_ALubyte_IMA4(dst, src, chans, len); + break; + case FmtShort: + Convert_ALshort_IMA4(dst, src, chans, len); + break; + (void)Convert_ALushort_IMA4; + (void)Convert_ALint_IMA4; + (void)Convert_ALuint_IMA4; + case FmtFloat: + Convert_ALfp_IMA4(dst, src, chans, len); + break; + (void)Convert_ALdfp_IMA4; + (void)Convert_ALmulaw_IMA4; + } +} + + +/* + * LoadData + * + * Loads the specified data into the buffer, using the specified formats. + * Currently, the new format must have the same channel configuration as the + * original format. + */ +static ALenum LoadData(ALbuffer *ALBuf, ALuint freq, ALenum NewFormat, ALsizei size, enum UserFmtChannels SrcChannels, enum UserFmtType SrcType, const ALvoid *data) +{ + ALuint NewChannels, NewBytes; + enum FmtChannels DstChannels; + enum FmtType DstType; + ALuint64 newsize; + ALvoid *temp; + + DecomposeFormat(NewFormat, &DstChannels, &DstType); + NewChannels = ChannelsFromFmt(DstChannels); + NewBytes = BytesFromFmt(DstType); + + assert(SrcChannels == DstChannels); + + if(SrcType == UserFmtIMA4) + { + ALuint OrigChannels = ChannelsFromUserFmt(SrcChannels); + + /* Here is where things vary: + * nVidia and Apple use 64+1 sample frames per block -> block_size=36 bytes per channel + * Most PC sound software uses 2040+1 sample frames per block -> block_size=1024 bytes per channel + */ + if((size%(36*OrigChannels)) != 0) + return AL_INVALID_VALUE; + + newsize = size / 36; + newsize *= 65; + newsize *= NewBytes; + if(newsize > INT_MAX) + return AL_OUT_OF_MEMORY; + + temp = realloc(ALBuf->data, newsize); + if(!temp && newsize) return AL_OUT_OF_MEMORY; + ALBuf->data = temp; + ALBuf->size = newsize; + + if(data != NULL) + ConvertDataIMA4(ALBuf->data, DstType, data, OrigChannels, + newsize/(65*NewChannels*NewBytes)); + + ALBuf->OriginalChannels = SrcChannels; + ALBuf->OriginalType = SrcType; + ALBuf->OriginalSize = size; + ALBuf->OriginalAlign = 36 * OrigChannels; + } + else + { + ALuint OrigBytes = BytesFromUserFmt(SrcType); + ALuint OrigChannels = ChannelsFromUserFmt(SrcChannels); + + if((size%(OrigBytes*OrigChannels)) != 0) + return AL_INVALID_VALUE; + + newsize = size / OrigBytes; + newsize *= NewBytes; + if(newsize > INT_MAX) + return AL_OUT_OF_MEMORY; + + temp = realloc(ALBuf->data, newsize); + if(!temp && newsize) return AL_OUT_OF_MEMORY; + ALBuf->data = temp; + ALBuf->size = newsize; + + if(data != NULL) + ConvertData(ALBuf->data, DstType, data, SrcType, newsize/NewBytes); + + ALBuf->OriginalChannels = SrcChannels; + ALBuf->OriginalType = SrcType; + ALBuf->OriginalSize = size; + ALBuf->OriginalAlign = OrigBytes * OrigChannels; + } + + ALBuf->Frequency = freq; + ALBuf->FmtChannels = DstChannels; + ALBuf->FmtType = DstType; + + ALBuf->LoopStart = 0; + ALBuf->LoopEnd = newsize / NewChannels / NewBytes; + + return AL_NO_ERROR; +} + + +ALuint BytesFromUserFmt(enum UserFmtType type) +{ + switch(type) + { + case UserFmtByte: return sizeof(ALbyte); + case UserFmtUByte: return sizeof(ALubyte); + case UserFmtShort: return sizeof(ALshort); + case UserFmtUShort: return sizeof(ALushort); + case UserFmtInt: return sizeof(ALint); + case UserFmtUInt: return sizeof(ALuint); + case UserFmtFloat: return sizeof(ALfp); + case UserFmtDouble: return sizeof(ALdfp); + case UserFmtMulaw: return sizeof(ALubyte); + case UserFmtIMA4: break; /* not handled here */ + } + return 0; +} +ALuint ChannelsFromUserFmt(enum UserFmtChannels chans) +{ + switch(chans) + { + case UserFmtMono: return 1; + case UserFmtStereo: return 2; + case UserFmtRear: return 2; + case UserFmtQuad: return 4; + case UserFmtX51: return 6; + case UserFmtX61: return 7; + case UserFmtX71: return 8; + } + return 0; +} +ALboolean DecomposeUserFormat(ALenum format, enum UserFmtChannels *chans, + enum UserFmtType *type) +{ + switch(format) + { + case AL_FORMAT_MONO8: + *chans = UserFmtMono; + *type = UserFmtUByte; + return AL_TRUE; + case AL_FORMAT_MONO16: + *chans = UserFmtMono; + *type = UserFmtShort; + return AL_TRUE; + case AL_FORMAT_MONO_FLOAT32: + *chans = UserFmtMono; + *type = UserFmtFloat; + return AL_TRUE; + case AL_FORMAT_MONO_DOUBLE_EXT: + *chans = UserFmtMono; + *type = UserFmtDouble; + return AL_TRUE; + case AL_FORMAT_MONO_IMA4: + *chans = UserFmtMono; + *type = UserFmtIMA4; + return AL_TRUE; + case AL_FORMAT_STEREO8: + *chans = UserFmtStereo; + *type = UserFmtUByte; + return AL_TRUE; + case AL_FORMAT_STEREO16: + *chans = UserFmtStereo; + *type = UserFmtShort; + return AL_TRUE; + case AL_FORMAT_STEREO_FLOAT32: + *chans = UserFmtStereo; + *type = UserFmtFloat; + return AL_TRUE; + case AL_FORMAT_STEREO_DOUBLE_EXT: + *chans = UserFmtStereo; + *type = UserFmtDouble; + return AL_TRUE; + case AL_FORMAT_STEREO_IMA4: + *chans = UserFmtStereo; + *type = UserFmtIMA4; + return AL_TRUE; + case AL_FORMAT_QUAD8_LOKI: + case AL_FORMAT_QUAD8: + *chans = UserFmtQuad; + *type = UserFmtUByte; + return AL_TRUE; + case AL_FORMAT_QUAD16_LOKI: + case AL_FORMAT_QUAD16: + *chans = UserFmtQuad; + *type = UserFmtShort; + return AL_TRUE; + case AL_FORMAT_QUAD32: + *chans = UserFmtQuad; + *type = UserFmtFloat; + return AL_TRUE; + case AL_FORMAT_REAR8: + *chans = UserFmtRear; + *type = UserFmtUByte; + return AL_TRUE; + case AL_FORMAT_REAR16: + *chans = UserFmtRear; + *type = UserFmtShort; + return AL_TRUE; + case AL_FORMAT_REAR32: + *chans = UserFmtRear; + *type = UserFmtFloat; + return AL_TRUE; + case AL_FORMAT_51CHN8: + *chans = UserFmtX51; + *type = UserFmtUByte; + return AL_TRUE; + case AL_FORMAT_51CHN16: + *chans = UserFmtX51; + *type = UserFmtShort; + return AL_TRUE; + case AL_FORMAT_51CHN32: + *chans = UserFmtX51; + *type = UserFmtFloat; + return AL_TRUE; + case AL_FORMAT_61CHN8: + *chans = UserFmtX61; + *type = UserFmtUByte; + return AL_TRUE; + case AL_FORMAT_61CHN16: + *chans = UserFmtX61; + *type = UserFmtShort; + return AL_TRUE; + case AL_FORMAT_61CHN32: + *chans = UserFmtX61; + *type = UserFmtFloat; + return AL_TRUE; + case AL_FORMAT_71CHN8: + *chans = UserFmtX71; + *type = UserFmtUByte; + return AL_TRUE; + case AL_FORMAT_71CHN16: + *chans = UserFmtX71; + *type = UserFmtShort; + return AL_TRUE; + case AL_FORMAT_71CHN32: + *chans = UserFmtX71; + *type = UserFmtFloat; + return AL_TRUE; + case AL_FORMAT_MONO_MULAW: + *chans = UserFmtMono; + *type = UserFmtMulaw; + return AL_TRUE; + case AL_FORMAT_STEREO_MULAW: + *chans = UserFmtStereo; + *type = UserFmtMulaw; + return AL_TRUE; + case AL_FORMAT_QUAD_MULAW: + *chans = UserFmtQuad; + *type = UserFmtMulaw; + return AL_TRUE; + case AL_FORMAT_REAR_MULAW: + *chans = UserFmtRear; + *type = UserFmtMulaw; + return AL_TRUE; + case AL_FORMAT_51CHN_MULAW: + *chans = UserFmtX51; + *type = UserFmtMulaw; + return AL_TRUE; + case AL_FORMAT_61CHN_MULAW: + *chans = UserFmtX61; + *type = UserFmtMulaw; + return AL_TRUE; + case AL_FORMAT_71CHN_MULAW: + *chans = UserFmtX71; + *type = UserFmtMulaw; + return AL_TRUE; + } + return AL_FALSE; +} + +ALuint BytesFromFmt(enum FmtType type) +{ + switch(type) + { + case FmtUByte: return sizeof(ALubyte); + case FmtShort: return sizeof(ALshort); + case FmtFloat: return sizeof(ALfp); + } + return 0; +} +ALuint ChannelsFromFmt(enum FmtChannels chans) +{ + switch(chans) + { + case FmtMono: return 1; + case FmtStereo: return 2; + case FmtRear: return 2; + case FmtQuad: return 4; + case FmtX51: return 6; + case FmtX61: return 7; + case FmtX71: return 8; + } + return 0; +} +ALboolean DecomposeFormat(ALenum format, enum FmtChannels *chans, enum FmtType *type) +{ + switch(format) + { + case AL_FORMAT_MONO8: + *chans = FmtMono; + *type = FmtUByte; + return AL_TRUE; + case AL_FORMAT_MONO16: + *chans = FmtMono; + *type = FmtShort; + return AL_TRUE; + case AL_FORMAT_MONO_FLOAT32: + *chans = FmtMono; + *type = FmtFloat; + return AL_TRUE; + case AL_FORMAT_STEREO8: + *chans = FmtStereo; + *type = FmtUByte; + return AL_TRUE; + case AL_FORMAT_STEREO16: + *chans = FmtStereo; + *type = FmtShort; + return AL_TRUE; + case AL_FORMAT_STEREO_FLOAT32: + *chans = FmtStereo; + *type = FmtFloat; + return AL_TRUE; + case AL_FORMAT_QUAD8_LOKI: + case AL_FORMAT_QUAD8: + *chans = FmtQuad; + *type = FmtUByte; + return AL_TRUE; + case AL_FORMAT_QUAD16_LOKI: + case AL_FORMAT_QUAD16: + *chans = FmtQuad; + *type = FmtShort; + return AL_TRUE; + case AL_FORMAT_QUAD32: + *chans = FmtQuad; + *type = FmtFloat; + return AL_TRUE; + case AL_FORMAT_REAR8: + *chans = FmtRear; + *type = FmtUByte; + return AL_TRUE; + case AL_FORMAT_REAR16: + *chans = FmtRear; + *type = FmtShort; + return AL_TRUE; + case AL_FORMAT_REAR32: + *chans = FmtRear; + *type = FmtFloat; + return AL_TRUE; + case AL_FORMAT_51CHN8: + *chans = FmtX51; + *type = FmtUByte; + return AL_TRUE; + case AL_FORMAT_51CHN16: + *chans = FmtX51; + *type = FmtShort; + return AL_TRUE; + case AL_FORMAT_51CHN32: + *chans = FmtX51; + *type = FmtFloat; + return AL_TRUE; + case AL_FORMAT_61CHN8: + *chans = FmtX61; + *type = FmtUByte; + return AL_TRUE; + case AL_FORMAT_61CHN16: + *chans = FmtX61; + *type = FmtShort; + return AL_TRUE; + case AL_FORMAT_61CHN32: + *chans = FmtX61; + *type = FmtFloat; + return AL_TRUE; + case AL_FORMAT_71CHN8: + *chans = FmtX71; + *type = FmtUByte; + return AL_TRUE; + case AL_FORMAT_71CHN16: + *chans = FmtX71; + *type = FmtShort; + return AL_TRUE; + case AL_FORMAT_71CHN32: + *chans = FmtX71; + *type = FmtFloat; + return AL_TRUE; + } + return AL_FALSE; +} + + +/* + * ReleaseALBuffers() + * + * INTERNAL: Called to destroy any buffers that still exist on the device + */ +ALvoid ReleaseALBuffers(ALCdevice *device) +{ + ALsizei i; + for(i = 0;i < device->BufferMap.size;i++) + { + ALbuffer *temp = device->BufferMap.array[i].value; + device->BufferMap.array[i].value = NULL; + + free(temp->data); + + ALTHUNK_REMOVEENTRY(temp->buffer); + memset(temp, 0, sizeof(ALbuffer)); + free(temp); + } +} diff --git a/jni/OpenAL/OpenAL32/alDatabuffer.c b/jni/OpenAL/OpenAL32/alDatabuffer.c new file mode 100644 index 0000000..cbe65a0 --- /dev/null +++ b/jni/OpenAL/OpenAL32/alDatabuffer.c @@ -0,0 +1,648 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" +#include "alError.h" +#include "alDatabuffer.h" +#include "alThunk.h" + + +#define LookupDatabuffer(m, k) ((ALdatabuffer*)LookupUIntMapKey(&(m), (k))) + +/* +* alGenDatabuffersEXT(ALsizei n, ALuint *puiBuffers) +* +* Generates n AL Databuffers, and stores the Databuffers Names in the array pointed to by puiBuffers +*/ +AL_API ALvoid AL_APIENTRY alGenDatabuffersEXT(ALsizei n,ALuint *puiBuffers) +{ + ALCcontext *Context; + ALsizei i=0; + + Context = GetContextSuspended(); + if(!Context) return; + + /* Check that we are actually generation some Databuffers */ + if(n < 0 || IsBadWritePtr((void*)puiBuffers, n * sizeof(ALuint))) + alSetError(Context, AL_INVALID_VALUE); + else + { + ALCdevice *device = Context->Device; + ALenum err; + + /* Create all the new Databuffers */ + while(i < n) + { + ALdatabuffer *buffer = calloc(1, sizeof(ALdatabuffer)); + if(!buffer) + { + alSetError(Context, AL_OUT_OF_MEMORY); + alDeleteDatabuffersEXT(i, puiBuffers); + break; + } + + buffer->databuffer = ALTHUNK_ADDENTRY(buffer); + err = InsertUIntMapEntry(&device->DatabufferMap, + buffer->databuffer, buffer); + if(err != AL_NO_ERROR) + { + ALTHUNK_REMOVEENTRY(buffer->databuffer); + memset(buffer, 0, sizeof(ALdatabuffer)); + free(buffer); + + alSetError(Context, err); + alDeleteDatabuffersEXT(i, puiBuffers); + break; + } + puiBuffers[i++] = buffer->databuffer; + + buffer->state = UNMAPPED; + } + } + + ProcessContext(Context); +} + +/* +* alDatabeleteBuffersEXT(ALsizei n, ALuint *puiBuffers) +* +* Deletes the n AL Databuffers pointed to by puiBuffers +*/ +AL_API ALvoid AL_APIENTRY alDeleteDatabuffersEXT(ALsizei n, const ALuint *buffers) +{ + ALCcontext *Context; + ALCdevice *device; + ALdatabuffer *ALBuf; + ALboolean Failed; + ALsizei i; + + Context = GetContextSuspended(); + if(!Context) return; + + /* Check we are actually Deleting some Databuffers */ + Failed = AL_TRUE; + device = Context->Device; + if(n < 0) + alSetError(Context, AL_INVALID_VALUE); + else + { + Failed = AL_FALSE; + /* Check that all the databuffers are valid and can actually be + * deleted */ + for(i = 0;i < n;i++) + { + if(!buffers[i]) + continue; + + /* Check for valid Buffer ID */ + if((ALBuf=LookupDatabuffer(device->DatabufferMap, buffers[i])) == NULL) + { + /* Invalid Databuffer */ + alSetError(Context, AL_INVALID_NAME); + Failed = AL_TRUE; + break; + } + else if(ALBuf->state != UNMAPPED) + { + /* Databuffer still in use, cannot be deleted */ + alSetError(Context, AL_INVALID_OPERATION); + Failed = AL_TRUE; + break; + } + } + } + + /* If all the Databuffers were valid (and unmapped), then we can delete them */ + if(!Failed) + { + for(i = 0;i < n;i++) + { + if((ALBuf=LookupDatabuffer(device->DatabufferMap, buffers[i])) == NULL) + continue; + + if(ALBuf == Context->SampleSource) + Context->SampleSource = NULL; + if(ALBuf == Context->SampleSink) + Context->SampleSink = NULL; + + // Release the memory used to store audio data + free(ALBuf->data); + + // Release buffer structure + RemoveUIntMapKey(&device->DatabufferMap, ALBuf->databuffer); + ALTHUNK_REMOVEENTRY(ALBuf->databuffer); + + memset(ALBuf, 0, sizeof(ALdatabuffer)); + free(ALBuf); + } + } + + ProcessContext(Context); +} + +/* +* alIsDatabufferEXT(ALuint uiBuffer) +* +* Checks if ulBuffer is a valid Databuffer Name +*/ +AL_API ALboolean AL_APIENTRY alIsDatabufferEXT(ALuint buffer) +{ + ALCcontext *Context; + ALboolean result; + ALCdevice *device; + + Context = GetContextSuspended(); + if(!Context) return AL_FALSE; + + device = Context->Device; + result = ((!buffer || LookupDatabuffer(device->DatabufferMap, buffer)) ? + AL_TRUE : AL_FALSE); + + ProcessContext(Context); + + return result; +} + +/* +* alDatabufferDataEXT(ALuint buffer,ALvoid *data,ALsizei size,ALenum usage) +* +* Fill databuffer with data +*/ +AL_API ALvoid AL_APIENTRY alDatabufferDataEXT(ALuint buffer,const ALvoid *data,ALsizeiptrEXT size,ALenum usage) +{ + ALCcontext *Context; + ALdatabuffer *ALBuf; + ALCdevice *Device; + ALvoid *temp; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALBuf=LookupDatabuffer(Device->DatabufferMap, buffer)) != NULL) + { + if(ALBuf->state == UNMAPPED) + { + if(usage == AL_STREAM_WRITE_EXT || usage == AL_STREAM_READ_EXT || + usage == AL_STREAM_COPY_EXT || usage == AL_STATIC_WRITE_EXT || + usage == AL_STATIC_READ_EXT || usage == AL_STATIC_COPY_EXT || + usage == AL_DYNAMIC_WRITE_EXT || usage == AL_DYNAMIC_READ_EXT || + usage == AL_DYNAMIC_COPY_EXT) + { + if(size >= 0) + { + /* (Re)allocate data */ + temp = realloc(ALBuf->data, size); + if(temp) + { + ALBuf->data = temp; + ALBuf->size = size; + ALBuf->usage = usage; + if(data) + memcpy(ALBuf->data, data, size); + } + else + alSetError(Context, AL_OUT_OF_MEMORY); + } + else + alSetError(Context, AL_INVALID_VALUE); + } + else + alSetError(Context, AL_INVALID_ENUM); + } + else + alSetError(Context, AL_INVALID_OPERATION); + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alDatabufferSubDataEXT(ALuint uiBuffer, ALintptrEXT start, ALsizeiptrEXT length, const ALvoid *data) +{ + ALCcontext *pContext; + ALdatabuffer *pBuffer; + ALCdevice *Device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + Device = pContext->Device; + if((pBuffer=LookupDatabuffer(Device->DatabufferMap, uiBuffer)) != NULL) + { + if(start >= 0 && length >= 0 && start+length <= pBuffer->size) + { + if(pBuffer->state == UNMAPPED) + memcpy(pBuffer->data+start, data, length); + else + alSetError(pContext, AL_INVALID_OPERATION); + } + else + alSetError(pContext, AL_INVALID_VALUE); + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + +AL_API ALvoid AL_APIENTRY alGetDatabufferSubDataEXT(ALuint uiBuffer, ALintptrEXT start, ALsizeiptrEXT length, ALvoid *data) +{ + ALCcontext *pContext; + ALdatabuffer *pBuffer; + ALCdevice *Device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + Device = pContext->Device; + if((pBuffer=LookupDatabuffer(Device->DatabufferMap, uiBuffer)) != NULL) + { + if(start >= 0 && length >= 0 && start+length <= pBuffer->size) + { + if(pBuffer->state == UNMAPPED) + memcpy(data, pBuffer->data+start, length); + else + alSetError(pContext, AL_INVALID_OPERATION); + } + else + alSetError(pContext, AL_INVALID_VALUE); + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alDatabufferfEXT(ALuint buffer, ALenum eParam, ALfloat flValue) +{ + ALCcontext *pContext; + ALCdevice *Device; + + (void)flValue; + + pContext = GetContextSuspended(); + if(!pContext) return; + + Device = pContext->Device; + if(LookupDatabuffer(Device->DatabufferMap, buffer) != NULL) + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + +AL_API ALvoid AL_APIENTRY alDatabufferfvEXT(ALuint buffer, ALenum eParam, const ALfloat* flValues) +{ + ALCcontext *pContext; + ALCdevice *Device; + + (void)flValues; + + pContext = GetContextSuspended(); + if(!pContext) return; + + Device = pContext->Device; + if(LookupDatabuffer(Device->DatabufferMap, buffer) != NULL) + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alDatabufferiEXT(ALuint buffer, ALenum eParam, ALint lValue) +{ + ALCcontext *pContext; + ALCdevice *Device; + + (void)lValue; + + pContext = GetContextSuspended(); + if(!pContext) return; + + Device = pContext->Device; + if(LookupDatabuffer(Device->DatabufferMap, buffer) != NULL) + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + +AL_API ALvoid AL_APIENTRY alDatabufferivEXT(ALuint buffer, ALenum eParam, const ALint* plValues) +{ + ALCcontext *pContext; + ALCdevice *Device; + + (void)plValues; + + pContext = GetContextSuspended(); + if(!pContext) return; + + Device = pContext->Device; + if(LookupDatabuffer(Device->DatabufferMap, buffer) != NULL) + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetDatabufferfEXT(ALuint buffer, ALenum eParam, ALfloat *pflValue) +{ + ALCcontext *pContext; + ALCdevice *Device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(pflValue) + { + Device = pContext->Device; + if(LookupDatabuffer(Device->DatabufferMap, buffer) != NULL) + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + +AL_API ALvoid AL_APIENTRY alGetDatabufferfvEXT(ALuint buffer, ALenum eParam, ALfloat* pflValues) +{ + ALCcontext *pContext; + ALCdevice *Device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(pflValues) + { + Device = pContext->Device; + if(LookupDatabuffer(Device->DatabufferMap, buffer) != NULL) + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + +AL_API ALvoid AL_APIENTRY alGetDatabufferiEXT(ALuint buffer, ALenum eParam, ALint *plValue) +{ + ALCcontext *pContext; + ALdatabuffer *pBuffer; + ALCdevice *Device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(plValue) + { + Device = pContext->Device; + if((pBuffer=LookupDatabuffer(Device->DatabufferMap, buffer)) != NULL) + { + switch(eParam) + { + case AL_SIZE: + *plValue = (ALint)pBuffer->size; + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + +AL_API ALvoid AL_APIENTRY alGetDatabufferivEXT(ALuint buffer, ALenum eParam, ALint* plValues) +{ + ALCcontext *pContext; + ALCdevice *Device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(plValues) + { + Device = pContext->Device; + if(LookupDatabuffer(Device->DatabufferMap, buffer) != NULL) + { + switch (eParam) + { + case AL_SIZE: + alGetDatabufferiEXT(buffer, eParam, plValues); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alSelectDatabufferEXT(ALenum target, ALuint uiBuffer) +{ + ALCcontext *pContext; + ALdatabuffer *pBuffer = NULL; + ALCdevice *Device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + Device = pContext->Device; + if(uiBuffer == 0 || + (pBuffer=LookupDatabuffer(Device->DatabufferMap, uiBuffer)) != NULL) + { + if(target == AL_SAMPLE_SOURCE_EXT) + pContext->SampleSource = pBuffer; + else if(target == AL_SAMPLE_SINK_EXT) + pContext->SampleSink = pBuffer; + else + alSetError(pContext, AL_INVALID_VALUE); + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + + +AL_API ALvoid* AL_APIENTRY alMapDatabufferEXT(ALuint uiBuffer, ALintptrEXT start, ALsizeiptrEXT length, ALenum access) +{ + ALCcontext *pContext; + ALdatabuffer *pBuffer; + ALvoid *ret = NULL; + ALCdevice *Device; + + pContext = GetContextSuspended(); + if(!pContext) return NULL; + + Device = pContext->Device; + if((pBuffer=LookupDatabuffer(Device->DatabufferMap, uiBuffer)) != NULL) + { + if(start >= 0 && length >= 0 && start+length <= pBuffer->size) + { + if(access == AL_READ_ONLY_EXT || access == AL_WRITE_ONLY_EXT || + access == AL_READ_WRITE_EXT) + { + if(pBuffer->state == UNMAPPED) + { + ret = pBuffer->data + start; + pBuffer->state = MAPPED; + } + else + alSetError(pContext, AL_INVALID_OPERATION); + } + else + alSetError(pContext, AL_INVALID_ENUM); + } + else + alSetError(pContext, AL_INVALID_VALUE); + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); + + return ret; +} + +AL_API ALvoid AL_APIENTRY alUnmapDatabufferEXT(ALuint uiBuffer) +{ + ALCcontext *pContext; + ALdatabuffer *pBuffer; + ALCdevice *Device; + + pContext = GetContextSuspended(); + if(!pContext) return; + + Device = pContext->Device; + if((pBuffer=LookupDatabuffer(Device->DatabufferMap, uiBuffer)) != NULL) + { + if(pBuffer->state == MAPPED) + pBuffer->state = UNMAPPED; + else + alSetError(pContext, AL_INVALID_OPERATION); + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + + +/* +* ReleaseALDatabuffers() +* +* INTERNAL FN : Called by DLLMain on exit to destroy any buffers that still exist +*/ +ALvoid ReleaseALDatabuffers(ALCdevice *device) +{ + ALsizei i; + for(i = 0;i < device->DatabufferMap.size;i++) + { + ALdatabuffer *temp = device->DatabufferMap.array[i].value; + device->DatabufferMap.array[i].value = NULL; + + // Release buffer data + free(temp->data); + + // Release Buffer structure + ALTHUNK_REMOVEENTRY(temp->databuffer); + memset(temp, 0, sizeof(ALdatabuffer)); + free(temp); + } +} diff --git a/jni/OpenAL/OpenAL32/alEffect.c b/jni/OpenAL/OpenAL32/alEffect.c new file mode 100644 index 0000000..cb07e21 --- /dev/null +++ b/jni/OpenAL/OpenAL32/alEffect.c @@ -0,0 +1,1377 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "alMain.h" +#include "alEffect.h" +#include "alThunk.h" +#include "alError.h" + + +ALboolean DisabledEffects[MAX_EFFECTS]; + + +static void InitEffectParams(ALeffect *effect, ALenum type); + +#define LookupEffect(m, k) ((ALeffect*)LookupUIntMapKey(&(m), (k))) + +AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) +{ + ALCcontext *Context; + ALsizei i=0; + + Context = GetContextSuspended(); + if(!Context) return; + + if(n < 0 || IsBadWritePtr((void*)effects, n * sizeof(ALuint))) + alSetError(Context, AL_INVALID_VALUE); + else + { + ALCdevice *device = Context->Device; + ALenum err; + + while(i < n) + { + ALeffect *effect = calloc(1, sizeof(ALeffect)); + if(!effect) + { + alSetError(Context, AL_OUT_OF_MEMORY); + alDeleteEffects(i, effects); + break; + } + + effect->effect = ALTHUNK_ADDENTRY(effect); + err = InsertUIntMapEntry(&device->EffectMap, effect->effect, effect); + if(err != AL_NO_ERROR) + { + ALTHUNK_REMOVEENTRY(effect->effect); + memset(effect, 0, sizeof(ALeffect)); + free(effect); + + alSetError(Context, err); + alDeleteEffects(i, effects); + break; + } + + effects[i++] = effect->effect; + InitEffectParams(effect, AL_EFFECT_NULL); + } + } + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, ALuint *effects) +{ + ALCcontext *Context; + ALCdevice *device; + ALeffect *ALEffect; + ALboolean Failed; + ALsizei i; + + Context = GetContextSuspended(); + if(!Context) return; + + Failed = AL_TRUE; + device = Context->Device; + if(n < 0) + alSetError(Context, AL_INVALID_VALUE); + else + { + Failed = AL_FALSE; + // Check that all effects are valid + for(i = 0;i < n;i++) + { + if(!effects[i]) + continue; + + if(LookupEffect(device->EffectMap, effects[i]) == NULL) + { + alSetError(Context, AL_INVALID_NAME); + Failed = AL_TRUE; + break; + } + } + } + + if(!Failed) + { + // All effects are valid + for(i = 0;i < n;i++) + { + // Recheck that the effect is valid, because there could be duplicated names + if((ALEffect=LookupEffect(device->EffectMap, effects[i])) == NULL) + continue; + + RemoveUIntMapKey(&device->EffectMap, ALEffect->effect); + ALTHUNK_REMOVEENTRY(ALEffect->effect); + + memset(ALEffect, 0, sizeof(ALeffect)); + free(ALEffect); + } + } + + ProcessContext(Context); +} + +AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect) +{ + ALCcontext *Context; + ALboolean result; + + Context = GetContextSuspended(); + if(!Context) return AL_FALSE; + + result = ((!effect || LookupEffect(Context->Device->EffectMap, effect)) ? + AL_TRUE : AL_FALSE); + + ProcessContext(Context); + + return result; +} + +AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue) +{ + ALCcontext *Context; + ALCdevice *Device; + ALeffect *ALEffect; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALEffect=LookupEffect(Device->EffectMap, effect)) != NULL) + { + if(param == AL_EFFECT_TYPE) + { + ALboolean isOk = (iValue == AL_EFFECT_NULL || + (iValue == AL_EFFECT_EAXREVERB && !DisabledEffects[EAXREVERB]) || + (iValue == AL_EFFECT_REVERB && !DisabledEffects[REVERB]) || + (iValue == AL_EFFECT_ECHO && !DisabledEffects[ECHO]) || + (iValue == AL_EFFECT_RING_MODULATOR && !DisabledEffects[MODULATOR])); + + if(isOk) + InitEffectParams(ALEffect, iValue); + else + alSetError(Context, AL_INVALID_VALUE); + } + else if(ALEffect->type == AL_EFFECT_EAXREVERB) + { + switch(param) + { + case AL_EAXREVERB_DECAY_HFLIMIT: + if(iValue >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && + iValue <= AL_EAXREVERB_MAX_DECAY_HFLIMIT) + ALEffect->Reverb.DecayHFLimit = iValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_REVERB) + { + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + if(iValue >= AL_REVERB_MIN_DECAY_HFLIMIT && + iValue <= AL_REVERB_MAX_DECAY_HFLIMIT) + ALEffect->Reverb.DecayHFLimit = iValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_RING_MODULATOR) + { + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + alEffectf(effect, param, (ALfloat)iValue); + break; + + case AL_RING_MODULATOR_WAVEFORM: + if(iValue >= AL_RING_MODULATOR_MIN_WAVEFORM && + iValue <= AL_RING_MODULATOR_MAX_WAVEFORM) + ALEffect->Modulator.Waveform = iValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_ENUM); + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, ALint *piValues) +{ + ALCcontext *Context; + ALCdevice *Device; + ALeffect *ALEffect; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALEffect=LookupEffect(Device->EffectMap, effect)) != NULL) + { + if(param == AL_EFFECT_TYPE) + { + alEffecti(effect, param, piValues[0]); + } + else if(ALEffect->type == AL_EFFECT_EAXREVERB) + { + switch(param) + { + case AL_EAXREVERB_DECAY_HFLIMIT: + alEffecti(effect, param, piValues[0]); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_REVERB) + { + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + alEffecti(effect, param, piValues[0]); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_RING_MODULATOR) + { + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + case AL_RING_MODULATOR_WAVEFORM: + alEffecti(effect, param, piValues[0]); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_ENUM); + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flArg) +{ + ALCcontext *Context; + ALCdevice *Device; + ALeffect *ALEffect; + ALfp flValue = float2ALfp(flArg); + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALEffect=LookupEffect(Device->EffectMap, effect)) != NULL) + { + if(ALEffect->type == AL_EFFECT_EAXREVERB) + { + switch(param) + { + case AL_EAXREVERB_DENSITY: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_DENSITY) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_DENSITY)) + ALEffect->Reverb.Density = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_DIFFUSION: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_DIFFUSION) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_DIFFUSION)) + ALEffect->Reverb.Diffusion = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_GAIN: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_GAIN) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_GAIN)) + ALEffect->Reverb.Gain = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_GAINHF: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_GAINHF) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_GAIN)) + ALEffect->Reverb.GainHF = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_GAINLF: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_GAINLF) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_GAINLF)) + ALEffect->Reverb.GainLF = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_DECAY_TIME: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_DECAY_TIME) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_DECAY_TIME)) + ALEffect->Reverb.DecayTime = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_DECAY_HFRATIO: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_DECAY_HFRATIO) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_DECAY_HFRATIO)) + ALEffect->Reverb.DecayHFRatio = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_DECAY_LFRATIO: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_DECAY_LFRATIO) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_DECAY_LFRATIO)) + ALEffect->Reverb.DecayLFRatio = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_REFLECTIONS_GAIN: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_REFLECTIONS_GAIN) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) + ALEffect->Reverb.ReflectionsGain = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_REFLECTIONS_DELAY: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_REFLECTIONS_DELAY) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) + ALEffect->Reverb.ReflectionsDelay = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_LATE_REVERB_GAIN: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_LATE_REVERB_GAIN) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) + ALEffect->Reverb.LateReverbGain = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_LATE_REVERB_DELAY: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_LATE_REVERB_DELAY) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) + ALEffect->Reverb.LateReverbDelay = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) + ALEffect->Reverb.AirAbsorptionGainHF = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_ECHO_TIME: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_ECHO_TIME) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_ECHO_TIME)) + ALEffect->Reverb.EchoTime = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_ECHO_DEPTH: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_ECHO_DEPTH) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_ECHO_DEPTH)) + ALEffect->Reverb.EchoDepth = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_MODULATION_TIME: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_MODULATION_TIME) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_MODULATION_TIME)) + ALEffect->Reverb.ModulationTime = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_MODULATION_DEPTH: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_MODULATION_DEPTH) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_MODULATION_DEPTH)) + ALEffect->Reverb.ModulationDepth = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_HFREFERENCE: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_HFREFERENCE) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_HFREFERENCE)) + ALEffect->Reverb.HFReference = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_LFREFERENCE: + if(flValue >= float2ALfp(AL_EAXREVERB_MIN_LFREFERENCE) && + flValue <= float2ALfp(AL_EAXREVERB_MAX_LFREFERENCE)) + ALEffect->Reverb.LFReference = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: + if(flValue >= float2ALfp(0.0f) && flValue <= float2ALfp(10.0f)) + ALEffect->Reverb.RoomRolloffFactor = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_REVERB) + { + switch(param) + { + case AL_REVERB_DENSITY: + if(flValue >= float2ALfp(AL_REVERB_MIN_DENSITY) && + flValue <= float2ALfp(AL_REVERB_MAX_DENSITY)) + ALEffect->Reverb.Density = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_DIFFUSION: + if(flValue >= float2ALfp(AL_REVERB_MIN_DIFFUSION) && + flValue <= float2ALfp(AL_REVERB_MAX_DIFFUSION)) + ALEffect->Reverb.Diffusion = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_GAIN: + if(flValue >= float2ALfp(AL_REVERB_MIN_GAIN) && + flValue <= float2ALfp(AL_REVERB_MAX_GAIN)) + ALEffect->Reverb.Gain = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_GAINHF: + if(flValue >= float2ALfp(AL_REVERB_MIN_GAINHF) && + flValue <= float2ALfp(AL_REVERB_MAX_GAINHF)) + ALEffect->Reverb.GainHF = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_DECAY_TIME: + if(flValue >= float2ALfp(AL_REVERB_MIN_DECAY_TIME) && + flValue <= float2ALfp(AL_REVERB_MAX_DECAY_TIME)) + ALEffect->Reverb.DecayTime = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_DECAY_HFRATIO: + if(flValue >= float2ALfp(AL_REVERB_MIN_DECAY_HFRATIO) && + flValue <= float2ALfp(AL_REVERB_MAX_DECAY_HFRATIO)) + ALEffect->Reverb.DecayHFRatio = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_REFLECTIONS_GAIN: + if(flValue >= float2ALfp(AL_REVERB_MIN_REFLECTIONS_GAIN) && + flValue <= float2ALfp(AL_REVERB_MAX_REFLECTIONS_GAIN)) + ALEffect->Reverb.ReflectionsGain = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_REFLECTIONS_DELAY: + if(flValue >= float2ALfp(AL_REVERB_MIN_REFLECTIONS_DELAY) && + flValue <= float2ALfp(AL_REVERB_MAX_REFLECTIONS_DELAY)) + ALEffect->Reverb.ReflectionsDelay = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_LATE_REVERB_GAIN: + if(flValue >= float2ALfp(AL_REVERB_MIN_LATE_REVERB_GAIN) && + flValue <= float2ALfp(AL_REVERB_MAX_LATE_REVERB_GAIN)) + ALEffect->Reverb.LateReverbGain = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_LATE_REVERB_DELAY: + if(flValue >= float2ALfp(AL_REVERB_MIN_LATE_REVERB_DELAY) && + flValue <= float2ALfp(AL_REVERB_MAX_LATE_REVERB_DELAY)) + ALEffect->Reverb.LateReverbDelay = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_AIR_ABSORPTION_GAINHF: + if(flValue >= float2ALfp(AL_REVERB_MIN_AIR_ABSORPTION_GAINHF) && + flValue <= float2ALfp(AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) + ALEffect->Reverb.AirAbsorptionGainHF = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_REVERB_ROOM_ROLLOFF_FACTOR: + if(flValue >= float2ALfp(AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR) && + flValue <= float2ALfp(AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) + ALEffect->Reverb.RoomRolloffFactor = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + case AL_ECHO_DELAY: + if(flValue >= float2ALfp(AL_ECHO_MIN_DELAY) && flValue <= float2ALfp(AL_ECHO_MAX_DELAY)) + ALEffect->Echo.Delay = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_ECHO_LRDELAY: + if(flValue >= float2ALfp(AL_ECHO_MIN_LRDELAY) && flValue <= float2ALfp(AL_ECHO_MAX_LRDELAY)) + ALEffect->Echo.LRDelay = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_ECHO_DAMPING: + if(flValue >= float2ALfp(AL_ECHO_MIN_DAMPING) && flValue <= float2ALfp(AL_ECHO_MAX_DAMPING)) + ALEffect->Echo.Damping = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_ECHO_FEEDBACK: + if(flValue >= float2ALfp(AL_ECHO_MIN_FEEDBACK) && flValue <= float2ALfp(AL_ECHO_MAX_FEEDBACK)) + ALEffect->Echo.Feedback = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_ECHO_SPREAD: + if(flValue >= float2ALfp(AL_ECHO_MIN_SPREAD) && flValue <= float2ALfp(AL_ECHO_MAX_SPREAD)) + ALEffect->Echo.Spread = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_RING_MODULATOR) + { + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + if(flValue >= float2ALfp(AL_RING_MODULATOR_MIN_FREQUENCY) && + flValue <= float2ALfp(AL_RING_MODULATOR_MAX_FREQUENCY)) + ALEffect->Modulator.Frequency = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + if(flValue >= float2ALfp(AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF) && + flValue <= float2ALfp(AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) + ALEffect->Modulator.HighPassCutoff = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_ENUM); + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, ALfloat *pflValues) +{ + ALCcontext *Context; + ALCdevice *Device; + ALeffect *ALEffect; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALEffect=LookupEffect(Device->EffectMap, effect)) != NULL) + { + if(ALEffect->type == AL_EFFECT_EAXREVERB) + { + switch(param) + { + case AL_EAXREVERB_DENSITY: + case AL_EAXREVERB_DIFFUSION: + case AL_EAXREVERB_GAIN: + case AL_EAXREVERB_GAINHF: + case AL_EAXREVERB_GAINLF: + case AL_EAXREVERB_DECAY_TIME: + case AL_EAXREVERB_DECAY_HFRATIO: + case AL_EAXREVERB_DECAY_LFRATIO: + case AL_EAXREVERB_REFLECTIONS_GAIN: + case AL_EAXREVERB_REFLECTIONS_DELAY: + case AL_EAXREVERB_LATE_REVERB_GAIN: + case AL_EAXREVERB_LATE_REVERB_DELAY: + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: + case AL_EAXREVERB_ECHO_TIME: + case AL_EAXREVERB_ECHO_DEPTH: + case AL_EAXREVERB_MODULATION_TIME: + case AL_EAXREVERB_MODULATION_DEPTH: + case AL_EAXREVERB_HFREFERENCE: + case AL_EAXREVERB_LFREFERENCE: + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: + alEffectf(effect, param, pflValues[0]); + break; + + case AL_EAXREVERB_REFLECTIONS_PAN: + if(!__isnan(pflValues[0]) && !__isnan(pflValues[1]) && !__isnan(pflValues[2])) + { + ALEffect->Reverb.ReflectionsPan[0] = float2ALfp(pflValues[0]); + ALEffect->Reverb.ReflectionsPan[1] = float2ALfp(pflValues[1]); + ALEffect->Reverb.ReflectionsPan[2] = float2ALfp(pflValues[2]); + } + else + alSetError(Context, AL_INVALID_VALUE); + break; + case AL_EAXREVERB_LATE_REVERB_PAN: + if(!__isnan(pflValues[0]) && !__isnan(pflValues[1]) && !__isnan(pflValues[2])) + { + ALEffect->Reverb.LateReverbPan[0] = float2ALfp(pflValues[0]); + ALEffect->Reverb.LateReverbPan[1] = float2ALfp(pflValues[1]); + ALEffect->Reverb.LateReverbPan[2] = float2ALfp(pflValues[2]); + } + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_REVERB) + { + switch(param) + { + case AL_REVERB_DENSITY: + case AL_REVERB_DIFFUSION: + case AL_REVERB_GAIN: + case AL_REVERB_GAINHF: + case AL_REVERB_DECAY_TIME: + case AL_REVERB_DECAY_HFRATIO: + case AL_REVERB_REFLECTIONS_GAIN: + case AL_REVERB_REFLECTIONS_DELAY: + case AL_REVERB_LATE_REVERB_GAIN: + case AL_REVERB_LATE_REVERB_DELAY: + case AL_REVERB_AIR_ABSORPTION_GAINHF: + case AL_REVERB_ROOM_ROLLOFF_FACTOR: + alEffectf(effect, param, pflValues[0]); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + case AL_ECHO_DELAY: + case AL_ECHO_LRDELAY: + case AL_ECHO_DAMPING: + case AL_ECHO_FEEDBACK: + case AL_ECHO_SPREAD: + alEffectf(effect, param, pflValues[0]); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_RING_MODULATOR) + { + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + alEffectf(effect, param, pflValues[0]); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_ENUM); + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue) +{ + ALCcontext *Context; + ALCdevice *Device; + ALeffect *ALEffect; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALEffect=LookupEffect(Device->EffectMap, effect)) != NULL) + { + if(param == AL_EFFECT_TYPE) + { + *piValue = ALEffect->type; + } + else if(ALEffect->type == AL_EFFECT_EAXREVERB) + { + switch(param) + { + case AL_EAXREVERB_DECAY_HFLIMIT: + *piValue = ALEffect->Reverb.DecayHFLimit; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_REVERB) + { + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + *piValue = ALEffect->Reverb.DecayHFLimit; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_RING_MODULATOR) + { + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + *piValue = (ALint)ALfp2float(ALEffect->Modulator.Frequency); + break; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + *piValue = (ALint)ALfp2float(ALEffect->Modulator.HighPassCutoff); + break; + case AL_RING_MODULATOR_WAVEFORM: + *piValue = ALEffect->Modulator.Waveform; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_ENUM); + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues) +{ + ALCcontext *Context; + ALCdevice *Device; + ALeffect *ALEffect; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALEffect=LookupEffect(Device->EffectMap, effect)) != NULL) + { + if(param == AL_EFFECT_TYPE) + { + alGetEffecti(effect, param, piValues); + } + else if(ALEffect->type == AL_EFFECT_EAXREVERB) + { + switch(param) + { + case AL_EAXREVERB_DECAY_HFLIMIT: + alGetEffecti(effect, param, piValues); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_REVERB) + { + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + alGetEffecti(effect, param, piValues); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_RING_MODULATOR) + { + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + case AL_RING_MODULATOR_WAVEFORM: + alGetEffecti(effect, param, piValues); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_ENUM); + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue) +{ + ALCcontext *Context; + ALCdevice *Device; + ALeffect *ALEffect; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALEffect=LookupEffect(Device->EffectMap, effect)) != NULL) + { + if(ALEffect->type == AL_EFFECT_EAXREVERB) + { + switch(param) + { + case AL_EAXREVERB_DENSITY: + *pflValue = ALfp2float(ALEffect->Reverb.Density); + break; + + case AL_EAXREVERB_DIFFUSION: + *pflValue = ALfp2float(ALEffect->Reverb.Diffusion); + break; + + case AL_EAXREVERB_GAIN: + *pflValue = ALfp2float(ALEffect->Reverb.Gain); + break; + + case AL_EAXREVERB_GAINHF: + *pflValue = ALfp2float(ALEffect->Reverb.GainHF); + break; + + case AL_EAXREVERB_GAINLF: + *pflValue = ALfp2float(ALEffect->Reverb.GainLF); + break; + + case AL_EAXREVERB_DECAY_TIME: + *pflValue = ALfp2float(ALEffect->Reverb.DecayTime); + break; + + case AL_EAXREVERB_DECAY_HFRATIO: + *pflValue = ALfp2float(ALEffect->Reverb.DecayHFRatio); + break; + + case AL_EAXREVERB_DECAY_LFRATIO: + *pflValue = ALfp2float(ALEffect->Reverb.DecayLFRatio); + break; + + case AL_EAXREVERB_REFLECTIONS_GAIN: + *pflValue = ALfp2float(ALEffect->Reverb.ReflectionsGain); + break; + + case AL_EAXREVERB_REFLECTIONS_DELAY: + *pflValue = ALfp2float(ALEffect->Reverb.ReflectionsDelay); + break; + + case AL_EAXREVERB_LATE_REVERB_GAIN: + *pflValue = ALfp2float(ALEffect->Reverb.LateReverbGain); + break; + + case AL_EAXREVERB_LATE_REVERB_DELAY: + *pflValue = ALfp2float(ALEffect->Reverb.LateReverbDelay); + break; + + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: + *pflValue = ALfp2float(ALEffect->Reverb.AirAbsorptionGainHF); + break; + + case AL_EAXREVERB_ECHO_TIME: + *pflValue = ALfp2float(ALEffect->Reverb.EchoTime); + break; + + case AL_EAXREVERB_ECHO_DEPTH: + *pflValue = ALfp2float(ALEffect->Reverb.EchoDepth); + break; + + case AL_EAXREVERB_MODULATION_TIME: + *pflValue = ALfp2float(ALEffect->Reverb.ModulationTime); + break; + + case AL_EAXREVERB_MODULATION_DEPTH: + *pflValue = ALfp2float(ALEffect->Reverb.ModulationDepth); + break; + + case AL_EAXREVERB_HFREFERENCE: + *pflValue = ALfp2float(ALEffect->Reverb.HFReference); + break; + + case AL_EAXREVERB_LFREFERENCE: + *pflValue = ALfp2float(ALEffect->Reverb.LFReference); + break; + + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: + *pflValue = ALfp2float(ALEffect->Reverb.RoomRolloffFactor); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_REVERB) + { + switch(param) + { + case AL_REVERB_DENSITY: + *pflValue = ALfp2float(ALEffect->Reverb.Density); + break; + + case AL_REVERB_DIFFUSION: + *pflValue = ALfp2float(ALEffect->Reverb.Diffusion); + break; + + case AL_REVERB_GAIN: + *pflValue = ALfp2float(ALEffect->Reverb.Gain); + break; + + case AL_REVERB_GAINHF: + *pflValue = ALfp2float(ALEffect->Reverb.GainHF); + break; + + case AL_REVERB_DECAY_TIME: + *pflValue = ALfp2float(ALEffect->Reverb.DecayTime); + break; + + case AL_REVERB_DECAY_HFRATIO: + *pflValue = ALfp2float(ALEffect->Reverb.DecayHFRatio); + break; + + case AL_REVERB_REFLECTIONS_GAIN: + *pflValue = ALfp2float(ALEffect->Reverb.ReflectionsGain); + break; + + case AL_REVERB_REFLECTIONS_DELAY: + *pflValue = ALfp2float(ALEffect->Reverb.ReflectionsDelay); + break; + + case AL_REVERB_LATE_REVERB_GAIN: + *pflValue = ALfp2float(ALEffect->Reverb.LateReverbGain); + break; + + case AL_REVERB_LATE_REVERB_DELAY: + *pflValue = ALfp2float(ALEffect->Reverb.LateReverbDelay); + break; + + case AL_REVERB_AIR_ABSORPTION_GAINHF: + *pflValue = ALfp2float(ALEffect->Reverb.AirAbsorptionGainHF); + break; + + case AL_REVERB_ROOM_ROLLOFF_FACTOR: + *pflValue = ALfp2float(ALEffect->Reverb.RoomRolloffFactor); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + case AL_ECHO_DELAY: + *pflValue = ALfp2float(ALEffect->Echo.Delay); + break; + + case AL_ECHO_LRDELAY: + *pflValue = ALfp2float(ALEffect->Echo.LRDelay); + break; + + case AL_ECHO_DAMPING: + *pflValue = ALfp2float(ALEffect->Echo.Damping); + break; + + case AL_ECHO_FEEDBACK: + *pflValue = ALfp2float(ALEffect->Echo.Feedback); + break; + + case AL_ECHO_SPREAD: + *pflValue = ALfp2float(ALEffect->Echo.Spread); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_RING_MODULATOR) + { + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + *pflValue = ALfp2float(ALEffect->Modulator.Frequency); + break; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + *pflValue = ALfp2float(ALEffect->Modulator.HighPassCutoff); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_ENUM); + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues) +{ + ALCcontext *Context; + ALCdevice *Device; + ALeffect *ALEffect; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALEffect=LookupEffect(Device->EffectMap, effect)) != NULL) + { + if(ALEffect->type == AL_EFFECT_EAXREVERB) + { + switch(param) + { + case AL_EAXREVERB_DENSITY: + case AL_EAXREVERB_DIFFUSION: + case AL_EAXREVERB_GAIN: + case AL_EAXREVERB_GAINHF: + case AL_EAXREVERB_GAINLF: + case AL_EAXREVERB_DECAY_TIME: + case AL_EAXREVERB_DECAY_HFRATIO: + case AL_EAXREVERB_DECAY_LFRATIO: + case AL_EAXREVERB_REFLECTIONS_GAIN: + case AL_EAXREVERB_REFLECTIONS_DELAY: + case AL_EAXREVERB_LATE_REVERB_GAIN: + case AL_EAXREVERB_LATE_REVERB_DELAY: + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: + case AL_EAXREVERB_ECHO_TIME: + case AL_EAXREVERB_ECHO_DEPTH: + case AL_EAXREVERB_MODULATION_TIME: + case AL_EAXREVERB_MODULATION_DEPTH: + case AL_EAXREVERB_HFREFERENCE: + case AL_EAXREVERB_LFREFERENCE: + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: + alGetEffectf(effect, param, pflValues); + break; + + case AL_EAXREVERB_REFLECTIONS_PAN: + pflValues[0] = ALfp2float(ALEffect->Reverb.ReflectionsPan[0]); + pflValues[1] = ALfp2float(ALEffect->Reverb.ReflectionsPan[1]); + pflValues[2] = ALfp2float(ALEffect->Reverb.ReflectionsPan[2]); + break; + case AL_EAXREVERB_LATE_REVERB_PAN: + pflValues[0] = ALfp2float(ALEffect->Reverb.LateReverbPan[0]); + pflValues[1] = ALfp2float(ALEffect->Reverb.LateReverbPan[1]); + pflValues[2] = ALfp2float(ALEffect->Reverb.LateReverbPan[2]); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_REVERB) + { + switch(param) + { + case AL_REVERB_DENSITY: + case AL_REVERB_DIFFUSION: + case AL_REVERB_GAIN: + case AL_REVERB_GAINHF: + case AL_REVERB_DECAY_TIME: + case AL_REVERB_DECAY_HFRATIO: + case AL_REVERB_REFLECTIONS_GAIN: + case AL_REVERB_REFLECTIONS_DELAY: + case AL_REVERB_LATE_REVERB_GAIN: + case AL_REVERB_LATE_REVERB_DELAY: + case AL_REVERB_AIR_ABSORPTION_GAINHF: + case AL_REVERB_ROOM_ROLLOFF_FACTOR: + alGetEffectf(effect, param, pflValues); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + case AL_ECHO_DELAY: + case AL_ECHO_LRDELAY: + case AL_ECHO_DAMPING: + case AL_ECHO_FEEDBACK: + case AL_ECHO_SPREAD: + alGetEffectf(effect, param, pflValues); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else if(ALEffect->type == AL_EFFECT_RING_MODULATOR) + { + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + alGetEffectf(effect, param, pflValues); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_ENUM); + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + + +ALvoid ReleaseALEffects(ALCdevice *device) +{ + ALsizei i; + for(i = 0;i < device->EffectMap.size;i++) + { + ALeffect *temp = device->EffectMap.array[i].value; + device->EffectMap.array[i].value = NULL; + + // Release effect structure + ALTHUNK_REMOVEENTRY(temp->effect); + memset(temp, 0, sizeof(ALeffect)); + free(temp); + } +} + + +static void InitEffectParams(ALeffect *effect, ALenum type) +{ + effect->type = type; + switch(type) + { + /* NOTE: Standard reverb and EAX reverb use the same defaults for the + * shared parameters, and EAX's additional parameters default to + * values assumed by standard reverb. + */ + case AL_EFFECT_EAXREVERB: + case AL_EFFECT_REVERB: + effect->Reverb.Density = float2ALfp(AL_EAXREVERB_DEFAULT_DENSITY); + effect->Reverb.Diffusion = float2ALfp(AL_EAXREVERB_DEFAULT_DIFFUSION); + effect->Reverb.Gain = float2ALfp(AL_EAXREVERB_DEFAULT_GAIN); + effect->Reverb.GainHF = float2ALfp(AL_EAXREVERB_DEFAULT_GAINHF); + effect->Reverb.GainLF = float2ALfp(AL_EAXREVERB_DEFAULT_GAINLF); + effect->Reverb.DecayTime = float2ALfp(AL_EAXREVERB_DEFAULT_DECAY_TIME); + effect->Reverb.DecayHFRatio = float2ALfp(AL_EAXREVERB_DEFAULT_DECAY_HFRATIO); + effect->Reverb.DecayLFRatio = float2ALfp(AL_EAXREVERB_DEFAULT_DECAY_LFRATIO); + effect->Reverb.ReflectionsGain = float2ALfp(AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN); + effect->Reverb.ReflectionsDelay = float2ALfp(AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY); + effect->Reverb.ReflectionsPan[0] = float2ALfp(AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ); + effect->Reverb.ReflectionsPan[1] = float2ALfp(AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ); + effect->Reverb.ReflectionsPan[2] = float2ALfp(AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ); + effect->Reverb.LateReverbGain = float2ALfp(AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN); + effect->Reverb.LateReverbDelay = float2ALfp(AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY); + effect->Reverb.LateReverbPan[0] = float2ALfp(AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ); + effect->Reverb.LateReverbPan[1] = float2ALfp(AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ); + effect->Reverb.LateReverbPan[2] = float2ALfp(AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ); + effect->Reverb.EchoTime = float2ALfp(AL_EAXREVERB_DEFAULT_ECHO_TIME); + effect->Reverb.EchoDepth = float2ALfp(AL_EAXREVERB_DEFAULT_ECHO_DEPTH); + effect->Reverb.ModulationTime = float2ALfp(AL_EAXREVERB_DEFAULT_MODULATION_TIME); + effect->Reverb.ModulationDepth = float2ALfp(AL_EAXREVERB_DEFAULT_MODULATION_DEPTH); + effect->Reverb.AirAbsorptionGainHF = float2ALfp(AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF); + effect->Reverb.HFReference = float2ALfp(AL_EAXREVERB_DEFAULT_HFREFERENCE); + effect->Reverb.LFReference = float2ALfp(AL_EAXREVERB_DEFAULT_LFREFERENCE); + effect->Reverb.RoomRolloffFactor = float2ALfp(AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR); + effect->Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; + break; + case AL_EFFECT_ECHO: + effect->Echo.Delay = float2ALfp(AL_ECHO_DEFAULT_DELAY); + effect->Echo.LRDelay = float2ALfp(AL_ECHO_DEFAULT_LRDELAY); + effect->Echo.Damping = float2ALfp(AL_ECHO_DEFAULT_DAMPING); + effect->Echo.Feedback = float2ALfp(AL_ECHO_DEFAULT_FEEDBACK); + effect->Echo.Spread = float2ALfp(AL_ECHO_DEFAULT_SPREAD); + break; + case AL_EFFECT_RING_MODULATOR: + effect->Modulator.Frequency = float2ALfp(AL_RING_MODULATOR_DEFAULT_FREQUENCY); + effect->Modulator.HighPassCutoff = float2ALfp(AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF); + effect->Modulator.Waveform = AL_RING_MODULATOR_DEFAULT_WAVEFORM; + break; + } +} diff --git a/jni/OpenAL/OpenAL32/alError.c b/jni/OpenAL/OpenAL32/alError.c new file mode 100644 index 0000000..43b8e44 --- /dev/null +++ b/jni/OpenAL/OpenAL32/alError.c @@ -0,0 +1,47 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "alMain.h" +#include "AL/alc.h" +#include "alError.h" + +AL_API ALenum AL_APIENTRY alGetError(ALvoid) +{ + ALCcontext *Context; + ALenum errorCode; + + Context = GetContextSuspended(); + if(!Context) return AL_INVALID_OPERATION; + + errorCode = Context->LastError; + Context->LastError = AL_NO_ERROR; + + ProcessContext(Context); + + return errorCode; +} + +ALvoid alSetError(ALCcontext *Context, ALenum errorCode) +{ + if(Context->LastError == AL_NO_ERROR) + Context->LastError = errorCode; +} diff --git a/jni/OpenAL/OpenAL32/alExtension.c b/jni/OpenAL/OpenAL32/alExtension.c new file mode 100644 index 0000000..0febf22 --- /dev/null +++ b/jni/OpenAL/OpenAL32/alExtension.c @@ -0,0 +1,331 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include + +#include "alError.h" +#include "alMain.h" +#include "alFilter.h" +#include "alEffect.h" +#include "alAuxEffectSlot.h" +#include "alDatabuffer.h" +#include "alSource.h" +#include "alBuffer.h" +#include "AL/al.h" +#include "AL/alc.h" + +typedef struct ALenums { + const ALchar *enumName; + ALenum value; +} ALenums; + + +static const ALenums enumeration[] = { + // Types + { "AL_INVALID", AL_INVALID }, + { "AL_NONE", AL_NONE }, + { "AL_FALSE", AL_FALSE }, + { "AL_TRUE", AL_TRUE }, + + // Source and Listener Properties + { "AL_SOURCE_RELATIVE", AL_SOURCE_RELATIVE }, + { "AL_CONE_INNER_ANGLE", AL_CONE_INNER_ANGLE }, + { "AL_CONE_OUTER_ANGLE", AL_CONE_OUTER_ANGLE }, + { "AL_PITCH", AL_PITCH }, + { "AL_POSITION", AL_POSITION }, + { "AL_DIRECTION", AL_DIRECTION }, + { "AL_VELOCITY", AL_VELOCITY }, + { "AL_LOOPING", AL_LOOPING }, + { "AL_BUFFER", AL_BUFFER }, + { "AL_GAIN", AL_GAIN }, + { "AL_MIN_GAIN", AL_MIN_GAIN }, + { "AL_MAX_GAIN", AL_MAX_GAIN }, + { "AL_ORIENTATION", AL_ORIENTATION }, + { "AL_REFERENCE_DISTANCE", AL_REFERENCE_DISTANCE }, + { "AL_ROLLOFF_FACTOR", AL_ROLLOFF_FACTOR }, + { "AL_CONE_OUTER_GAIN", AL_CONE_OUTER_GAIN }, + { "AL_MAX_DISTANCE", AL_MAX_DISTANCE }, + { "AL_SEC_OFFSET", AL_SEC_OFFSET }, + { "AL_SAMPLE_OFFSET", AL_SAMPLE_OFFSET }, + { "AL_SAMPLE_RW_OFFSETS_SOFT", AL_SAMPLE_RW_OFFSETS_SOFT }, + { "AL_BYTE_OFFSET", AL_BYTE_OFFSET }, + { "AL_BYTE_RW_OFFSETS_SOFT", AL_BYTE_RW_OFFSETS_SOFT }, + { "AL_SOURCE_TYPE", AL_SOURCE_TYPE }, + { "AL_STATIC", AL_STATIC }, + { "AL_STREAMING", AL_STREAMING }, + { "AL_UNDETERMINED", AL_UNDETERMINED }, + { "AL_METERS_PER_UNIT", AL_METERS_PER_UNIT }, + + // Source EFX Properties + { "AL_DIRECT_FILTER", AL_DIRECT_FILTER }, + { "AL_AUXILIARY_SEND_FILTER", AL_AUXILIARY_SEND_FILTER }, + { "AL_AIR_ABSORPTION_FACTOR", AL_AIR_ABSORPTION_FACTOR }, + { "AL_ROOM_ROLLOFF_FACTOR", AL_ROOM_ROLLOFF_FACTOR }, + { "AL_CONE_OUTER_GAINHF", AL_CONE_OUTER_GAINHF }, + { "AL_DIRECT_FILTER_GAINHF_AUTO", AL_DIRECT_FILTER_GAINHF_AUTO }, + { "AL_AUXILIARY_SEND_FILTER_GAIN_AUTO", AL_AUXILIARY_SEND_FILTER_GAIN_AUTO }, + { "AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO", AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO}, + + // Source State information + { "AL_SOURCE_STATE", AL_SOURCE_STATE }, + { "AL_INITIAL", AL_INITIAL }, + { "AL_PLAYING", AL_PLAYING }, + { "AL_PAUSED", AL_PAUSED }, + { "AL_STOPPED", AL_STOPPED }, + + // Queue information + { "AL_BUFFERS_QUEUED", AL_BUFFERS_QUEUED }, + { "AL_BUFFERS_PROCESSED", AL_BUFFERS_PROCESSED }, + + // Buffer Formats + { "AL_FORMAT_MONO8", AL_FORMAT_MONO8 }, + { "AL_FORMAT_MONO16", AL_FORMAT_MONO16 }, + { "AL_FORMAT_MONO_FLOAT32", AL_FORMAT_MONO_FLOAT32 }, + { "AL_FORMAT_MONO_DOUBLE_EXT", AL_FORMAT_MONO_DOUBLE_EXT }, + { "AL_FORMAT_STEREO8", AL_FORMAT_STEREO8 }, + { "AL_FORMAT_STEREO16", AL_FORMAT_STEREO16 }, + { "AL_FORMAT_STEREO_FLOAT32", AL_FORMAT_STEREO_FLOAT32 }, + { "AL_FORMAT_STEREO_DOUBLE_EXT", AL_FORMAT_STEREO_DOUBLE_EXT }, + { "AL_FORMAT_MONO_IMA4", AL_FORMAT_MONO_IMA4 }, + { "AL_FORMAT_STEREO_IMA4", AL_FORMAT_STEREO_IMA4 }, + { "AL_FORMAT_QUAD8_LOKI", AL_FORMAT_QUAD8_LOKI }, + { "AL_FORMAT_QUAD16_LOKI", AL_FORMAT_QUAD16_LOKI }, + { "AL_FORMAT_QUAD8", AL_FORMAT_QUAD8 }, + { "AL_FORMAT_QUAD16", AL_FORMAT_QUAD16 }, + { "AL_FORMAT_QUAD32", AL_FORMAT_QUAD32 }, + { "AL_FORMAT_51CHN8", AL_FORMAT_51CHN8 }, + { "AL_FORMAT_51CHN16", AL_FORMAT_51CHN16 }, + { "AL_FORMAT_51CHN32", AL_FORMAT_51CHN32 }, + { "AL_FORMAT_61CHN8", AL_FORMAT_61CHN8 }, + { "AL_FORMAT_61CHN16", AL_FORMAT_61CHN16 }, + { "AL_FORMAT_61CHN32", AL_FORMAT_61CHN32 }, + { "AL_FORMAT_71CHN8", AL_FORMAT_71CHN8 }, + { "AL_FORMAT_71CHN16", AL_FORMAT_71CHN16 }, + { "AL_FORMAT_71CHN32", AL_FORMAT_71CHN32 }, + { "AL_FORMAT_REAR8", AL_FORMAT_REAR8 }, + { "AL_FORMAT_REAR16", AL_FORMAT_REAR16 }, + { "AL_FORMAT_REAR32", AL_FORMAT_REAR32 }, + { "AL_FORMAT_MONO_MULAW", AL_FORMAT_MONO_MULAW }, + { "AL_FORMAT_MONO_MULAW_EXT", AL_FORMAT_MONO_MULAW }, + { "AL_FORMAT_STEREO_MULAW", AL_FORMAT_STEREO_MULAW }, + { "AL_FORMAT_STEREO_MULAW_EXT", AL_FORMAT_STEREO_MULAW }, + { "AL_FORMAT_QUAD_MULAW", AL_FORMAT_QUAD_MULAW }, + { "AL_FORMAT_51CHN_MULAW", AL_FORMAT_51CHN_MULAW }, + { "AL_FORMAT_61CHN_MULAW", AL_FORMAT_61CHN_MULAW }, + { "AL_FORMAT_71CHN_MULAW", AL_FORMAT_71CHN_MULAW }, + { "AL_FORMAT_REAR_MULAW", AL_FORMAT_REAR_MULAW }, + + // Buffer attributes + { "AL_FREQUENCY", AL_FREQUENCY }, + { "AL_BITS", AL_BITS }, + { "AL_CHANNELS", AL_CHANNELS }, + { "AL_SIZE", AL_SIZE }, + + // Buffer States (not supported yet) + { "AL_UNUSED", AL_UNUSED }, + { "AL_PENDING", AL_PENDING }, + { "AL_PROCESSED", AL_PROCESSED }, + + // AL Error Messages + { "AL_NO_ERROR", AL_NO_ERROR }, + { "AL_INVALID_NAME", AL_INVALID_NAME }, + { "AL_INVALID_ENUM", AL_INVALID_ENUM }, + { "AL_INVALID_VALUE", AL_INVALID_VALUE }, + { "AL_INVALID_OPERATION", AL_INVALID_OPERATION }, + { "AL_OUT_OF_MEMORY", AL_OUT_OF_MEMORY }, + + // Context strings + { "AL_VENDOR", AL_VENDOR }, + { "AL_VERSION", AL_VERSION }, + { "AL_RENDERER", AL_RENDERER }, + { "AL_EXTENSIONS", AL_EXTENSIONS }, + + // Global states + { "AL_DOPPLER_FACTOR", AL_DOPPLER_FACTOR }, + { "AL_DOPPLER_VELOCITY", AL_DOPPLER_VELOCITY }, + { "AL_DISTANCE_MODEL", AL_DISTANCE_MODEL }, + { "AL_SPEED_OF_SOUND", AL_SPEED_OF_SOUND }, + { "AL_SOURCE_DISTANCE_MODEL", AL_SOURCE_DISTANCE_MODEL }, + + // Distance Models + { "AL_INVERSE_DISTANCE", AL_INVERSE_DISTANCE }, + { "AL_INVERSE_DISTANCE_CLAMPED", AL_INVERSE_DISTANCE_CLAMPED }, + { "AL_LINEAR_DISTANCE", AL_LINEAR_DISTANCE }, + { "AL_LINEAR_DISTANCE_CLAMPED", AL_LINEAR_DISTANCE_CLAMPED }, + { "AL_EXPONENT_DISTANCE", AL_EXPONENT_DISTANCE }, + { "AL_EXPONENT_DISTANCE_CLAMPED", AL_EXPONENT_DISTANCE_CLAMPED }, + + // Filter types + { "AL_FILTER_TYPE", AL_FILTER_TYPE }, + { "AL_FILTER_NULL", AL_FILTER_NULL }, + { "AL_FILTER_LOWPASS", AL_FILTER_LOWPASS }, +#if 0 + { "AL_FILTER_HIGHPASS", AL_FILTER_HIGHPASS }, + { "AL_FILTER_BANDPASS", AL_FILTER_BANDPASS }, +#endif + + // Filter params + { "AL_LOWPASS_GAIN", AL_LOWPASS_GAIN }, + { "AL_LOWPASS_GAINHF", AL_LOWPASS_GAINHF }, + + // Effect types + { "AL_EFFECT_TYPE", AL_EFFECT_TYPE }, + { "AL_EFFECT_NULL", AL_EFFECT_NULL }, + { "AL_EFFECT_REVERB", AL_EFFECT_REVERB }, + { "AL_EFFECT_EAXREVERB", AL_EFFECT_EAXREVERB }, +#if 0 + { "AL_EFFECT_CHORUS", AL_EFFECT_CHORUS }, + { "AL_EFFECT_DISTORTION", AL_EFFECT_DISTORTION }, +#endif + { "AL_EFFECT_ECHO", AL_EFFECT_ECHO }, +#if 0 + { "AL_EFFECT_FLANGER", AL_EFFECT_FLANGER }, + { "AL_EFFECT_FREQUENCY_SHIFTER", AL_EFFECT_FREQUENCY_SHIFTER }, + { "AL_EFFECT_VOCAL_MORPHER", AL_EFFECT_VOCAL_MORPHER }, + { "AL_EFFECT_PITCH_SHIFTER", AL_EFFECT_PITCH_SHIFTER }, +#endif + { "AL_EFFECT_RING_MODULATOR", AL_EFFECT_RING_MODULATOR }, +#if 0 + { "AL_EFFECT_AUTOWAH", AL_EFFECT_AUTOWAH }, + { "AL_EFFECT_COMPRESSOR", AL_EFFECT_COMPRESSOR }, + { "AL_EFFECT_EQUALIZER", AL_EFFECT_EQUALIZER }, +#endif + + // Reverb params + { "AL_REVERB_DENSITY", AL_REVERB_DENSITY }, + { "AL_REVERB_DIFFUSION", AL_REVERB_DIFFUSION }, + { "AL_REVERB_GAIN", AL_REVERB_GAIN }, + { "AL_REVERB_GAINHF", AL_REVERB_GAINHF }, + { "AL_REVERB_DECAY_TIME", AL_REVERB_DECAY_TIME }, + { "AL_REVERB_DECAY_HFRATIO", AL_REVERB_DECAY_HFRATIO }, + { "AL_REVERB_REFLECTIONS_GAIN", AL_REVERB_REFLECTIONS_GAIN }, + { "AL_REVERB_REFLECTIONS_DELAY", AL_REVERB_REFLECTIONS_DELAY }, + { "AL_REVERB_LATE_REVERB_GAIN", AL_REVERB_LATE_REVERB_GAIN }, + { "AL_REVERB_LATE_REVERB_DELAY", AL_REVERB_LATE_REVERB_DELAY }, + { "AL_REVERB_AIR_ABSORPTION_GAINHF", AL_REVERB_AIR_ABSORPTION_GAINHF }, + { "AL_REVERB_ROOM_ROLLOFF_FACTOR", AL_REVERB_ROOM_ROLLOFF_FACTOR }, + { "AL_REVERB_DECAY_HFLIMIT", AL_REVERB_DECAY_HFLIMIT }, + + // EAX Reverb params + { "AL_EAXREVERB_DENSITY", AL_EAXREVERB_DENSITY }, + { "AL_EAXREVERB_DIFFUSION", AL_EAXREVERB_DIFFUSION }, + { "AL_EAXREVERB_GAIN", AL_EAXREVERB_GAIN }, + { "AL_EAXREVERB_GAINHF", AL_EAXREVERB_GAINHF }, + { "AL_EAXREVERB_GAINLF", AL_EAXREVERB_GAINLF }, + { "AL_EAXREVERB_DECAY_TIME", AL_EAXREVERB_DECAY_TIME }, + { "AL_EAXREVERB_DECAY_HFRATIO", AL_EAXREVERB_DECAY_HFRATIO }, + { "AL_EAXREVERB_DECAY_LFRATIO", AL_EAXREVERB_DECAY_LFRATIO }, + { "AL_EAXREVERB_REFLECTIONS_GAIN", AL_EAXREVERB_REFLECTIONS_GAIN }, + { "AL_EAXREVERB_REFLECTIONS_DELAY", AL_EAXREVERB_REFLECTIONS_DELAY }, + { "AL_EAXREVERB_REFLECTIONS_PAN", AL_EAXREVERB_REFLECTIONS_PAN }, + { "AL_EAXREVERB_LATE_REVERB_GAIN", AL_EAXREVERB_LATE_REVERB_GAIN }, + { "AL_EAXREVERB_LATE_REVERB_DELAY", AL_EAXREVERB_LATE_REVERB_DELAY }, + { "AL_EAXREVERB_LATE_REVERB_PAN", AL_EAXREVERB_LATE_REVERB_PAN }, + { "AL_EAXREVERB_ECHO_TIME", AL_EAXREVERB_ECHO_TIME }, + { "AL_EAXREVERB_ECHO_DEPTH", AL_EAXREVERB_ECHO_DEPTH }, + { "AL_EAXREVERB_MODULATION_TIME", AL_EAXREVERB_MODULATION_TIME }, + { "AL_EAXREVERB_MODULATION_DEPTH", AL_EAXREVERB_MODULATION_DEPTH }, + { "AL_EAXREVERB_AIR_ABSORPTION_GAINHF", AL_EAXREVERB_AIR_ABSORPTION_GAINHF }, + { "AL_EAXREVERB_HFREFERENCE", AL_EAXREVERB_HFREFERENCE }, + { "AL_EAXREVERB_LFREFERENCE", AL_EAXREVERB_LFREFERENCE }, + { "AL_EAXREVERB_ROOM_ROLLOFF_FACTOR", AL_EAXREVERB_ROOM_ROLLOFF_FACTOR }, + { "AL_EAXREVERB_DECAY_HFLIMIT", AL_EAXREVERB_DECAY_HFLIMIT }, + + // Echo params + { "AL_ECHO_DELAY", AL_ECHO_DELAY }, + { "AL_ECHO_LRDELAY", AL_ECHO_LRDELAY }, + { "AL_ECHO_DAMPING", AL_ECHO_DAMPING }, + { "AL_ECHO_FEEDBACK", AL_ECHO_FEEDBACK }, + { "AL_ECHO_SPREAD", AL_ECHO_SPREAD }, + + // Ring Modulator params + { "AL_RING_MODULATOR_FREQUENCY", AL_RING_MODULATOR_FREQUENCY }, + { "AL_RING_MODULATOR_HIGHPASS_CUTOFF", AL_RING_MODULATOR_HIGHPASS_CUTOFF }, + { "AL_RING_MODULATOR_WAVEFORM", AL_RING_MODULATOR_WAVEFORM }, + + + // Default + { NULL, (ALenum)0 } +}; + + + +AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName) +{ + ALboolean bIsSupported = AL_FALSE; + ALCcontext *pContext; + const char *ptr; + size_t len; + + pContext = GetContextSuspended(); + if(!pContext) return AL_FALSE; + + if(!extName) + { + alSetError(pContext, AL_INVALID_VALUE); + ProcessContext(pContext); + return AL_FALSE; + } + + len = strlen(extName); + ptr = pContext->ExtensionList; + while(ptr && *ptr) + { + if(strncasecmp(ptr, extName, len) == 0 && + (ptr[len] == '\0' || isspace(ptr[len]))) + { + bIsSupported = AL_TRUE; + break; + } + if((ptr=strchr(ptr, ' ')) != NULL) + { + do { + ++ptr; + } while(isspace(*ptr)); + } + } + + ProcessContext(pContext); + + return bIsSupported; +} + + +AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName) +{ + if(!funcName) + return NULL; + return alcGetProcAddress(NULL, funcName); +} + +AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName) +{ + ALsizei i = 0; + + while(enumeration[i].enumName && + strcmp(enumeration[i].enumName, enumName) != 0) + i++; + + return enumeration[i].value; +} diff --git a/jni/OpenAL/OpenAL32/alFilter.c b/jni/OpenAL/OpenAL32/alFilter.c new file mode 100644 index 0000000..7d6cda8 --- /dev/null +++ b/jni/OpenAL/OpenAL32/alFilter.c @@ -0,0 +1,432 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "alMain.h" +#include "alFilter.h" +#include "alThunk.h" +#include "alError.h" + + +static void InitFilterParams(ALfilter *filter, ALenum type); + +#define LookupFilter(m, k) ((ALfilter*)LookupUIntMapKey(&(m), (k))) + +AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) +{ + ALCcontext *Context; + ALsizei i=0; + + Context = GetContextSuspended(); + if(!Context) return; + + if(n < 0 || IsBadWritePtr((void*)filters, n * sizeof(ALuint))) + alSetError(Context, AL_INVALID_VALUE); + else + { + ALCdevice *device = Context->Device; + ALenum err; + + while(i < n) + { + ALfilter *filter = calloc(1, sizeof(ALfilter)); + if(!filter) + { + alSetError(Context, AL_OUT_OF_MEMORY); + alDeleteFilters(i, filters); + break; + } + + filter->filter = ALTHUNK_ADDENTRY(filter); + err = InsertUIntMapEntry(&device->FilterMap, filter->filter, filter); + if(err != AL_NO_ERROR) + { + ALTHUNK_REMOVEENTRY(filter->filter); + memset(filter, 0, sizeof(ALfilter)); + free(filter); + + alSetError(Context, err); + alDeleteFilters(i, filters); + break; + } + + filters[i++] = filter->filter; + InitFilterParams(filter, AL_FILTER_NULL); + } + } + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, ALuint *filters) +{ + ALCcontext *Context; + ALCdevice *device; + ALfilter *ALFilter; + ALboolean Failed; + ALsizei i; + + Context = GetContextSuspended(); + if(!Context) return; + + Failed = AL_TRUE; + device = Context->Device; + if(n < 0) + alSetError(Context, AL_INVALID_VALUE); + else + { + Failed = AL_FALSE; + // Check that all filters are valid + for(i = 0;i < n;i++) + { + if(!filters[i]) + continue; + + if(LookupFilter(device->FilterMap, filters[i]) == NULL) + { + alSetError(Context, AL_INVALID_NAME); + Failed = AL_TRUE; + break; + } + } + } + + if(!Failed) + { + // All filters are valid + for(i = 0;i < n;i++) + { + // Recheck that the filter is valid, because there could be duplicated names + if((ALFilter=LookupFilter(device->FilterMap, filters[i])) == NULL) + continue; + + RemoveUIntMapKey(&device->FilterMap, ALFilter->filter); + ALTHUNK_REMOVEENTRY(ALFilter->filter); + + memset(ALFilter, 0, sizeof(ALfilter)); + free(ALFilter); + } + } + + ProcessContext(Context); +} + +AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter) +{ + ALCcontext *Context; + ALboolean result; + + Context = GetContextSuspended(); + if(!Context) return AL_FALSE; + + result = ((!filter || LookupFilter(Context->Device->FilterMap, filter)) ? + AL_TRUE : AL_FALSE); + + ProcessContext(Context); + + return result; +} + +AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue) +{ + ALCcontext *Context; + ALCdevice *Device; + ALfilter *ALFilter; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALFilter=LookupFilter(Device->FilterMap, filter)) != NULL) + { + switch(param) + { + case AL_FILTER_TYPE: + if(iValue == AL_FILTER_NULL || + iValue == AL_FILTER_LOWPASS) + InitFilterParams(ALFilter, iValue); + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, ALint *piValues) +{ + ALCcontext *Context; + ALCdevice *Device; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if(LookupFilter(Device->FilterMap, filter) != NULL) + { + switch(param) + { + case AL_FILTER_TYPE: + alFilteri(filter, param, piValues[0]); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flArg) +{ + ALCcontext *Context; + ALCdevice *Device; + ALfilter *ALFilter; + ALfp flValue = float2ALfp(flArg); + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALFilter=LookupFilter(Device->FilterMap, filter)) != NULL) + { + switch(ALFilter->type) + { + case AL_FILTER_LOWPASS: + switch(param) + { + case AL_LOWPASS_GAIN: + if(flValue >= int2ALfp(0) && flValue <= int2ALfp(1)) + ALFilter->Gain = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + case AL_LOWPASS_GAINHF: + if(flValue >= int2ALfp(0) && flValue <= int2ALfp(1)) + ALFilter->GainHF = flValue; + else + alSetError(Context, AL_INVALID_VALUE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, ALfloat *pflValues) +{ + ALCcontext *Context; + ALCdevice *Device; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if(LookupFilter(Device->FilterMap, filter) != NULL) + { + switch(param) + { + default: + alFilterf(filter, param, pflValues[0]); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue) +{ + ALCcontext *Context; + ALCdevice *Device; + ALfilter *ALFilter; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALFilter=LookupFilter(Device->FilterMap, filter)) != NULL) + { + switch(param) + { + case AL_FILTER_TYPE: + *piValue = ALFilter->type; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues) +{ + ALCcontext *Context; + ALCdevice *Device; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if(LookupFilter(Device->FilterMap, filter) != NULL) + { + switch(param) + { + case AL_FILTER_TYPE: + alGetFilteri(filter, param, piValues); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue) +{ + ALCcontext *Context; + ALCdevice *Device; + ALfilter *ALFilter; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if((ALFilter=LookupFilter(Device->FilterMap, filter)) != NULL) + { + switch(ALFilter->type) + { + case AL_FILTER_LOWPASS: + switch(param) + { + case AL_LOWPASS_GAIN: + *pflValue = ALfp2float(ALFilter->Gain); + break; + + case AL_LOWPASS_GAINHF: + *pflValue = ALfp2float(ALFilter->GainHF); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues) +{ + ALCcontext *Context; + ALCdevice *Device; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if(LookupFilter(Device->FilterMap, filter) != NULL) + { + switch(param) + { + default: + alGetFilterf(filter, param, pflValues); + break; + } + } + else + alSetError(Context, AL_INVALID_NAME); + + ProcessContext(Context); +} + + +ALvoid ReleaseALFilters(ALCdevice *device) +{ + ALsizei i; + for(i = 0;i < device->FilterMap.size;i++) + { + ALfilter *temp = device->FilterMap.array[i].value; + device->FilterMap.array[i].value = NULL; + + // Release filter structure + ALTHUNK_REMOVEENTRY(temp->filter); + memset(temp, 0, sizeof(ALfilter)); + free(temp); + } +} + + +static void InitFilterParams(ALfilter *filter, ALenum type) +{ + filter->type = type; + + filter->Gain = int2ALfp(1); + filter->GainHF = int2ALfp(1); +} diff --git a/jni/OpenAL/OpenAL32/alListener.c b/jni/OpenAL/OpenAL32/alListener.c new file mode 100644 index 0000000..c21c605 --- /dev/null +++ b/jni/OpenAL/OpenAL32/alListener.c @@ -0,0 +1,489 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "alMain.h" +#include "AL/alc.h" +#include "alError.h" +#include "alListener.h" +#include "alSource.h" + +AL_API ALvoid AL_APIENTRY alListenerf(ALenum eParam, ALfloat flArg) +{ + ALCcontext *pContext; + ALboolean updateAll = AL_FALSE; + ALfp flValue = float2ALfp(flArg); + + pContext = GetContextSuspended(); + if(!pContext) return; + + switch(eParam) + { + case AL_GAIN: + if(flValue >= int2ALfp(0)) + { + pContext->Listener.Gain = flValue; + updateAll = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_METERS_PER_UNIT: + if(flValue > int2ALfp(0)) + { + pContext->Listener.MetersPerUnit = flValue; + updateAll = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + + // Force updating the sources for these parameters, since even head- + // relative sources are affected + if(updateAll) + { + ALsizei pos; + for(pos = 0;pos < pContext->SourceMap.size;pos++) + { + ALsource *source = pContext->SourceMap.array[pos].value; + source->NeedsUpdate = AL_TRUE; + } + } + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alListener3f(ALenum eParam, ALfloat flArg1, ALfloat flArg2, ALfloat flArg3) +{ + ALCcontext *pContext; + ALboolean updateWorld = AL_FALSE; + ALfp flValue1 = float2ALfp(flArg1); + ALfp flValue2 = float2ALfp(flArg2); + ALfp flValue3 = float2ALfp(flArg3); + + + pContext = GetContextSuspended(); + if(!pContext) return; + + switch(eParam) + { + case AL_POSITION: + pContext->Listener.Position[0] = flValue1; + pContext->Listener.Position[1] = flValue2; + pContext->Listener.Position[2] = flValue3; + updateWorld = AL_TRUE; + break; + + case AL_VELOCITY: + pContext->Listener.Velocity[0] = flValue1; + pContext->Listener.Velocity[1] = flValue2; + pContext->Listener.Velocity[2] = flValue3; + updateWorld = AL_TRUE; + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + + if(updateWorld) + { + ALsizei pos; + for(pos = 0;pos < pContext->SourceMap.size;pos++) + { + ALsource *source = pContext->SourceMap.array[pos].value; + if(!source->bHeadRelative) + source->NeedsUpdate = AL_TRUE; + } + } + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alListenerfv(ALenum eParam, const ALfloat *pflValues) +{ + ALCcontext *pContext; + ALboolean updateWorld = AL_FALSE; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(pflValues) + { + switch(eParam) + { + case AL_GAIN: + case AL_METERS_PER_UNIT: + alListenerf(eParam, pflValues[0]); + break; + + case AL_POSITION: + case AL_VELOCITY: + alListener3f(eParam, pflValues[0], pflValues[1], pflValues[2]); + break; + + case AL_ORIENTATION: + // AT then UP + pContext->Listener.Forward[0] = float2ALfp(pflValues[0]); + pContext->Listener.Forward[1] = float2ALfp(pflValues[1]); + pContext->Listener.Forward[2] = float2ALfp(pflValues[2]); + pContext->Listener.Up[0] = float2ALfp(pflValues[3]); + pContext->Listener.Up[1] = float2ALfp(pflValues[4]); + pContext->Listener.Up[2] = float2ALfp(pflValues[5]); + updateWorld = AL_TRUE; + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_VALUE); + + if(updateWorld) + { + ALsizei pos; + for(pos = 0;pos < pContext->SourceMap.size;pos++) + { + ALsource *source = pContext->SourceMap.array[pos].value; + if(!source->bHeadRelative) + source->NeedsUpdate = AL_TRUE; + } + } + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alListeneri(ALenum eParam, ALint lValue) +{ + ALCcontext *pContext; + + (void)lValue; + + pContext = GetContextSuspended(); + if(!pContext) return; + + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alListener3i(ALenum eParam, ALint lValue1, ALint lValue2, ALint lValue3) +{ + ALCcontext *pContext; + + pContext = GetContextSuspended(); + if(!pContext) return; + + switch(eParam) + { + case AL_POSITION: + case AL_VELOCITY: + alListener3f(eParam, (ALfloat)lValue1, (ALfloat)lValue2, (ALfloat)lValue3); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alListeneriv( ALenum eParam, const ALint* plValues ) +{ + ALCcontext *pContext; + ALfloat flValues[6]; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(plValues) + { + switch(eParam) + { + case AL_POSITION: + case AL_VELOCITY: + flValues[0] = (ALfloat)plValues[0]; + flValues[1] = (ALfloat)plValues[1]; + flValues[2] = (ALfloat)plValues[2]; + alListenerfv(eParam, flValues); + break; + + case AL_ORIENTATION: + flValues[0] = (ALfloat)plValues[0]; + flValues[1] = (ALfloat)plValues[1]; + flValues[2] = (ALfloat)plValues[2]; + flValues[3] = (ALfloat)plValues[3]; + flValues[4] = (ALfloat)plValues[4]; + flValues[5] = (ALfloat)plValues[5]; + alListenerfv(eParam, flValues); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetListenerf(ALenum eParam, ALfloat *pflValue) +{ + ALCcontext *pContext; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(pflValue) + { + switch(eParam) + { + case AL_GAIN: + *pflValue = ALfp2float(pContext->Listener.Gain); + break; + + case AL_METERS_PER_UNIT: + *pflValue = ALfp2float(pContext->Listener.MetersPerUnit); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetListener3f(ALenum eParam, ALfloat *pflValue1, ALfloat *pflValue2, ALfloat *pflValue3) +{ + ALCcontext *pContext; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(pflValue1 && pflValue2 && pflValue3) + { + switch(eParam) + { + case AL_POSITION: + *pflValue1 = ALfp2float(pContext->Listener.Position[0]); + *pflValue2 = ALfp2float(pContext->Listener.Position[1]); + *pflValue3 = ALfp2float(pContext->Listener.Position[2]); + break; + + case AL_VELOCITY: + *pflValue1 = ALfp2float(pContext->Listener.Velocity[0]); + *pflValue2 = ALfp2float(pContext->Listener.Velocity[1]); + *pflValue3 = ALfp2float(pContext->Listener.Velocity[2]); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetListenerfv(ALenum eParam, ALfloat *pflValues) +{ + ALCcontext *pContext; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(pflValues) + { + switch(eParam) + { + case AL_GAIN: + pflValues[0] = ALfp2float(pContext->Listener.Gain); + break; + + case AL_METERS_PER_UNIT: + pflValues[0] = ALfp2float(pContext->Listener.MetersPerUnit); + break; + + case AL_POSITION: + pflValues[0] = ALfp2float(pContext->Listener.Position[0]); + pflValues[1] = ALfp2float(pContext->Listener.Position[1]); + pflValues[2] = ALfp2float(pContext->Listener.Position[2]); + break; + + case AL_VELOCITY: + pflValues[0] = ALfp2float(pContext->Listener.Velocity[0]); + pflValues[1] = ALfp2float(pContext->Listener.Velocity[1]); + pflValues[2] = ALfp2float(pContext->Listener.Velocity[2]); + break; + + case AL_ORIENTATION: + // AT then UP + pflValues[0] = ALfp2float(pContext->Listener.Forward[0]); + pflValues[1] = ALfp2float(pContext->Listener.Forward[1]); + pflValues[2] = ALfp2float(pContext->Listener.Forward[2]); + pflValues[3] = ALfp2float(pContext->Listener.Up[0]); + pflValues[4] = ALfp2float(pContext->Listener.Up[1]); + pflValues[5] = ALfp2float(pContext->Listener.Up[2]); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetListeneri(ALenum eParam, ALint *plValue) +{ + ALCcontext *pContext; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(plValue) + { + switch(eParam) + { + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alGetListener3i(ALenum eParam, ALint *plValue1, ALint *plValue2, ALint *plValue3) +{ + ALCcontext *pContext; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(plValue1 && plValue2 && plValue3) + { + switch (eParam) + { + case AL_POSITION: + *plValue1 = (ALint)ALfp2int(pContext->Listener.Position[0]); + *plValue2 = (ALint)ALfp2int(pContext->Listener.Position[1]); + *plValue3 = (ALint)ALfp2int(pContext->Listener.Position[2]); + break; + + case AL_VELOCITY: + *plValue1 = (ALint)ALfp2int(pContext->Listener.Velocity[0]); + *plValue2 = (ALint)ALfp2int(pContext->Listener.Velocity[1]); + *plValue3 = (ALint)ALfp2int(pContext->Listener.Velocity[2]); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alGetListeneriv(ALenum eParam, ALint* plValues) +{ + ALCcontext *pContext; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(plValues) + { + switch(eParam) + { + case AL_POSITION: + plValues[0] = (ALint)ALfp2int(pContext->Listener.Position[0]); + plValues[1] = (ALint)ALfp2int(pContext->Listener.Position[1]); + plValues[2] = (ALint)ALfp2int(pContext->Listener.Position[2]); + break; + + case AL_VELOCITY: + plValues[0] = (ALint)ALfp2int(pContext->Listener.Velocity[0]); + plValues[1] = (ALint)ALfp2int(pContext->Listener.Velocity[1]); + plValues[2] = (ALint)ALfp2int(pContext->Listener.Velocity[2]); + break; + + case AL_ORIENTATION: + // AT then UP + plValues[0] = (ALint)ALfp2int(pContext->Listener.Forward[0]); + plValues[1] = (ALint)ALfp2int(pContext->Listener.Forward[1]); + plValues[2] = (ALint)ALfp2int(pContext->Listener.Forward[2]); + plValues[3] = (ALint)ALfp2int(pContext->Listener.Up[0]); + plValues[4] = (ALint)ALfp2int(pContext->Listener.Up[1]); + plValues[5] = (ALint)ALfp2int(pContext->Listener.Up[2]); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} diff --git a/jni/OpenAL/OpenAL32/alSource.c b/jni/OpenAL/OpenAL32/alSource.c new file mode 100644 index 0000000..b5d6ede --- /dev/null +++ b/jni/OpenAL/OpenAL32/alSource.c @@ -0,0 +1,2091 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include +#include +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" +#include "alError.h" +#include "alSource.h" +#include "alBuffer.h" +#include "alThunk.h" +#include "alAuxEffectSlot.h" + +#ifdef ANDROID +//#include + +// Apportable: Defines a cap on the maximum number of playing sources +extern int alc_max_sources; +extern int alc_active_sources; +#endif + +resampler_t DefaultResampler; +const ALsizei ResamplerPadding[RESAMPLER_MAX] = { + 0, /* Point */ + 1, /* Linear */ + 2, /* Cubic */ +}; +const ALsizei ResamplerPrePadding[RESAMPLER_MAX] = { + 0, /* Point */ + 0, /* Linear */ + 1, /* Cubic */ +}; + + +static ALvoid InitSourceParams(ALsource *Source); +static ALvoid GetSourceOffset(ALsource *Source, ALenum eName, ALdfp *Offsets, ALdfp updateLen); +static ALboolean ApplyOffset(ALsource *Source); +static ALint GetByteOffset(ALsource *Source); + +#define LookupSource(m, k) ((ALsource*)LookupUIntMapKey(&(m), (k))) +#define LookupBuffer(m, k) ((ALbuffer*)LookupUIntMapKey(&(m), (k))) +#define LookupFilter(m, k) ((ALfilter*)LookupUIntMapKey(&(m), (k))) +#define LookupEffectSlot(m, k) ((ALeffectslot*)LookupUIntMapKey(&(m), (k))) + +AL_API ALvoid AL_APIENTRY alGenSources(ALsizei n,ALuint *sources) +{ + ALCcontext *Context; + ALCdevice *Device; + + Context = GetContextSuspended(); + if(!Context) return; + + Device = Context->Device; + if(n < 0 || IsBadWritePtr((void*)sources, n * sizeof(ALuint))) + alSetError(Context, AL_INVALID_VALUE); + else if((ALuint)n > Device->MaxNoOfSources - Context->SourceMap.size) + alSetError(Context, AL_INVALID_VALUE); + else + { + ALenum err; + ALsizei i; + + // Add additional sources to the list + i = 0; + while(i < n) + { + ALsource *source = calloc(1, sizeof(ALsource)); + if(!source) + { + alSetError(Context, AL_OUT_OF_MEMORY); + alDeleteSources(i, sources); + break; + } + + source->source = (ALuint)ALTHUNK_ADDENTRY(source); + err = InsertUIntMapEntry(&Context->SourceMap, source->source, + source); + if(err != AL_NO_ERROR) + { + ALTHUNK_REMOVEENTRY(source->source); + memset(source, 0, sizeof(ALsource)); + free(source); + + alSetError(Context, err); + alDeleteSources(i, sources); + break; + } + + sources[i++] = source->source; + InitSourceParams(source); + } + } + + ProcessContext(Context); +} + + +AL_API ALvoid AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) +{ + ALCcontext *Context; + ALsource *Source; + ALsizei i, j; + ALbufferlistitem *BufferList; + ALboolean SourcesValid = AL_FALSE; + + Context = GetContextSuspended(); + if(!Context) return; + + if(n < 0) + alSetError(Context, AL_INVALID_VALUE); + else + { + SourcesValid = AL_TRUE; + // Check that all Sources are valid (and can therefore be deleted) + for(i = 0;i < n;i++) + { + if(LookupSource(Context->SourceMap, sources[i]) == NULL) + { + alSetError(Context, AL_INVALID_NAME); + SourcesValid = AL_FALSE; + break; + } + } + } + + if(SourcesValid) + { + // All Sources are valid, and can be deleted + for(i = 0;i < n;i++) + { + // Recheck that the Source is valid, because there could be duplicated Source names + if((Source=LookupSource(Context->SourceMap, sources[i])) == NULL) + continue; + + for(j = 0;j < Context->ActiveSourceCount;j++) + { + if(Context->ActiveSources[j] == Source) + { + ALsizei end = --(Context->ActiveSourceCount); + Context->ActiveSources[j] = Context->ActiveSources[end]; + break; + } + } + + // For each buffer in the source's queue... + while(Source->queue != NULL) + { + BufferList = Source->queue; + Source->queue = BufferList->next; + + if(BufferList->buffer != NULL) + BufferList->buffer->refcount--; + free(BufferList); + } + + for(j = 0;j < MAX_SENDS;++j) + { + if(Source->Send[j].Slot) + Source->Send[j].Slot->refcount--; + Source->Send[j].Slot = NULL; + } + + // Remove Source from list of Sources + RemoveUIntMapKey(&Context->SourceMap, Source->source); + ALTHUNK_REMOVEENTRY(Source->source); + + memset(Source,0,sizeof(ALsource)); + free(Source); + } + } + + ProcessContext(Context); +} + + +AL_API ALboolean AL_APIENTRY alIsSource(ALuint source) +{ + ALCcontext *Context; + ALboolean result; + + Context = GetContextSuspended(); + if(!Context) return AL_FALSE; + + result = (LookupSource(Context->SourceMap, source) ? AL_TRUE : AL_FALSE); + + ProcessContext(Context); + + return result; +} + + +AL_API ALvoid AL_APIENTRY alSourcef(ALuint source, ALenum eParam, ALfloat flValue) +{ + ALCcontext *pContext; + ALsource *Source; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if((Source=LookupSource(pContext->SourceMap, source)) != NULL) + { + switch(eParam) + { + case AL_PITCH: + if(flValue >= 0.0f) + { + Source->flPitch = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_CONE_INNER_ANGLE: + if(flValue >= 0.0f && flValue <= 360.0f) + { + Source->flInnerAngle = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_CONE_OUTER_ANGLE: + if(flValue >= 0.0f && flValue <= 360.0f) + { + Source->flOuterAngle = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_GAIN: + if(flValue >= 0.0f) + { + Source->flGain = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_MAX_DISTANCE: + if(flValue >= 0.0f) + { + Source->flMaxDistance = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_ROLLOFF_FACTOR: + if(flValue >= 0.0f) + { + Source->flRollOffFactor = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_REFERENCE_DISTANCE: + if(flValue >= 0.0f) + { + Source->flRefDistance = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_MIN_GAIN: + if(flValue >= 0.0f && flValue <= 1.0f) + { + Source->flMinGain = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_MAX_GAIN: + if(flValue >= 0.0f && flValue <= 1.0f) + { + Source->flMaxGain = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_CONE_OUTER_GAIN: + if(flValue >= 0.0f && flValue <= 1.0f) + { + Source->flOuterGain = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_CONE_OUTER_GAINHF: + if(flValue >= 0.0f && flValue <= 1.0f) + { + Source->OuterGainHF = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_AIR_ABSORPTION_FACTOR: + if(flValue >= 0.0f && flValue <= 10.0f) + { + Source->AirAbsorptionFactor = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_ROOM_ROLLOFF_FACTOR: + if(flValue >= 0.0f && flValue <= 10.0f) + { + Source->RoomRolloffFactor = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_DOPPLER_FACTOR: + if(flValue >= 0.0f && flValue <= 1.0f) + { + Source->DopplerFactor = float2ALfp(flValue); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + if(flValue >= 0.0f) + { + Source->lOffsetType = eParam; + + // Store Offset (convert Seconds into Milliseconds) + if(eParam == AL_SEC_OFFSET) + Source->lOffset = (ALint)(flValue * 1000.0f); + else + Source->lOffset = (ALint)flValue; + + if ((Source->state == AL_PLAYING) || (Source->state == AL_PAUSED)) + { + if(ApplyOffset(Source) == AL_FALSE) + alSetError(pContext, AL_INVALID_VALUE); + } + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + { + // Invalid Source Name + alSetError(pContext, AL_INVALID_NAME); + } + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alSource3f(ALuint source, ALenum eParam, ALfloat flValue1,ALfloat flValue2,ALfloat flValue3) +{ + ALCcontext *pContext; + ALsource *Source; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if((Source=LookupSource(pContext->SourceMap, source)) != NULL) + { + switch(eParam) + { + case AL_POSITION: + Source->vPosition[0] = float2ALfp(flValue1); + Source->vPosition[1] = float2ALfp(flValue2); + Source->vPosition[2] = float2ALfp(flValue3); + Source->NeedsUpdate = AL_TRUE; + break; + + case AL_VELOCITY: + Source->vVelocity[0] = float2ALfp(flValue1); + Source->vVelocity[1] = float2ALfp(flValue2); + Source->vVelocity[2] = float2ALfp(flValue3); + Source->NeedsUpdate = AL_TRUE; + break; + + case AL_DIRECTION: + Source->vOrientation[0] = float2ALfp(flValue1); + Source->vOrientation[1] = float2ALfp(flValue2); + Source->vOrientation[2] = float2ALfp(flValue3); + Source->NeedsUpdate = AL_TRUE; + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alSourcefv(ALuint source, ALenum eParam, const ALfloat *pflValues) +{ + ALCcontext *pContext; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(pflValues) + { + if(LookupSource(pContext->SourceMap, source) != NULL) + { + switch(eParam) + { + case AL_PITCH: + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_GAIN: + case AL_MAX_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_REFERENCE_DISTANCE: + case AL_MIN_GAIN: + case AL_MAX_GAIN: + case AL_CONE_OUTER_GAIN: + case AL_CONE_OUTER_GAINHF: + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_AIR_ABSORPTION_FACTOR: + case AL_ROOM_ROLLOFF_FACTOR: + alSourcef(source, eParam, pflValues[0]); + break; + + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + alSource3f(source, eParam, pflValues[0], pflValues[1], pflValues[2]); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alSourcei(ALuint source,ALenum eParam,ALint lValue) +{ + ALCcontext *pContext; + ALsource *Source; + ALbufferlistitem *BufferListItem; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if((Source=LookupSource(pContext->SourceMap, source)) != NULL) + { + ALCdevice *device = pContext->Device; + + switch(eParam) + { + case AL_MAX_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_REFERENCE_DISTANCE: + alSourcef(source, eParam, (ALfloat)lValue); + break; + + case AL_SOURCE_RELATIVE: + if(lValue == AL_FALSE || lValue == AL_TRUE) + { + Source->bHeadRelative = (ALboolean)lValue; + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_LOOPING: + if(lValue == AL_FALSE || lValue == AL_TRUE) + Source->bLooping = (ALboolean)lValue; + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_BUFFER: + if(Source->state == AL_STOPPED || Source->state == AL_INITIAL) + { + ALbuffer *buffer = NULL; + + if(lValue == 0 || + (buffer=LookupBuffer(device->BufferMap, lValue)) != NULL) + { + // Remove all elements in the queue + while(Source->queue != NULL) + { + BufferListItem = Source->queue; + Source->queue = BufferListItem->next; + + if(BufferListItem->buffer) + BufferListItem->buffer->refcount--; + free(BufferListItem); + } + Source->BuffersInQueue = 0; + + // Add the buffer to the queue (as long as it is NOT the NULL buffer) + if(buffer != NULL) + { + // Source is now in STATIC mode + Source->lSourceType = AL_STATIC; + + // Add the selected buffer to the queue + BufferListItem = malloc(sizeof(ALbufferlistitem)); + BufferListItem->buffer = buffer; + BufferListItem->next = NULL; + BufferListItem->prev = NULL; + + Source->queue = BufferListItem; + Source->BuffersInQueue = 1; + + if(buffer->FmtChannels == FmtMono) + Source->Update = CalcSourceParams; + else + Source->Update = CalcNonAttnSourceParams; + + // Increment reference counter for buffer + buffer->refcount++; + } + else + { + // Source is now in UNDETERMINED mode + Source->lSourceType = AL_UNDETERMINED; + } + Source->BuffersPlayed = 0; + + // Update AL_BUFFER parameter + Source->Buffer = buffer; + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + } + else + alSetError(pContext, AL_INVALID_OPERATION); + break; + + case AL_SOURCE_STATE: + // Query only + alSetError(pContext, AL_INVALID_OPERATION); + break; + + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + if(lValue >= 0) + { + Source->lOffsetType = eParam; + + // Store Offset (convert Seconds into Milliseconds) + if(eParam == AL_SEC_OFFSET) + Source->lOffset = lValue * 1000; + else + Source->lOffset = lValue; + + if(Source->state == AL_PLAYING || Source->state == AL_PAUSED) + { + if(ApplyOffset(Source) == AL_FALSE) + alSetError(pContext, AL_INVALID_VALUE); + } + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_DIRECT_FILTER: { + ALfilter *filter = NULL; + + if(lValue == 0 || + (filter=LookupFilter(pContext->Device->FilterMap, lValue)) != NULL) + { + if(!filter) + { + Source->DirectFilter.type = AL_FILTER_NULL; + Source->DirectFilter.filter = 0; + } + else + memcpy(&Source->DirectFilter, filter, sizeof(*filter)); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + } break; + + case AL_DIRECT_FILTER_GAINHF_AUTO: + if(lValue == AL_TRUE || lValue == AL_FALSE) + { + Source->DryGainHFAuto = lValue; + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + if(lValue == AL_TRUE || lValue == AL_FALSE) + { + Source->WetGainAuto = lValue; + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + if(lValue == AL_TRUE || lValue == AL_FALSE) + { + Source->WetGainHFAuto = lValue; + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + case AL_DISTANCE_MODEL: + if(lValue == AL_NONE || + lValue == AL_INVERSE_DISTANCE || + lValue == AL_INVERSE_DISTANCE_CLAMPED || + lValue == AL_LINEAR_DISTANCE || + lValue == AL_LINEAR_DISTANCE_CLAMPED || + lValue == AL_EXPONENT_DISTANCE || + lValue == AL_EXPONENT_DISTANCE_CLAMPED) + { + Source->DistanceModel = lValue; + if(pContext->SourceDistanceModel) + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum eParam, ALint lValue1, ALint lValue2, ALint lValue3) +{ + ALCcontext *pContext; + ALsource *Source; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if((Source=LookupSource(pContext->SourceMap, source)) != NULL) + { + ALCdevice *device = pContext->Device; + + switch (eParam) + { + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + alSource3f(source, eParam, (ALfloat)lValue1, (ALfloat)lValue2, (ALfloat)lValue3); + break; + + case AL_AUXILIARY_SEND_FILTER: { + ALeffectslot *ALEffectSlot = NULL; + ALfilter *ALFilter = NULL; + + if((ALuint)lValue2 < device->NumAuxSends && + (lValue1 == 0 || + (ALEffectSlot=LookupEffectSlot(pContext->EffectSlotMap, lValue1)) != NULL) && + (lValue3 == 0 || + (ALFilter=LookupFilter(device->FilterMap, lValue3)) != NULL)) + { + /* Release refcount on the previous slot, and add one for + * the new slot */ + if(Source->Send[lValue2].Slot) + Source->Send[lValue2].Slot->refcount--; + Source->Send[lValue2].Slot = ALEffectSlot; + if(Source->Send[lValue2].Slot) + Source->Send[lValue2].Slot->refcount++; + + if(!ALFilter) + { + /* Disable filter */ + Source->Send[lValue2].WetFilter.type = 0; + Source->Send[lValue2].WetFilter.filter = 0; + } + else + memcpy(&Source->Send[lValue2].WetFilter, ALFilter, sizeof(*ALFilter)); + Source->NeedsUpdate = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + } break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum eParam, const ALint* plValues) +{ + ALCcontext *pContext; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(plValues) + { + if(LookupSource(pContext->SourceMap, source) != NULL) + { + switch(eParam) + { + case AL_SOURCE_RELATIVE: + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_LOOPING: + case AL_BUFFER: + case AL_SOURCE_STATE: + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_MAX_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_REFERENCE_DISTANCE: + case AL_DIRECT_FILTER: + case AL_DIRECT_FILTER_GAINHF_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + case AL_DISTANCE_MODEL: + alSourcei(source, eParam, plValues[0]); + break; + + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + case AL_AUXILIARY_SEND_FILTER: + alSource3i(source, eParam, plValues[0], plValues[1], plValues[2]); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetSourcef(ALuint source, ALenum eParam, ALfloat *pflValue) +{ + ALCcontext *pContext; + ALsource *Source; + ALdfp Offsets[2]; + ALdfp updateLen; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(pflValue) + { + if((Source=LookupSource(pContext->SourceMap, source)) != NULL) + { + switch(eParam) + { + case AL_PITCH: + *pflValue = ALfp2float(Source->flPitch); + break; + + case AL_GAIN: + *pflValue = ALfp2float(Source->flGain); + break; + + case AL_MIN_GAIN: + *pflValue = ALfp2float(Source->flMinGain); + break; + + case AL_MAX_GAIN: + *pflValue = ALfp2float(Source->flMaxGain); + break; + + case AL_MAX_DISTANCE: + *pflValue = ALfp2float(Source->flMaxDistance); + break; + + case AL_ROLLOFF_FACTOR: + *pflValue = ALfp2float(Source->flRollOffFactor); + break; + + case AL_CONE_OUTER_GAIN: + *pflValue = ALfp2float(Source->flOuterGain); + break; + + case AL_CONE_OUTER_GAINHF: + *pflValue = ALfp2float(Source->OuterGainHF); + break; + + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + updateLen = ALdfpDiv(int2ALdfp(pContext->Device->UpdateSize), + int2ALdfp(pContext->Device->Frequency)); + GetSourceOffset(Source, eParam, Offsets, updateLen); + *pflValue = (ALfloat)ALdfp2double(Offsets[0]); + break; + + case AL_CONE_INNER_ANGLE: + *pflValue = ALfp2float(Source->flInnerAngle); + break; + + case AL_CONE_OUTER_ANGLE: + *pflValue = ALfp2float(Source->flOuterAngle); + break; + + case AL_REFERENCE_DISTANCE: + *pflValue = ALfp2float(Source->flRefDistance); + break; + + case AL_AIR_ABSORPTION_FACTOR: + *pflValue = ALfp2float(Source->AirAbsorptionFactor); + break; + + case AL_ROOM_ROLLOFF_FACTOR: + *pflValue = ALfp2float(Source->RoomRolloffFactor); + break; + + case AL_DOPPLER_FACTOR: + *pflValue = ALfp2float(Source->DopplerFactor); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetSource3f(ALuint source, ALenum eParam, ALfloat* pflValue1, ALfloat* pflValue2, ALfloat* pflValue3) +{ + ALCcontext *pContext; + ALsource *Source; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(pflValue1 && pflValue2 && pflValue3) + { + if((Source=LookupSource(pContext->SourceMap, source)) != NULL) + { + switch(eParam) + { + case AL_POSITION: + *pflValue1 = ALfp2float(Source->vPosition[0]); + *pflValue2 = ALfp2float(Source->vPosition[1]); + *pflValue3 = ALfp2float(Source->vPosition[2]); + break; + + case AL_VELOCITY: + *pflValue1 = ALfp2float(Source->vVelocity[0]); + *pflValue2 = ALfp2float(Source->vVelocity[1]); + *pflValue3 = ALfp2float(Source->vVelocity[2]); + break; + + case AL_DIRECTION: + *pflValue1 = ALfp2float(Source->vOrientation[0]); + *pflValue2 = ALfp2float(Source->vOrientation[1]); + *pflValue3 = ALfp2float(Source->vOrientation[2]); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetSourcefv(ALuint source, ALenum eParam, ALfloat *pflValues) +{ + ALCcontext *pContext; + ALsource *Source; + ALdfp Offsets[2]; + ALdfp updateLen; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(pflValues) + { + if((Source=LookupSource(pContext->SourceMap, source)) != NULL) + { + switch(eParam) + { + case AL_PITCH: + case AL_GAIN: + case AL_MIN_GAIN: + case AL_MAX_GAIN: + case AL_MAX_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_DOPPLER_FACTOR: + case AL_CONE_OUTER_GAIN: + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_REFERENCE_DISTANCE: + case AL_CONE_OUTER_GAINHF: + case AL_AIR_ABSORPTION_FACTOR: + case AL_ROOM_ROLLOFF_FACTOR: + alGetSourcef(source, eParam, pflValues); + break; + + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + alGetSource3f(source, eParam, pflValues+0, pflValues+1, pflValues+2); + break; + + case AL_SAMPLE_RW_OFFSETS_SOFT: + case AL_BYTE_RW_OFFSETS_SOFT: + updateLen = ALdfpDiv(int2ALdfp(pContext->Device->UpdateSize), + int2ALdfp(pContext->Device->Frequency)); + GetSourceOffset(Source, eParam, Offsets, updateLen); + pflValues[0] = (ALfloat)ALdfp2double(Offsets[0]); + pflValues[1] = (ALfloat)ALdfp2double(Offsets[1]); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alGetSourcei(ALuint source, ALenum eParam, ALint *plValue) +{ + ALCcontext *pContext; + ALsource *Source; + ALdfp Offsets[2]; + ALdfp updateLen; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(plValue) + { + if((Source=LookupSource(pContext->SourceMap, source)) != NULL) + { + switch(eParam) + { + case AL_MAX_DISTANCE: + *plValue = (ALint)ALfp2int(Source->flMaxDistance); + break; + + case AL_ROLLOFF_FACTOR: + *plValue = (ALint)ALfp2int(Source->flRollOffFactor); + break; + + case AL_REFERENCE_DISTANCE: + *plValue = (ALint)ALfp2int(Source->flRefDistance); + break; + + case AL_SOURCE_RELATIVE: + *plValue = Source->bHeadRelative; + break; + + case AL_CONE_INNER_ANGLE: + *plValue = (ALint)ALfp2int(Source->flInnerAngle); + break; + + case AL_CONE_OUTER_ANGLE: + *plValue = (ALint)ALfp2int(Source->flOuterAngle); + break; + + case AL_LOOPING: + *plValue = Source->bLooping; + break; + + case AL_BUFFER: + *plValue = (Source->Buffer ? Source->Buffer->buffer : 0); + break; + + case AL_SOURCE_STATE: + *plValue = Source->state; + break; + + case AL_BUFFERS_QUEUED: + *plValue = Source->BuffersInQueue; + break; + + case AL_BUFFERS_PROCESSED: + if(Source->bLooping || Source->lSourceType != AL_STREAMING) + { + /* Buffers on a looping source are in a perpetual state + * of PENDING, so don't report any as PROCESSED */ + *plValue = 0; + } + else + *plValue = Source->BuffersPlayed; + break; + + case AL_SOURCE_TYPE: + *plValue = Source->lSourceType; + break; + + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + updateLen = ALdfpDiv(int2ALdfp(pContext->Device->UpdateSize), + int2ALdfp(pContext->Device->Frequency)); + GetSourceOffset(Source, eParam, Offsets, updateLen); + *plValue = (ALint)ALfp2int(Offsets[0]); + break; + + case AL_DIRECT_FILTER: + *plValue = Source->DirectFilter.filter; + break; + + case AL_DIRECT_FILTER_GAINHF_AUTO: + *plValue = Source->DryGainHFAuto; + break; + + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + *plValue = Source->WetGainAuto; + break; + + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + *plValue = Source->WetGainHFAuto; + break; + + case AL_DOPPLER_FACTOR: + *plValue = (ALint)ALfp2int(Source->DopplerFactor); + break; + + case AL_DISTANCE_MODEL: + *plValue = Source->DistanceModel; + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum eParam, ALint* plValue1, ALint* plValue2, ALint* plValue3) +{ + ALCcontext *pContext; + ALsource *Source; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(plValue1 && plValue2 && plValue3) + { + if((Source=LookupSource(pContext->SourceMap, source)) != NULL) + { + switch(eParam) + { + case AL_POSITION: + *plValue1 = (ALint)ALfp2int(Source->vPosition[0]); + *plValue2 = (ALint)ALfp2int(Source->vPosition[1]); + *plValue3 = (ALint)ALfp2int(Source->vPosition[2]); + break; + + case AL_VELOCITY: + *plValue1 = (ALint)ALfp2int(Source->vVelocity[0]); + *plValue2 = (ALint)ALfp2int(Source->vVelocity[1]); + *plValue3 = (ALint)ALfp2int(Source->vVelocity[2]); + break; + + case AL_DIRECTION: + *plValue1 = (ALint)ALfp2int(Source->vOrientation[0]); + *plValue2 = (ALint)ALfp2int(Source->vOrientation[1]); + *plValue3 = (ALint)ALfp2int(Source->vOrientation[2]); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum eParam, ALint* plValues) +{ + ALCcontext *pContext; + ALsource *Source; + ALdfp Offsets[2]; + ALdfp updateLen; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(plValues) + { + if((Source=LookupSource(pContext->SourceMap, source)) != NULL) + { + switch(eParam) + { + case AL_SOURCE_RELATIVE: + case AL_CONE_INNER_ANGLE: + case AL_CONE_OUTER_ANGLE: + case AL_LOOPING: + case AL_BUFFER: + case AL_SOURCE_STATE: + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + case AL_SEC_OFFSET: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_MAX_DISTANCE: + case AL_ROLLOFF_FACTOR: + case AL_DOPPLER_FACTOR: + case AL_REFERENCE_DISTANCE: + case AL_SOURCE_TYPE: + case AL_DIRECT_FILTER: + case AL_DIRECT_FILTER_GAINHF_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + case AL_DISTANCE_MODEL: + alGetSourcei(source, eParam, plValues); + break; + + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + alGetSource3i(source, eParam, plValues+0, plValues+1, plValues+2); + break; + + case AL_SAMPLE_RW_OFFSETS_SOFT: + case AL_BYTE_RW_OFFSETS_SOFT: + updateLen = ALdfpDiv(int2ALdfp(pContext->Device->UpdateSize), + int2ALdfp(pContext->Device->Frequency)); + GetSourceOffset(Source, eParam, Offsets, updateLen); + plValues[0] = (ALint)ALdfp2int(Offsets[0]); + plValues[1] = (ALint)ALdfp2int(Offsets[1]); + break; + + default: + alSetError(pContext, AL_INVALID_ENUM); + break; + } + } + else + alSetError(pContext, AL_INVALID_NAME); + } + else + alSetError(pContext, AL_INVALID_VALUE); + + ProcessContext(pContext); +} + + +AL_API ALvoid AL_APIENTRY alSourcePlay(ALuint source) +{ + alSourcePlayv(1, &source); +} + +AL_API ALvoid AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) +{ + ALCcontext *Context; + ALsource *Source; + ALbufferlistitem *BufferList; + ALsizei i, j; + + Context = GetContextSuspended(); + if(!Context) return; + + if(n < 0) + { + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + if(n > 0 && !sources) + { + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + + // Check that all the Sources are valid + for(i = 0;i < n;i++) + { + if(!LookupSource(Context->SourceMap, sources[i])) + { + alSetError(Context, AL_INVALID_NAME); + goto done; + } + } + +#ifdef MAX_SOURCES_LOW + // Apportable: Cap the number of active source that are playing + if (Context->ActiveSourceCount + n > alc_max_sources) { + if (Context->ActiveSourceCount > alc_max_sources) { + n = 0; + } else { + n = alc_max_sources - Context->ActiveSourceCount; + } + } +#endif + + while(Context->MaxActiveSources-Context->ActiveSourceCount < n) + { + void *temp = NULL; + ALsizei newcount; + + newcount = Context->MaxActiveSources << 1; + if(newcount > 0) + temp = realloc(Context->ActiveSources, + sizeof(*Context->ActiveSources) * newcount); + if(!temp) + { + alSetError(Context, AL_OUT_OF_MEMORY); + goto done; + } + + Context->ActiveSources = temp; + Context->MaxActiveSources = newcount; + } + +#ifdef MAX_SOURCES_LOW + //Apportable Need to give the ALC platform code a hint for setting Source limit based on performance + // LOGI("Playing %d/%d ActiveSources", Context->ActiveSourceCount, alc_max_sources); + alc_active_sources = Context->ActiveSourceCount; +#endif + + for(i = 0;i < n;i++) + { + Source = (ALsource*)ALTHUNK_LOOKUPENTRY(sources[i]); + + // Check that there is a queue containing at least one non-null, non zero length AL Buffer + BufferList = Source->queue; + while(BufferList) + { + if(BufferList->buffer != NULL && BufferList->buffer->size) + break; + BufferList = BufferList->next; + } + + if(!BufferList) + { + Source->state = AL_STOPPED; + Source->BuffersPlayed = Source->BuffersInQueue; + Source->position = 0; + Source->position_fraction = 0; + Source->lOffset = 0; + continue; + } + + if(Source->state != AL_PAUSED) + { + Source->state = AL_PLAYING; + Source->position = 0; + Source->position_fraction = 0; + Source->BuffersPlayed = 0; + + Source->Buffer = Source->queue->buffer; + } + else + Source->state = AL_PLAYING; + + // Check if an Offset has been set + if(Source->lOffset) + ApplyOffset(Source); + + // If device is disconnected, go right to stopped + if(!Context->Device->Connected) + { + Source->state = AL_STOPPED; + Source->BuffersPlayed = Source->BuffersInQueue; + Source->position = 0; + Source->position_fraction = 0; + } + else + { + for(j = 0;j < Context->ActiveSourceCount;j++) + { + if(Context->ActiveSources[j] == Source) + break; + } + if(j == Context->ActiveSourceCount) + Context->ActiveSources[Context->ActiveSourceCount++] = Source; + } + } + +done: + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alSourcePause(ALuint source) +{ + alSourcePausev(1, &source); +} + +AL_API ALvoid AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) +{ + ALCcontext *Context; + ALsource *Source; + ALsizei i; + + Context = GetContextSuspended(); + if(!Context) return; + + if(n < 0) + { + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + if(n > 0 && !sources) + { + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + + // Check all the Sources are valid + for(i = 0;i < n;i++) + { + if(!LookupSource(Context->SourceMap, sources[i])) + { + alSetError(Context, AL_INVALID_NAME); + goto done; + } + } + + for(i = 0;i < n;i++) + { + Source = (ALsource*)ALTHUNK_LOOKUPENTRY(sources[i]); + if(Source->state == AL_PLAYING) + Source->state = AL_PAUSED; + } + +done: + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alSourceStop(ALuint source) +{ + alSourceStopv(1, &source); +} + +AL_API ALvoid AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources) +{ + ALCcontext *Context; + ALsource *Source; + ALsizei i; + + Context = GetContextSuspended(); + if(!Context) return; + + if(n < 0) + { + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + if(n > 0 && !sources) + { + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + + // Check all the Sources are valid + for(i = 0;i < n;i++) + { + if(!LookupSource(Context->SourceMap, sources[i])) + { + alSetError(Context, AL_INVALID_NAME); + goto done; + } + } + + for(i = 0;i < n;i++) + { + Source = (ALsource*)ALTHUNK_LOOKUPENTRY(sources[i]); + if(Source->state != AL_INITIAL) + { + Source->state = AL_STOPPED; + Source->BuffersPlayed = Source->BuffersInQueue; + } + Source->lOffset = 0; + } + +done: + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alSourceRewind(ALuint source) +{ + alSourceRewindv(1, &source); +} + +AL_API ALvoid AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources) +{ + ALCcontext *Context; + ALsource *Source; + ALsizei i; + + Context = GetContextSuspended(); + if(!Context) return; + + if(n < 0) + { + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + if(n > 0 && !sources) + { + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + + // Check all the Sources are valid + for(i = 0;i < n;i++) + { + if(!LookupSource(Context->SourceMap, sources[i])) + { + alSetError(Context, AL_INVALID_NAME); + goto done; + } + } + + for(i = 0;i < n;i++) + { + Source = (ALsource*)ALTHUNK_LOOKUPENTRY(sources[i]); + if(Source->state != AL_INITIAL) + { + Source->state = AL_INITIAL; + Source->position = 0; + Source->position_fraction = 0; + Source->BuffersPlayed = 0; + if(Source->queue) + Source->Buffer = Source->queue->buffer; + } + Source->lOffset = 0; + } + +done: + ProcessContext(Context); +} + + +AL_API ALvoid AL_APIENTRY alSourceQueueBuffers(ALuint source, ALsizei n, const ALuint *buffers) +{ + ALCcontext *Context; + ALCdevice *device; + ALsource *Source; + ALbuffer *buffer; + ALsizei i; + ALbufferlistitem *BufferListStart; + ALbufferlistitem *BufferList; + ALbuffer *BufferFmt; + + if(n == 0) + return; + + Context = GetContextSuspended(); + if(!Context) return; + + if(n < 0) + { + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + + // Check that all buffers are valid or zero and that the source is valid + + // Check that this is a valid source + if((Source=LookupSource(Context->SourceMap, source)) == NULL) + { + alSetError(Context, AL_INVALID_NAME); + goto done; + } + + // Check that this is not a STATIC Source + if(Source->lSourceType == AL_STATIC) + { + // Invalid Source Type (can't queue on a Static Source) + alSetError(Context, AL_INVALID_OPERATION); + goto done; + } + + device = Context->Device; + + BufferFmt = NULL; + + // Check existing Queue (if any) for a valid Buffers and get its frequency and format + BufferList = Source->queue; + while(BufferList) + { + if(BufferList->buffer) + { + BufferFmt = BufferList->buffer; + break; + } + BufferList = BufferList->next; + } + + for(i = 0;i < n;i++) + { + if(!buffers[i]) + continue; + + if((buffer=LookupBuffer(device->BufferMap, buffers[i])) == NULL) + { + alSetError(Context, AL_INVALID_NAME); + goto done; + } + + if(BufferFmt == NULL) + { + BufferFmt = buffer; + + if(buffer->FmtChannels == FmtMono) + Source->Update = CalcSourceParams; + else + Source->Update = CalcNonAttnSourceParams; + + Source->NeedsUpdate = AL_TRUE; + } + else if(BufferFmt->Frequency != buffer->Frequency || + BufferFmt->OriginalChannels != buffer->OriginalChannels || + BufferFmt->OriginalType != buffer->OriginalType) + { + alSetError(Context, AL_INVALID_OPERATION); + goto done; + } + } + + // Change Source Type + Source->lSourceType = AL_STREAMING; + + buffer = (ALbuffer*)ALTHUNK_LOOKUPENTRY(buffers[0]); + + // All buffers are valid - so add them to the list + BufferListStart = malloc(sizeof(ALbufferlistitem)); + BufferListStart->buffer = buffer; + BufferListStart->next = NULL; + BufferListStart->prev = NULL; + + // Increment reference counter for buffer + if(buffer) buffer->refcount++; + + BufferList = BufferListStart; + + for(i = 1;i < n;i++) + { + buffer = (ALbuffer*)ALTHUNK_LOOKUPENTRY(buffers[i]); + + BufferList->next = malloc(sizeof(ALbufferlistitem)); + BufferList->next->buffer = buffer; + BufferList->next->next = NULL; + BufferList->next->prev = BufferList; + + // Increment reference counter for buffer + if(buffer) buffer->refcount++; + + BufferList = BufferList->next; + } + + if(Source->queue == NULL) + { + Source->queue = BufferListStart; + // Update Current Buffer + Source->Buffer = BufferListStart->buffer; + } + else + { + // Find end of queue + BufferList = Source->queue; + while(BufferList->next != NULL) + BufferList = BufferList->next; + + BufferList->next = BufferListStart; + BufferList->next->prev = BufferList; + } + + // Update number of buffers in queue + Source->BuffersInQueue += n; + +done: + ProcessContext(Context); +} + + +// Implementation assumes that n is the number of buffers to be removed from the queue and buffers is +// an array of buffer IDs that are to be filled with the names of the buffers removed +AL_API ALvoid AL_APIENTRY alSourceUnqueueBuffers( ALuint source, ALsizei n, ALuint* buffers ) +{ + ALCcontext *Context; + ALsource *Source; + ALsizei i; + ALbufferlistitem *BufferList; + + if(n == 0) + return; + + Context = GetContextSuspended(); + if(!Context) return; + + if(n < 0) + { + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + + if((Source=LookupSource(Context->SourceMap, source)) == NULL) + { + alSetError(Context, AL_INVALID_NAME); + goto done; + } + + if(Source->bLooping || Source->lSourceType != AL_STREAMING || + (ALuint)n > Source->BuffersPlayed) + { + // Some buffers can't be unqueue because they have not been processed + alSetError(Context, AL_INVALID_VALUE); + goto done; + } + + for(i = 0;i < n;i++) + { + BufferList = Source->queue; + Source->queue = BufferList->next; + + if(BufferList->buffer) + { + // Record name of buffer + buffers[i] = BufferList->buffer->buffer; + // Decrement buffer reference counter + BufferList->buffer->refcount--; + } + else + buffers[i] = 0; + + // Release memory for buffer list item + free(BufferList); + Source->BuffersInQueue--; + } + if(Source->queue) + Source->queue->prev = NULL; + + if(Source->state != AL_PLAYING) + { + if(Source->queue) + Source->Buffer = Source->queue->buffer; + else + Source->Buffer = NULL; + } + Source->BuffersPlayed -= n; + +done: + ProcessContext(Context); +} + + +static ALvoid InitSourceParams(ALsource *Source) +{ + Source->flInnerAngle = int2ALfp(360); + Source->flOuterAngle = int2ALfp(360); + Source->flPitch = int2ALfp(1); + Source->vPosition[0] = int2ALfp(0); + Source->vPosition[1] = int2ALfp(0); + Source->vPosition[2] = int2ALfp(0); + Source->vOrientation[0] = int2ALfp(0); + Source->vOrientation[1] = int2ALfp(0); + Source->vOrientation[2] = int2ALfp(0); + Source->vVelocity[0] = int2ALfp(0); + Source->vVelocity[1] = int2ALfp(0); + Source->vVelocity[2] = int2ALfp(0); + Source->flRefDistance = int2ALfp(1); + Source->flMaxDistance = int2ALfp(FLT_MAX); + Source->flRollOffFactor = int2ALfp(1); + Source->bLooping = AL_FALSE; + Source->flGain = int2ALfp(1); + Source->flMinGain = int2ALfp(0); + Source->flMaxGain = int2ALfp(1); + Source->flOuterGain = int2ALfp(0); + Source->OuterGainHF = int2ALfp(1); + + Source->DryGainHFAuto = AL_TRUE; + Source->WetGainAuto = AL_TRUE; + Source->WetGainHFAuto = AL_TRUE; + Source->AirAbsorptionFactor = int2ALfp(0); + Source->RoomRolloffFactor = int2ALfp(0); + Source->DopplerFactor = int2ALfp(1); + + Source->DistanceModel = AL_INVERSE_DISTANCE_CLAMPED; + + Source->Resampler = DefaultResampler; + + Source->state = AL_INITIAL; + Source->lSourceType = AL_UNDETERMINED; + + Source->NeedsUpdate = AL_TRUE; + + Source->Buffer = NULL; +} + + +/* + GetSourceOffset + + Gets the current playback position in the given Source, in the appropriate format (Bytes, Samples or MilliSeconds) + The offset is relative to the start of the queue (not the start of the current buffer) +*/ +static ALvoid GetSourceOffset(ALsource *Source, ALenum name, ALdfp *offset, ALdfp updateLen) +{ + const ALbufferlistitem *BufferList; + const ALbuffer *Buffer = NULL; + enum UserFmtType OriginalType; + ALsizei BufferFreq; + ALint Channels, Bytes; + ALuint readPos, writePos; + ALuint TotalBufferDataSize; + ALuint i; + + // Find the first non-NULL Buffer in the Queue + BufferList = Source->queue; + while(BufferList) + { + if(BufferList->buffer) + { + Buffer = BufferList->buffer; + break; + } + BufferList = BufferList->next; + } + + if((Source->state != AL_PLAYING && Source->state != AL_PAUSED) || !Buffer) + { + offset[0] = int2ALdfp(0); + offset[1] = int2ALdfp(0); + return; + } + + // Get Current Buffer Size and frequency (in milliseconds) + BufferFreq = Buffer->Frequency; + OriginalType = Buffer->OriginalType; + Channels = ChannelsFromFmt(Buffer->FmtChannels); + Bytes = BytesFromFmt(Buffer->FmtType); + + // Get Current BytesPlayed (NOTE : This is the byte offset into the *current* buffer) + readPos = Source->position * Channels * Bytes; + // Add byte length of any processed buffers in the queue + TotalBufferDataSize = 0; + BufferList = Source->queue; + for(i = 0;BufferList;i++) + { + if(BufferList->buffer) + { + if(i < Source->BuffersPlayed) + readPos += BufferList->buffer->size; + TotalBufferDataSize += BufferList->buffer->size; + } + BufferList = BufferList->next; + } + if(Source->state == AL_PLAYING) + writePos = readPos + ((ALuint)(ALdfp2int(ALdfpMult(updateLen,int2ALdfp(BufferFreq)))) * Channels * Bytes); + else + writePos = readPos; + + if(Source->bLooping) + { + readPos %= TotalBufferDataSize; + writePos %= TotalBufferDataSize; + } + else + { + // Wrap positions back to 0 + if(readPos >= TotalBufferDataSize) + readPos = 0; + if(writePos >= TotalBufferDataSize) + writePos = 0; + } + + switch(name) + { + case AL_SEC_OFFSET: + offset[0] = ALdfpDiv(readPos, int2ALdfp(Channels * Bytes * BufferFreq)); + offset[1] = ALdfpDiv(writePos, int2ALdfp(Channels * Bytes * BufferFreq)); + break; + case AL_SAMPLE_OFFSET: + case AL_SAMPLE_RW_OFFSETS_SOFT: + offset[0] = int2ALdfp(readPos / (Channels * Bytes)); + offset[1] = int2ALdfp(writePos / (Channels * Bytes)); + break; + case AL_BYTE_OFFSET: + case AL_BYTE_RW_OFFSETS_SOFT: + // Take into account the original format of the Buffer + if(OriginalType == UserFmtIMA4) + { + ALuint FrameBlockSize = 65 * Bytes * Channels; + ALuint BlockSize = 36 * Channels; + + // Round down to nearest ADPCM block + offset[0] = int2ALdfp(readPos / FrameBlockSize * BlockSize); + if(Source->state != AL_PLAYING) + offset[1] = offset[0]; + else + { + // Round up to nearest ADPCM block + offset[1] = int2ALdfp((writePos+FrameBlockSize-1) / + (FrameBlockSize * BlockSize)); + } + } + else + { + ALuint OrigBytes = BytesFromUserFmt(OriginalType); + offset[0] = int2ALdfp(readPos / Bytes * OrigBytes); + offset[1] = int2ALdfp(writePos / Bytes * OrigBytes); + } + break; + } +} + + +/* + ApplyOffset + + Apply a playback offset to the Source. This function will update the queue (to correctly + mark buffers as 'pending' or 'processed' depending upon the new offset. +*/ +static ALboolean ApplyOffset(ALsource *Source) +{ + const ALbufferlistitem *BufferList; + const ALbuffer *Buffer; + ALint lBufferSize, lTotalBufferSize; + ALint BuffersPlayed; + ALint lByteOffset; + + // Get true byte offset + lByteOffset = GetByteOffset(Source); + + // If the offset is invalid, don't apply it + if(lByteOffset == -1) + return AL_FALSE; + + // Sort out the queue (pending and processed states) + BufferList = Source->queue; + lTotalBufferSize = 0; + BuffersPlayed = 0; + + while(BufferList) + { + Buffer = BufferList->buffer; + lBufferSize = Buffer ? Buffer->size : 0; + + if(lBufferSize <= lByteOffset-lTotalBufferSize) + { + // Offset is past this buffer so increment BuffersPlayed + BuffersPlayed++; + } + else if(lTotalBufferSize <= lByteOffset) + { + // Offset is within this buffer + // Set Current Buffer + Source->Buffer = BufferList->buffer; + Source->BuffersPlayed = BuffersPlayed; + + // SW Mixer Positions are in Samples + Source->position = (lByteOffset - lTotalBufferSize) / + FrameSizeFromFmt(Buffer->FmtChannels, Buffer->FmtType); + return AL_TRUE; + } + + // Increment the TotalBufferSize + lTotalBufferSize += lBufferSize; + + // Move on to next buffer in the Queue + BufferList = BufferList->next; + } + // Offset is out of range of the buffer queue + return AL_FALSE; +} + + +/* + GetByteOffset + + Returns the 'true' byte offset into the Source's queue (from the Sample, Byte or Millisecond + offset supplied by the application). This takes into account the fact that the buffer format + may have been modifed by AL (e.g 8bit samples are converted to float) +*/ +static ALint GetByteOffset(ALsource *Source) +{ + const ALbuffer *Buffer = NULL; + const ALbufferlistitem *BufferList; + ALint ByteOffset = -1; + + // Find the first non-NULL Buffer in the Queue + BufferList = Source->queue; + while(BufferList) + { + if(BufferList->buffer) + { + Buffer = BufferList->buffer; + break; + } + BufferList = BufferList->next; + } + + if(!Buffer) + { + Source->lOffset = 0; + return -1; + } + + // Determine the ByteOffset (and ensure it is block aligned) + switch(Source->lOffsetType) + { + case AL_BYTE_OFFSET: + // Take into consideration the original format + ByteOffset = Source->lOffset; + if(Buffer->OriginalType == UserFmtIMA4) + { + // Round down to nearest ADPCM block + ByteOffset /= 36 * ChannelsFromUserFmt(Buffer->OriginalChannels); + // Multiply by compression rate (65 sample frames per block) + ByteOffset *= 65; + } + else + ByteOffset /= FrameSizeFromUserFmt(Buffer->OriginalChannels, Buffer->OriginalType); + ByteOffset *= FrameSizeFromFmt(Buffer->FmtChannels, Buffer->FmtType); + break; + + case AL_SAMPLE_OFFSET: + ByteOffset = Source->lOffset * FrameSizeFromFmt(Buffer->FmtChannels, Buffer->FmtType); + break; + + case AL_SEC_OFFSET: + // Note - lOffset is internally stored as Milliseconds + ByteOffset = (ALint)(Source->lOffset / 1000.0 * Buffer->Frequency); + ByteOffset *= FrameSizeFromFmt(Buffer->FmtChannels, Buffer->FmtType); + break; + } + // Clear Offset + Source->lOffset = 0; + + return ByteOffset; +} + + +ALvoid ReleaseALSources(ALCcontext *Context) +{ + ALsizei pos; + ALuint j; + for(pos = 0;pos < Context->SourceMap.size;pos++) + { + ALsource *temp = Context->SourceMap.array[pos].value; + Context->SourceMap.array[pos].value = NULL; + + // For each buffer in the source's queue, decrement its reference counter and remove it + while(temp->queue != NULL) + { + ALbufferlistitem *BufferList = temp->queue; + temp->queue = BufferList->next; + + if(BufferList->buffer != NULL) + BufferList->buffer->refcount--; + free(BufferList); + } + + for(j = 0;j < MAX_SENDS;++j) + { + if(temp->Send[j].Slot) + temp->Send[j].Slot->refcount--; + temp->Send[j].Slot = NULL; + } + + // Release source structure + ALTHUNK_REMOVEENTRY(temp->source); + memset(temp, 0, sizeof(ALsource)); + free(temp); + } +} diff --git a/jni/OpenAL/OpenAL32/alState.c b/jni/OpenAL/OpenAL32/alState.c new file mode 100644 index 0000000..74c6a40 --- /dev/null +++ b/jni/OpenAL/OpenAL32/alState.c @@ -0,0 +1,661 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include +#include "alMain.h" +#include "AL/alc.h" +#include "AL/alext.h" +#include "alError.h" +#include "alSource.h" +#include "alState.h" +#include "alDatabuffer.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"; + +AL_API ALvoid AL_APIENTRY alEnable(ALenum capability) +{ + ALCcontext *Context; + ALboolean updateSources = AL_FALSE; + + Context = GetContextSuspended(); + if(!Context) return; + + switch(capability) + { + case AL_SOURCE_DISTANCE_MODEL: + Context->SourceDistanceModel = AL_TRUE; + updateSources = AL_TRUE; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + + if(updateSources) + { + ALsizei pos; + for(pos = 0;pos < Context->SourceMap.size;pos++) + { + ALsource *source = Context->SourceMap.array[pos].value; + source->NeedsUpdate = AL_TRUE; + } + } + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alDisable(ALenum capability) +{ + ALCcontext *Context; + ALboolean updateSources = AL_FALSE; + + Context = GetContextSuspended(); + if(!Context) return; + + switch(capability) + { + case AL_SOURCE_DISTANCE_MODEL: + Context->SourceDistanceModel = AL_FALSE; + updateSources = AL_TRUE; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + + if(updateSources) + { + ALsizei pos; + for(pos = 0;pos < Context->SourceMap.size;pos++) + { + ALsource *source = Context->SourceMap.array[pos].value; + source->NeedsUpdate = AL_TRUE; + } + } + + ProcessContext(Context); +} + +AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability) +{ + ALCcontext *Context; + ALboolean value=AL_FALSE; + + Context = GetContextSuspended(); + if(!Context) return AL_FALSE; + + switch(capability) + { + case AL_SOURCE_DISTANCE_MODEL: + value = Context->SourceDistanceModel; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + + ProcessContext(Context); + + return value; +} + +AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname) +{ + ALCcontext *Context; + ALboolean value=AL_FALSE; + + Context = GetContextSuspended(); + if(!Context) return AL_FALSE; + + switch(pname) + { + case AL_DOPPLER_FACTOR: + if(Context->DopplerFactor != int2ALfp(0)) + value = AL_TRUE; + break; + + case AL_DOPPLER_VELOCITY: + if(Context->DopplerVelocity != int2ALfp(0)) + 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->flSpeedOfSound != int2ALfp(0)) + value = AL_TRUE; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + + ProcessContext(Context); + + return value; +} + +AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname) +{ + ALCcontext *Context; + ALdouble value = 0.0; + + Context = GetContextSuspended(); + if(!Context) return 0.0; + + switch(pname) + { + case AL_DOPPLER_FACTOR: + value = (double)ALfp2float(Context->DopplerFactor); + break; + + case AL_DOPPLER_VELOCITY: + value = (double)ALfp2float(Context->DopplerVelocity); + break; + + case AL_DISTANCE_MODEL: + value = (double)Context->DistanceModel; + break; + + case AL_SPEED_OF_SOUND: + value = (double)ALfp2float(Context->flSpeedOfSound); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + + ProcessContext(Context); + + return value; +} + +AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname) +{ + ALCcontext *Context; + ALfloat value = 0.0f; + + Context = GetContextSuspended(); + if(!Context) return 0.0f; + + switch(pname) + { + case AL_DOPPLER_FACTOR: + value = ALfp2float(Context->DopplerFactor); + break; + + case AL_DOPPLER_VELOCITY: + value = ALfp2float(Context->DopplerVelocity); + break; + + case AL_DISTANCE_MODEL: + value = (float)Context->DistanceModel; + break; + + case AL_SPEED_OF_SOUND: + value = ALfp2float(Context->flSpeedOfSound); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + + ProcessContext(Context); + + return value; +} + +AL_API ALint AL_APIENTRY alGetInteger(ALenum pname) +{ + ALCcontext *Context; + ALint value = 0; + + Context = GetContextSuspended(); + if(!Context) return 0; + + switch(pname) + { + case AL_DOPPLER_FACTOR: + value = (ALint)ALfp2int(Context->DopplerFactor); + break; + + case AL_DOPPLER_VELOCITY: + value = (ALint)ALfp2int(Context->DopplerVelocity); + break; + + case AL_DISTANCE_MODEL: + value = (ALint)Context->DistanceModel; + break; + + case AL_SPEED_OF_SOUND: + value = (ALint)ALfp2int(Context->flSpeedOfSound); + break; + + case AL_SAMPLE_SOURCE_EXT: + if(Context->SampleSource) + value = (ALint)Context->SampleSource->databuffer; + else + value = 0; + break; + + case AL_SAMPLE_SINK_EXT: + if(Context->SampleSink) + value = (ALint)Context->SampleSink->databuffer; + else + value = 0; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + + ProcessContext(Context); + + return value; +} + +AL_API ALvoid AL_APIENTRY alGetBooleanv(ALenum pname,ALboolean *data) +{ + ALCcontext *Context; + + Context = GetContextSuspended(); + if(!Context) return; + + if(data) + { + switch(pname) + { + case AL_DOPPLER_FACTOR: + *data = (ALboolean)((Context->DopplerFactor != int2ALfp(0)) ? AL_TRUE : AL_FALSE); + break; + + case AL_DOPPLER_VELOCITY: + *data = (ALboolean)((Context->DopplerVelocity != int2ALfp(0)) ? AL_TRUE : AL_FALSE); + break; + + case AL_DISTANCE_MODEL: + *data = (ALboolean)((Context->DistanceModel == AL_INVERSE_DISTANCE_CLAMPED) ? AL_TRUE : AL_FALSE); + break; + + case AL_SPEED_OF_SOUND: + *data = (ALboolean)((Context->flSpeedOfSound != int2ALfp(0)) ? AL_TRUE : AL_FALSE); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + { + // data is a NULL pointer + alSetError(Context, AL_INVALID_VALUE); + } + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetDoublev(ALenum pname,ALdouble *data) +{ + ALCcontext *Context; + + Context = GetContextSuspended(); + if(!Context) return; + + if(data) + { + switch(pname) + { + case AL_DOPPLER_FACTOR: + *data = (double)ALfp2float(Context->DopplerFactor); + break; + + case AL_DOPPLER_VELOCITY: + *data = (double)ALfp2float(Context->DopplerVelocity); + break; + + case AL_DISTANCE_MODEL: + *data = (double)Context->DistanceModel; + break; + + case AL_SPEED_OF_SOUND: + *data = (double)ALfp2float(Context->flSpeedOfSound); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + { + // data is a NULL pointer + alSetError(Context, AL_INVALID_VALUE); + } + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetFloatv(ALenum pname,ALfloat *data) +{ + ALCcontext *Context; + + Context = GetContextSuspended(); + if(!Context) return; + + if(data) + { + switch(pname) + { + case AL_DOPPLER_FACTOR: + *data = ALfp2float(Context->DopplerFactor); + break; + + case AL_DOPPLER_VELOCITY: + *data = ALfp2float(Context->DopplerVelocity); + break; + + case AL_DISTANCE_MODEL: + *data = (float)Context->DistanceModel; + break; + + case AL_SPEED_OF_SOUND: + *data = ALfp2float(Context->flSpeedOfSound); + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + { + // data is a NULL pointer + alSetError(Context, AL_INVALID_VALUE); + } + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alGetIntegerv(ALenum pname,ALint *data) +{ + ALCcontext *Context; + + Context = GetContextSuspended(); + if(!Context) return; + + if(data) + { + switch(pname) + { + case AL_DOPPLER_FACTOR: + *data = (ALint)ALfp2int(Context->DopplerFactor); + break; + + case AL_DOPPLER_VELOCITY: + *data = (ALint)ALfp2int(Context->DopplerVelocity); + break; + + case AL_DISTANCE_MODEL: + *data = (ALint)Context->DistanceModel; + break; + + case AL_SPEED_OF_SOUND: + *data = (ALint)ALfp2int(Context->flSpeedOfSound); + break; + + case AL_SAMPLE_SOURCE_EXT: + if(Context->SampleSource) + *data = (ALint)Context->SampleSource->databuffer; + else + *data = 0; + break; + + case AL_SAMPLE_SINK_EXT: + if(Context->SampleSink) + *data = (ALint)Context->SampleSink->databuffer; + else + *data = 0; + break; + + default: + alSetError(Context, AL_INVALID_ENUM); + break; + } + } + else + { + // data is a NULL pointer + alSetError(Context, AL_INVALID_VALUE); + } + + ProcessContext(Context); +} + +AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname) +{ + const ALchar *value; + ALCcontext *pContext; + + pContext = GetContextSuspended(); + if(!pContext) 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=pContext->ExtensionList;//alExtensions; + 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: + value=NULL; + alSetError(pContext, AL_INVALID_ENUM); + break; + } + + ProcessContext(pContext); + + return value; +} + +AL_API ALvoid AL_APIENTRY alDopplerFactor(ALfloat value) +{ + ALCcontext *Context; + ALboolean updateSources = AL_FALSE; + + Context = GetContextSuspended(); + if(!Context) return; + + if(value >= 0.0f) + { + Context->DopplerFactor = float2ALfp(value); + updateSources = AL_TRUE; + } + else + alSetError(Context, AL_INVALID_VALUE); + + // Force updating the sources for these parameters, since even head- + // relative sources are affected + if(updateSources) + { + ALsizei pos; + for(pos = 0;pos < Context->SourceMap.size;pos++) + { + ALsource *source = Context->SourceMap.array[pos].value; + source->NeedsUpdate = AL_TRUE; + } + } + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alDopplerVelocity(ALfloat value) +{ + ALCcontext *Context; + ALboolean updateSources = AL_FALSE; + + Context = GetContextSuspended(); + if(!Context) return; + + if(value > 0.0f) + { + Context->DopplerVelocity=float2ALfp(value); + updateSources = AL_TRUE; + } + else + alSetError(Context, AL_INVALID_VALUE); + + if(updateSources) + { + ALsizei pos; + for(pos = 0;pos < Context->SourceMap.size;pos++) + { + ALsource *source = Context->SourceMap.array[pos].value; + source->NeedsUpdate = AL_TRUE; + } + } + + ProcessContext(Context); +} + +AL_API ALvoid AL_APIENTRY alSpeedOfSound(ALfloat flSpeedOfSound) +{ + ALCcontext *pContext; + ALboolean updateSources = AL_FALSE; + + pContext = GetContextSuspended(); + if(!pContext) return; + + if(flSpeedOfSound > 0.0f) + { + pContext->flSpeedOfSound = float2ALfp(flSpeedOfSound); + updateSources = AL_TRUE; + } + else + alSetError(pContext, AL_INVALID_VALUE); + + if(updateSources) + { + ALsizei pos; + for(pos = 0;pos < pContext->SourceMap.size;pos++) + { + ALsource *source = pContext->SourceMap.array[pos].value; + source->NeedsUpdate = AL_TRUE; + } + } + + ProcessContext(pContext); +} + +AL_API ALvoid AL_APIENTRY alDistanceModel(ALenum value) +{ + ALCcontext *Context; + ALboolean updateSources = AL_FALSE; + + Context = GetContextSuspended(); + if(!Context) return; + + switch(value) + { + case AL_NONE: + case AL_INVERSE_DISTANCE: + case AL_INVERSE_DISTANCE_CLAMPED: + case AL_LINEAR_DISTANCE: + case AL_LINEAR_DISTANCE_CLAMPED: + case AL_EXPONENT_DISTANCE: + case AL_EXPONENT_DISTANCE_CLAMPED: + Context->DistanceModel = value; + updateSources = !Context->SourceDistanceModel; + break; + + default: + alSetError(Context, AL_INVALID_VALUE); + break; + } + + if(updateSources) + { + ALsizei pos; + for(pos = 0;pos < Context->SourceMap.size;pos++) + { + ALsource *source = Context->SourceMap.array[pos].value; + source->NeedsUpdate = AL_TRUE; + } + } + + ProcessContext(Context); +} diff --git a/jni/OpenAL/OpenAL32/alThunk.c b/jni/OpenAL/OpenAL32/alThunk.c new file mode 100644 index 0000000..08b80b0 --- /dev/null +++ b/jni/OpenAL/OpenAL32/alThunk.c @@ -0,0 +1,111 @@ +/** + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include + +#include "alMain.h" +#include "alThunk.h" + +typedef struct { + ALvoid *ptr; + ALboolean InUse; +} ThunkEntry; + +static ThunkEntry *g_ThunkArray; +static ALuint g_ThunkArraySize; + +static CRITICAL_SECTION g_ThunkLock; + +void alThunkInit(void) +{ + InitializeCriticalSection(&g_ThunkLock); + g_ThunkArraySize = 1; + g_ThunkArray = calloc(1, g_ThunkArraySize * sizeof(ThunkEntry)); +} + +void alThunkExit(void) +{ + free(g_ThunkArray); + g_ThunkArray = NULL; + g_ThunkArraySize = 0; + DeleteCriticalSection(&g_ThunkLock); +} + +ALuint alThunkAddEntry(ALvoid *ptr) +{ + ALuint index; + + EnterCriticalSection(&g_ThunkLock); + + for(index = 0;index < g_ThunkArraySize;index++) + { + if(g_ThunkArray[index].InUse == AL_FALSE) + break; + } + + if(index == g_ThunkArraySize) + { + ThunkEntry *NewList; + + NewList = realloc(g_ThunkArray, g_ThunkArraySize*2 * sizeof(ThunkEntry)); + if(!NewList) + { + LeaveCriticalSection(&g_ThunkLock); + AL_PRINT("Realloc failed to increase to %u enties!\n", g_ThunkArraySize*2); + return 0; + } + memset(&NewList[g_ThunkArraySize], 0, g_ThunkArraySize*sizeof(ThunkEntry)); + g_ThunkArraySize *= 2; + g_ThunkArray = NewList; + } + + g_ThunkArray[index].ptr = ptr; + g_ThunkArray[index].InUse = AL_TRUE; + + LeaveCriticalSection(&g_ThunkLock); + + return index+1; +} + +void alThunkRemoveEntry(ALuint index) +{ + EnterCriticalSection(&g_ThunkLock); + + if(index > 0 && index <= g_ThunkArraySize) + g_ThunkArray[index-1].InUse = AL_FALSE; + + LeaveCriticalSection(&g_ThunkLock); +} + +ALvoid *alThunkLookupEntry(ALuint index) +{ + ALvoid *ptr = NULL; + + EnterCriticalSection(&g_ThunkLock); + + if(index > 0 && index <= g_ThunkArraySize) + ptr = g_ThunkArray[index-1].ptr; + + LeaveCriticalSection(&g_ThunkLock); + + return ptr; +} diff --git a/jni/OpenAL/al.h b/jni/OpenAL/al.h new file mode 100644 index 0000000..44db779 --- /dev/null +++ b/jni/OpenAL/al.h @@ -0,0 +1,724 @@ +#ifndef AL_AL_H +#define AL_AL_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(AL_LIBTYPE_STATIC) + #define AL_API +#elif defined(_WIN32) && !defined(_XBOX) + #if defined(AL_BUILD_LIBRARY) + #define AL_API __declspec(dllexport) + #else + #define AL_API __declspec(dllimport) + #endif +#else + #if defined(AL_BUILD_LIBRARY) && defined(HAVE_GCC_VISIBILITY) + #define AL_API __attribute__((visibility("protected"))) + #else + #define AL_API extern + #endif +#endif + +#if defined(_WIN32) + #define AL_APIENTRY __cdecl +#else + #define AL_APIENTRY +#endif + +#if defined(TARGET_OS_MAC) && TARGET_OS_MAC + #pragma export on +#endif + +/* + * The OPENAL, ALAPI, ALAPIENTRY, AL_INVALID, AL_ILLEGAL_ENUM, and + * AL_ILLEGAL_COMMAND macros are deprecated, but are included for + * applications porting code from AL 1.0 + */ +#define OPENAL +#define ALAPI AL_API +#define ALAPIENTRY AL_APIENTRY +#define AL_INVALID (-1) +#define AL_ILLEGAL_ENUM AL_INVALID_ENUM +#define AL_ILLEGAL_COMMAND AL_INVALID_OPERATION + +#define AL_VERSION_1_0 +#define AL_VERSION_1_1 + + +/** 8-bit boolean */ +typedef char ALboolean; + +/** character */ +typedef char ALchar; + +/** signed 8-bit 2's complement integer */ +typedef signed char ALbyte; + +/** unsigned 8-bit integer */ +typedef unsigned char ALubyte; + +/** signed 16-bit 2's complement integer */ +typedef short ALshort; + +/** unsigned 16-bit integer */ +typedef unsigned short ALushort; + +/** signed 32-bit 2's complement integer */ +typedef int ALint; + +/** unsigned 32-bit integer */ +typedef unsigned int ALuint; + +/** non-negative 32-bit binary integer size */ +typedef int ALsizei; + +/** enumerated 32-bit value */ +typedef int ALenum; + +/** 32-bit IEEE754 floating-point */ +typedef float ALfloat; + +/** 64-bit IEEE754 floating-point */ +typedef double ALdouble; + +/** void type (for opaque pointers only) */ +typedef void ALvoid; + + +/* Enumerant values begin at column 50. No tabs. */ + +/* "no distance model" or "no buffer" */ +#define AL_NONE 0 + +/* Boolean False. */ +#define AL_FALSE 0 + +/** Boolean True. */ +#define AL_TRUE 1 + +/** Indicate Source has relative coordinates. */ +#define AL_SOURCE_RELATIVE 0x202 + + + +/** + * Directional source, inner cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_INNER_ANGLE 0x1001 + +/** + * Directional source, outer cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_OUTER_ANGLE 0x1002 + +/** + * Specify the pitch to be applied at source. + * Range: [0.5-2.0] + * Default: 1.0 + */ +#define AL_PITCH 0x1003 + +/** + * Specify the current location in three dimensional space. + * OpenAL, like OpenGL, uses a right handed coordinate system, + * where in a frontal default view X (thumb) points right, + * Y points up (index finger), and Z points towards the + * viewer/camera (middle finger). + * To switch from a left handed coordinate system, flip the + * sign on the Z coordinate. + * Listener position is always in the world coordinate system. + */ +#define AL_POSITION 0x1004 + +/** Specify the current direction. */ +#define AL_DIRECTION 0x1005 + +/** Specify the current velocity in three dimensional space. */ +#define AL_VELOCITY 0x1006 + +/** + * Indicate whether source is looping. + * Type: ALboolean? + * Range: [AL_TRUE, AL_FALSE] + * Default: FALSE. + */ +#define AL_LOOPING 0x1007 + +/** + * Indicate the buffer to provide sound samples. + * Type: ALuint. + * Range: any valid Buffer id. + */ +#define AL_BUFFER 0x1009 + +/** + * Indicate the gain (volume amplification) applied. + * Type: ALfloat. + * Range: ]0.0- ] + * A value of 1.0 means un-attenuated/unchanged. + * Each division by 2 equals an attenuation of -6dB. + * Each multiplicaton with 2 equals an amplification of +6dB. + * A value of 0.0 is meaningless with respect to a logarithmic + * scale; it is interpreted as zero volume - the channel + * is effectively disabled. + */ +#define AL_GAIN 0x100A + +/* + * Indicate minimum source attenuation + * Type: ALfloat + * Range: [0.0 - 1.0] + * + * Logarthmic + */ +#define AL_MIN_GAIN 0x100D + +/** + * Indicate maximum source attenuation + * Type: ALfloat + * Range: [0.0 - 1.0] + * + * Logarthmic + */ +#define AL_MAX_GAIN 0x100E + +/** + * Indicate listener orientation. + * + * at/up + */ +#define AL_ORIENTATION 0x100F + +/** + * Source state information. + */ +#define AL_SOURCE_STATE 0x1010 +#define AL_INITIAL 0x1011 +#define AL_PLAYING 0x1012 +#define AL_PAUSED 0x1013 +#define AL_STOPPED 0x1014 + +/** + * Buffer Queue params + */ +#define AL_BUFFERS_QUEUED 0x1015 +#define AL_BUFFERS_PROCESSED 0x1016 + +/** + * Source buffer position information + */ +#define AL_SEC_OFFSET 0x1024 +#define AL_SAMPLE_OFFSET 0x1025 +#define AL_BYTE_OFFSET 0x1026 + +/* + * Source type (Static, Streaming or undetermined) + * Source is Static if a Buffer has been attached using AL_BUFFER + * Source is Streaming if one or more Buffers have been attached using alSourceQueueBuffers + * Source is undetermined when it has the NULL buffer attached + */ +#define AL_SOURCE_TYPE 0x1027 +#define AL_STATIC 0x1028 +#define AL_STREAMING 0x1029 +#define AL_UNDETERMINED 0x1030 + +/** Sound samples: format specifier. */ +#define AL_FORMAT_MONO8 0x1100 +#define AL_FORMAT_MONO16 0x1101 +#define AL_FORMAT_STEREO8 0x1102 +#define AL_FORMAT_STEREO16 0x1103 + +/** + * source specific reference distance + * Type: ALfloat + * Range: 0.0 - +inf + * + * At 0.0, no distance attenuation occurs. Default is + * 1.0. + */ +#define AL_REFERENCE_DISTANCE 0x1020 + +/** + * source specific rolloff factor + * Type: ALfloat + * Range: 0.0 - +inf + * + */ +#define AL_ROLLOFF_FACTOR 0x1021 + +/** + * Directional source, outer cone gain. + * + * Default: 0.0 + * Range: [0.0 - 1.0] + * Logarithmic + */ +#define AL_CONE_OUTER_GAIN 0x1022 + +/** + * Indicate distance above which sources are not + * attenuated using the inverse clamped distance model. + * + * Default: +inf + * Type: ALfloat + * Range: 0.0 - +inf + */ +#define AL_MAX_DISTANCE 0x1023 + +/** + * Sound samples: frequency, in units of Hertz [Hz]. + * This is the number of samples per second. Half of the + * sample frequency marks the maximum significant + * frequency component. + */ +#define AL_FREQUENCY 0x2001 +#define AL_BITS 0x2002 +#define AL_CHANNELS 0x2003 +#define AL_SIZE 0x2004 + +/** + * Buffer state. + * + * Not supported for public use (yet). + */ +#define AL_UNUSED 0x2010 +#define AL_PENDING 0x2011 +#define AL_PROCESSED 0x2012 + + +/** Errors: No Error. */ +#define AL_NO_ERROR AL_FALSE + +/** + * Invalid Name paramater passed to AL call. + */ +#define AL_INVALID_NAME 0xA001 + +/** + * Invalid parameter passed to AL call. + */ +#define AL_INVALID_ENUM 0xA002 + +/** + * Invalid enum parameter value. + */ +#define AL_INVALID_VALUE 0xA003 + +/** + * Illegal call. + */ +#define AL_INVALID_OPERATION 0xA004 + + +/** + * No mojo. + */ +#define AL_OUT_OF_MEMORY 0xA005 + + +/** Context strings: Vendor Name. */ +#define AL_VENDOR 0xB001 +#define AL_VERSION 0xB002 +#define AL_RENDERER 0xB003 +#define AL_EXTENSIONS 0xB004 + +/** Global tweakage. */ + +/** + * Doppler scale. Default 1.0 + */ +#define AL_DOPPLER_FACTOR 0xC000 + +/** + * Tweaks speed of propagation. + */ +#define AL_DOPPLER_VELOCITY 0xC001 + +/** + * Speed of Sound in units per second + */ +#define AL_SPEED_OF_SOUND 0xC003 + +/** + * Distance models + * + * used in conjunction with DistanceModel + * + * implicit: NONE, which disances distance attenuation. + */ +#define AL_DISTANCE_MODEL 0xD000 +#define AL_INVERSE_DISTANCE 0xD001 +#define AL_INVERSE_DISTANCE_CLAMPED 0xD002 +#define AL_LINEAR_DISTANCE 0xD003 +#define AL_LINEAR_DISTANCE_CLAMPED 0xD004 +#define AL_EXPONENT_DISTANCE 0xD005 +#define AL_EXPONENT_DISTANCE_CLAMPED 0xD006 + +/* + * Renderer State management + */ +AL_API void AL_APIENTRY alEnable( ALenum capability ); + +AL_API void AL_APIENTRY alDisable( ALenum capability ); + +AL_API ALboolean AL_APIENTRY alIsEnabled( ALenum capability ); + + +/* + * State retrieval + */ +AL_API const ALchar* AL_APIENTRY alGetString( ALenum param ); + +AL_API void AL_APIENTRY alGetBooleanv( ALenum param, ALboolean* data ); + +AL_API void AL_APIENTRY alGetIntegerv( ALenum param, ALint* data ); + +AL_API void AL_APIENTRY alGetFloatv( ALenum param, ALfloat* data ); + +AL_API void AL_APIENTRY alGetDoublev( ALenum param, ALdouble* data ); + +AL_API ALboolean AL_APIENTRY alGetBoolean( ALenum param ); + +AL_API ALint AL_APIENTRY alGetInteger( ALenum param ); + +AL_API ALfloat AL_APIENTRY alGetFloat( ALenum param ); + +AL_API ALdouble AL_APIENTRY alGetDouble( ALenum param ); + + +/* + * Error support. + * Obtain the most recent error generated in the AL state machine. + */ +AL_API ALenum AL_APIENTRY alGetError( void ); + + +/* + * Extension support. + * Query for the presence of an extension, and obtain any appropriate + * function pointers and enum values. + */ +AL_API ALboolean AL_APIENTRY alIsExtensionPresent( const ALchar* extname ); + +AL_API void* AL_APIENTRY alGetProcAddress( const ALchar* fname ); + +AL_API ALenum AL_APIENTRY alGetEnumValue( const ALchar* ename ); + + +/* + * LISTENER + * Listener represents the location and orientation of the + * 'user' in 3D-space. + * + * Properties include: - + * + * Gain AL_GAIN ALfloat + * Position AL_POSITION ALfloat[3] + * Velocity AL_VELOCITY ALfloat[3] + * Orientation AL_ORIENTATION ALfloat[6] (Forward then Up vectors) +*/ + +/* + * Set Listener parameters + */ +AL_API void AL_APIENTRY alListenerf( ALenum param, ALfloat value ); + +AL_API void AL_APIENTRY alListener3f( ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); + +AL_API void AL_APIENTRY alListenerfv( ALenum param, const ALfloat* values ); + +AL_API void AL_APIENTRY alListeneri( ALenum param, ALint value ); + +AL_API void AL_APIENTRY alListener3i( ALenum param, ALint value1, ALint value2, ALint value3 ); + +AL_API void AL_APIENTRY alListeneriv( ALenum param, const ALint* values ); + +/* + * Get Listener parameters + */ +AL_API void AL_APIENTRY alGetListenerf( ALenum param, ALfloat* value ); + +AL_API void AL_APIENTRY alGetListener3f( ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3 ); + +AL_API void AL_APIENTRY alGetListenerfv( ALenum param, ALfloat* values ); + +AL_API void AL_APIENTRY alGetListeneri( ALenum param, ALint* value ); + +AL_API void AL_APIENTRY alGetListener3i( ALenum param, ALint *value1, ALint *value2, ALint *value3 ); + +AL_API void AL_APIENTRY alGetListeneriv( ALenum param, ALint* values ); + + +/** + * SOURCE + * Sources represent individual sound objects in 3D-space. + * Sources take the PCM data provided in the specified Buffer, + * apply Source-specific modifications, and then + * submit them to be mixed according to spatial arrangement etc. + * + * Properties include: - + * + * Gain AL_GAIN ALfloat + * Min Gain AL_MIN_GAIN ALfloat + * Max Gain AL_MAX_GAIN ALfloat + * Position AL_POSITION ALfloat[3] + * Velocity AL_VELOCITY ALfloat[3] + * Direction AL_DIRECTION ALfloat[3] + * Head Relative Mode AL_SOURCE_RELATIVE ALint (AL_TRUE or AL_FALSE) + * Reference Distance AL_REFERENCE_DISTANCE ALfloat + * Max Distance AL_MAX_DISTANCE ALfloat + * RollOff Factor AL_ROLLOFF_FACTOR ALfloat + * Inner Angle AL_CONE_INNER_ANGLE ALint or ALfloat + * Outer Angle AL_CONE_OUTER_ANGLE ALint or ALfloat + * Cone Outer Gain AL_CONE_OUTER_GAIN ALint or ALfloat + * Pitch AL_PITCH ALfloat + * Looping AL_LOOPING ALint (AL_TRUE or AL_FALSE) + * MS Offset AL_MSEC_OFFSET ALint or ALfloat + * Byte Offset AL_BYTE_OFFSET ALint or ALfloat + * Sample Offset AL_SAMPLE_OFFSET ALint or ALfloat + * Attached Buffer AL_BUFFER ALint + * State (Query only) AL_SOURCE_STATE ALint + * Buffers Queued (Query only) AL_BUFFERS_QUEUED ALint + * Buffers Processed (Query only) AL_BUFFERS_PROCESSED ALint + */ + +/* Create Source objects */ +AL_API void AL_APIENTRY alGenSources( ALsizei n, ALuint* sources ); + +/* Delete Source objects */ +AL_API void AL_APIENTRY alDeleteSources( ALsizei n, const ALuint* sources ); + +/* Verify a handle is a valid Source */ +AL_API ALboolean AL_APIENTRY alIsSource( ALuint sid ); + +/* + * Set Source parameters + */ +AL_API void AL_APIENTRY alSourcef( ALuint sid, ALenum param, ALfloat value ); + +AL_API void AL_APIENTRY alSource3f( ALuint sid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); + +AL_API void AL_APIENTRY alSourcefv( ALuint sid, ALenum param, const ALfloat* values ); + +AL_API void AL_APIENTRY alSourcei( ALuint sid, ALenum param, ALint value ); + +AL_API void AL_APIENTRY alSource3i( ALuint sid, ALenum param, ALint value1, ALint value2, ALint value3 ); + +AL_API void AL_APIENTRY alSourceiv( ALuint sid, ALenum param, const ALint* values ); + +/* + * Get Source parameters + */ +AL_API void AL_APIENTRY alGetSourcef( ALuint sid, ALenum param, ALfloat* value ); + +AL_API void AL_APIENTRY alGetSource3f( ALuint sid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); + +AL_API void AL_APIENTRY alGetSourcefv( ALuint sid, ALenum param, ALfloat* values ); + +AL_API void AL_APIENTRY alGetSourcei( ALuint sid, ALenum param, ALint* value ); + +AL_API void AL_APIENTRY alGetSource3i( ALuint sid, ALenum param, ALint* value1, ALint* value2, ALint* value3); + +AL_API void AL_APIENTRY alGetSourceiv( ALuint sid, ALenum param, ALint* values ); + + +/* + * Source vector based playback calls + */ + +/* Play, replay, or resume (if paused) a list of Sources */ +AL_API void AL_APIENTRY alSourcePlayv( ALsizei ns, const ALuint *sids ); + +/* Stop a list of Sources */ +AL_API void AL_APIENTRY alSourceStopv( ALsizei ns, const ALuint *sids ); + +/* Rewind a list of Sources */ +AL_API void AL_APIENTRY alSourceRewindv( ALsizei ns, const ALuint *sids ); + +/* Pause a list of Sources */ +AL_API void AL_APIENTRY alSourcePausev( ALsizei ns, const ALuint *sids ); + +/* + * Source based playback calls + */ + +/* Play, replay, or resume a Source */ +AL_API void AL_APIENTRY alSourcePlay( ALuint sid ); + +/* Stop a Source */ +AL_API void AL_APIENTRY alSourceStop( ALuint sid ); + +/* Rewind a Source (set playback postiton to beginning) */ +AL_API void AL_APIENTRY alSourceRewind( ALuint sid ); + +/* Pause a Source */ +AL_API void AL_APIENTRY alSourcePause( ALuint sid ); + +/* + * Source Queuing + */ +AL_API void AL_APIENTRY alSourceQueueBuffers( ALuint sid, ALsizei numEntries, const ALuint *bids ); + +AL_API void AL_APIENTRY alSourceUnqueueBuffers( ALuint sid, ALsizei numEntries, ALuint *bids ); + + +/** + * BUFFER + * Buffer objects are storage space for sample data. + * Buffers are referred to by Sources. One Buffer can be used + * by multiple Sources. + * + * Properties include: - + * + * Frequency (Query only) AL_FREQUENCY ALint + * Size (Query only) AL_SIZE ALint + * Bits (Query only) AL_BITS ALint + * Channels (Query only) AL_CHANNELS ALint + */ + +/* Create Buffer objects */ +AL_API void AL_APIENTRY alGenBuffers( ALsizei n, ALuint* buffers ); + +/* Delete Buffer objects */ +AL_API void AL_APIENTRY alDeleteBuffers( ALsizei n, const ALuint* buffers ); + +/* Verify a handle is a valid Buffer */ +AL_API ALboolean AL_APIENTRY alIsBuffer( ALuint bid ); + +/* Specify the data to be copied into a buffer */ +AL_API void AL_APIENTRY alBufferData( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq ); + +/* + * Set Buffer parameters + */ +AL_API void AL_APIENTRY alBufferf( ALuint bid, ALenum param, ALfloat value ); + +AL_API void AL_APIENTRY alBuffer3f( ALuint bid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); + +AL_API void AL_APIENTRY alBufferfv( ALuint bid, ALenum param, const ALfloat* values ); + +AL_API void AL_APIENTRY alBufferi( ALuint bid, ALenum param, ALint value ); + +AL_API void AL_APIENTRY alBuffer3i( ALuint bid, ALenum param, ALint value1, ALint value2, ALint value3 ); + +AL_API void AL_APIENTRY alBufferiv( ALuint bid, ALenum param, const ALint* values ); + +/* + * Get Buffer parameters + */ +AL_API void AL_APIENTRY alGetBufferf( ALuint bid, ALenum param, ALfloat* value ); + +AL_API void AL_APIENTRY alGetBuffer3f( ALuint bid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); + +AL_API void AL_APIENTRY alGetBufferfv( ALuint bid, ALenum param, ALfloat* values ); + +AL_API void AL_APIENTRY alGetBufferi( ALuint bid, ALenum param, ALint* value ); + +AL_API void AL_APIENTRY alGetBuffer3i( ALuint bid, ALenum param, ALint* value1, ALint* value2, ALint* value3); + +AL_API void AL_APIENTRY alGetBufferiv( ALuint bid, ALenum param, ALint* values ); + + +/* + * Global Parameters + */ +AL_API void AL_APIENTRY alDopplerFactor( ALfloat value ); + +AL_API void AL_APIENTRY alDopplerVelocity( ALfloat value ); + +AL_API void AL_APIENTRY alSpeedOfSound( ALfloat value ); + +AL_API void AL_APIENTRY alDistanceModel( ALenum distanceModel ); + +/* + * Pointer-to-function types, useful for dynamically getting AL entry points. + */ +typedef void (AL_APIENTRY *LPALENABLE)( ALenum capability ); +typedef void (AL_APIENTRY *LPALDISABLE)( ALenum capability ); +typedef ALboolean (AL_APIENTRY *LPALISENABLED)( ALenum capability ); +typedef const ALchar* (AL_APIENTRY *LPALGETSTRING)( ALenum param ); +typedef void (AL_APIENTRY *LPALGETBOOLEANV)( ALenum param, ALboolean* data ); +typedef void (AL_APIENTRY *LPALGETINTEGERV)( ALenum param, ALint* data ); +typedef void (AL_APIENTRY *LPALGETFLOATV)( ALenum param, ALfloat* data ); +typedef void (AL_APIENTRY *LPALGETDOUBLEV)( ALenum param, ALdouble* data ); +typedef ALboolean (AL_APIENTRY *LPALGETBOOLEAN)( ALenum param ); +typedef ALint (AL_APIENTRY *LPALGETINTEGER)( ALenum param ); +typedef ALfloat (AL_APIENTRY *LPALGETFLOAT)( ALenum param ); +typedef ALdouble (AL_APIENTRY *LPALGETDOUBLE)( ALenum param ); +typedef ALenum (AL_APIENTRY *LPALGETERROR)( void ); +typedef ALboolean (AL_APIENTRY *LPALISEXTENSIONPRESENT)(const ALchar* extname ); +typedef void* (AL_APIENTRY *LPALGETPROCADDRESS)( const ALchar* fname ); +typedef ALenum (AL_APIENTRY *LPALGETENUMVALUE)( const ALchar* ename ); +typedef void (AL_APIENTRY *LPALLISTENERF)( ALenum param, ALfloat value ); +typedef void (AL_APIENTRY *LPALLISTENER3F)( ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (AL_APIENTRY *LPALLISTENERFV)( ALenum param, const ALfloat* values ); +typedef void (AL_APIENTRY *LPALLISTENERI)( ALenum param, ALint value ); +typedef void (AL_APIENTRY *LPALLISTENER3I)( ALenum param, ALint value1, ALint value2, ALint value3 ); +typedef void (AL_APIENTRY *LPALLISTENERIV)( ALenum param, const ALint* values ); +typedef void (AL_APIENTRY *LPALGETLISTENERF)( ALenum param, ALfloat* value ); +typedef void (AL_APIENTRY *LPALGETLISTENER3F)( ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3 ); +typedef void (AL_APIENTRY *LPALGETLISTENERFV)( ALenum param, ALfloat* values ); +typedef void (AL_APIENTRY *LPALGETLISTENERI)( ALenum param, ALint* value ); +typedef void (AL_APIENTRY *LPALGETLISTENER3I)( ALenum param, ALint *value1, ALint *value2, ALint *value3 ); +typedef void (AL_APIENTRY *LPALGETLISTENERIV)( ALenum param, ALint* values ); +typedef void (AL_APIENTRY *LPALGENSOURCES)( ALsizei n, ALuint* sources ); +typedef void (AL_APIENTRY *LPALDELETESOURCES)( ALsizei n, const ALuint* sources ); +typedef ALboolean (AL_APIENTRY *LPALISSOURCE)( ALuint sid ); +typedef void (AL_APIENTRY *LPALSOURCEF)( ALuint sid, ALenum param, ALfloat value); +typedef void (AL_APIENTRY *LPALSOURCE3F)( ALuint sid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (AL_APIENTRY *LPALSOURCEFV)( ALuint sid, ALenum param, const ALfloat* values ); +typedef void (AL_APIENTRY *LPALSOURCEI)( ALuint sid, ALenum param, ALint value); +typedef void (AL_APIENTRY *LPALSOURCE3I)( ALuint sid, ALenum param, ALint value1, ALint value2, ALint value3 ); +typedef void (AL_APIENTRY *LPALSOURCEIV)( ALuint sid, ALenum param, const ALint* values ); +typedef void (AL_APIENTRY *LPALGETSOURCEF)( ALuint sid, ALenum param, ALfloat* value ); +typedef void (AL_APIENTRY *LPALGETSOURCE3F)( ALuint sid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +typedef void (AL_APIENTRY *LPALGETSOURCEFV)( ALuint sid, ALenum param, ALfloat* values ); +typedef void (AL_APIENTRY *LPALGETSOURCEI)( ALuint sid, ALenum param, ALint* value ); +typedef void (AL_APIENTRY *LPALGETSOURCE3I)( ALuint sid, ALenum param, ALint* value1, ALint* value2, ALint* value3); +typedef void (AL_APIENTRY *LPALGETSOURCEIV)( ALuint sid, ALenum param, ALint* values ); +typedef void (AL_APIENTRY *LPALSOURCEPLAYV)( ALsizei ns, const ALuint *sids ); +typedef void (AL_APIENTRY *LPALSOURCESTOPV)( ALsizei ns, const ALuint *sids ); +typedef void (AL_APIENTRY *LPALSOURCEREWINDV)( ALsizei ns, const ALuint *sids ); +typedef void (AL_APIENTRY *LPALSOURCEPAUSEV)( ALsizei ns, const ALuint *sids ); +typedef void (AL_APIENTRY *LPALSOURCEPLAY)( ALuint sid ); +typedef void (AL_APIENTRY *LPALSOURCESTOP)( ALuint sid ); +typedef void (AL_APIENTRY *LPALSOURCEREWIND)( ALuint sid ); +typedef void (AL_APIENTRY *LPALSOURCEPAUSE)( ALuint sid ); +typedef void (AL_APIENTRY *LPALSOURCEQUEUEBUFFERS)(ALuint sid, ALsizei numEntries, const ALuint *bids ); +typedef void (AL_APIENTRY *LPALSOURCEUNQUEUEBUFFERS)(ALuint sid, ALsizei numEntries, ALuint *bids ); +typedef void (AL_APIENTRY *LPALGENBUFFERS)( ALsizei n, ALuint* buffers ); +typedef void (AL_APIENTRY *LPALDELETEBUFFERS)( ALsizei n, const ALuint* buffers ); +typedef ALboolean (AL_APIENTRY *LPALISBUFFER)( ALuint bid ); +typedef void (AL_APIENTRY *LPALBUFFERDATA)( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq ); +typedef void (AL_APIENTRY *LPALBUFFERF)( ALuint bid, ALenum param, ALfloat value); +typedef void (AL_APIENTRY *LPALBUFFER3F)( ALuint bid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (AL_APIENTRY *LPALBUFFERFV)( ALuint bid, ALenum param, const ALfloat* values ); +typedef void (AL_APIENTRY *LPALBUFFERI)( ALuint bid, ALenum param, ALint value); +typedef void (AL_APIENTRY *LPALBUFFER3I)( ALuint bid, ALenum param, ALint value1, ALint value2, ALint value3 ); +typedef void (AL_APIENTRY *LPALBUFFERIV)( ALuint bid, ALenum param, const ALint* values ); +typedef void (AL_APIENTRY *LPALGETBUFFERF)( ALuint bid, ALenum param, ALfloat* value ); +typedef void (AL_APIENTRY *LPALGETBUFFER3F)( ALuint bid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +typedef void (AL_APIENTRY *LPALGETBUFFERFV)( ALuint bid, ALenum param, ALfloat* values ); +typedef void (AL_APIENTRY *LPALGETBUFFERI)( ALuint bid, ALenum param, ALint* value ); +typedef void (AL_APIENTRY *LPALGETBUFFER3I)( ALuint bid, ALenum param, ALint* value1, ALint* value2, ALint* value3); +typedef void (AL_APIENTRY *LPALGETBUFFERIV)( ALuint bid, ALenum param, ALint* values ); +typedef void (AL_APIENTRY *LPALDOPPLERFACTOR)( ALfloat value ); +typedef void (AL_APIENTRY *LPALDOPPLERVELOCITY)( ALfloat value ); +typedef void (AL_APIENTRY *LPALSPEEDOFSOUND)( ALfloat value ); +typedef void (AL_APIENTRY *LPALDISTANCEMODEL)( ALenum distanceModel ); + +#if defined(TARGET_OS_MAC) && TARGET_OS_MAC + #pragma export off +#endif + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif /* AL_AL_H */ diff --git a/jni/OpenAL/alc.h b/jni/OpenAL/alc.h new file mode 100644 index 0000000..04543a0 --- /dev/null +++ b/jni/OpenAL/alc.h @@ -0,0 +1,277 @@ +#ifndef AL_ALC_H +#define AL_ALC_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(AL_LIBTYPE_STATIC) + #define ALC_API +#elif defined(_WIN32) && !defined(_XBOX) + #if defined(AL_BUILD_LIBRARY) + #define ALC_API __declspec(dllexport) + #else + #define ALC_API __declspec(dllimport) + #endif +#else + #if defined(AL_BUILD_LIBRARY) && defined(HAVE_GCC_VISIBILITY) + #define ALC_API __attribute__((visibility("protected"))) + #else + #define ALC_API extern + #endif +#endif + +#if defined(_WIN32) + #define ALC_APIENTRY __cdecl +#else + #define ALC_APIENTRY +#endif + +#if defined(TARGET_OS_MAC) && TARGET_OS_MAC + #pragma export on +#endif + +/* + * The ALCAPI, ALCAPIENTRY, and ALC_INVALID macros are deprecated, but are + * included for applications porting code from AL 1.0 + */ +#define ALCAPI ALC_API +#define ALCAPIENTRY ALC_APIENTRY +#define ALC_INVALID 0 + + +#define ALC_VERSION_0_1 1 + +typedef struct ALCdevice_struct ALCdevice; +typedef struct ALCcontext_struct ALCcontext; + + +/** 8-bit boolean */ +typedef char ALCboolean; + +/** character */ +typedef char ALCchar; + +/** signed 8-bit 2's complement integer */ +typedef signed char ALCbyte; + +/** unsigned 8-bit integer */ +typedef unsigned char ALCubyte; + +/** signed 16-bit 2's complement integer */ +typedef short ALCshort; + +/** unsigned 16-bit integer */ +typedef unsigned short ALCushort; + +/** signed 32-bit 2's complement integer */ +typedef int ALCint; + +/** unsigned 32-bit integer */ +typedef unsigned int ALCuint; + +/** non-negative 32-bit binary integer size */ +typedef int ALCsizei; + +/** enumerated 32-bit value */ +typedef int ALCenum; + +/** 32-bit IEEE754 floating-point */ +typedef float ALCfloat; + +/** 64-bit IEEE754 floating-point */ +typedef double ALCdouble; + +/** void type (for opaque pointers only) */ +typedef void ALCvoid; + + +/* Enumerant values begin at column 50. No tabs. */ + +/* Boolean False. */ +#define ALC_FALSE 0 + +/* Boolean True. */ +#define ALC_TRUE 1 + +/** + * followed by Hz + */ +#define ALC_FREQUENCY 0x1007 + +/** + * followed by Hz + */ +#define ALC_REFRESH 0x1008 + +/** + * followed by AL_TRUE, AL_FALSE + */ +#define ALC_SYNC 0x1009 + +/** + * followed by Num of requested Mono (3D) Sources + */ +#define ALC_MONO_SOURCES 0x1010 + +/** + * followed by Num of requested Stereo Sources + */ +#define ALC_STEREO_SOURCES 0x1011 + +/** + * errors + */ + +/** + * No error + */ +#define ALC_NO_ERROR ALC_FALSE + +/** + * No device + */ +#define ALC_INVALID_DEVICE 0xA001 + +/** + * invalid context ID + */ +#define ALC_INVALID_CONTEXT 0xA002 + +/** + * bad enum + */ +#define ALC_INVALID_ENUM 0xA003 + +/** + * bad value + */ +#define ALC_INVALID_VALUE 0xA004 + +/** + * Out of memory. + */ +#define ALC_OUT_OF_MEMORY 0xA005 + + +/** + * The Specifier string for default device + */ +#define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 +#define ALC_DEVICE_SPECIFIER 0x1005 +#define ALC_EXTENSIONS 0x1006 + +#define ALC_MAJOR_VERSION 0x1000 +#define ALC_MINOR_VERSION 0x1001 + +#define ALC_ATTRIBUTES_SIZE 0x1002 +#define ALC_ALL_ATTRIBUTES 0x1003 + + +/** + * Capture extension + */ +#define ALC_CAPTURE_DEVICE_SPECIFIER 0x310 +#define ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER 0x311 +#define ALC_CAPTURE_SAMPLES 0x312 + + +/* + * Context Management + */ +ALC_API ALCcontext * ALC_APIENTRY alcCreateContext( ALCdevice *device, const ALCint* attrlist ); + +ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent( ALCcontext *context ); + +ALC_API void ALC_APIENTRY alcProcessContext( ALCcontext *context ); + +ALC_API void ALC_APIENTRY alcSuspendContext( ALCcontext *context ); + +ALC_API void ALC_APIENTRY alcDestroyContext( ALCcontext *context ); + +ALC_API ALCcontext * ALC_APIENTRY alcGetCurrentContext( void ); + +ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice( ALCcontext *context ); + + +/* + * Device Management + */ +ALC_API ALCdevice * ALC_APIENTRY alcOpenDevice( const ALCchar *devicename ); + +ALC_API ALCboolean ALC_APIENTRY alcCloseDevice( ALCdevice *device ); + + +/* + * Error support. + * Obtain the most recent Context error + */ +ALC_API ALCenum ALC_APIENTRY alcGetError( ALCdevice *device ); + + +/* + * Extension support. + * Query for the presence of an extension, and obtain any appropriate + * function pointers and enum values. + */ +ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent( ALCdevice *device, const ALCchar *extname ); + +ALC_API void * ALC_APIENTRY alcGetProcAddress( ALCdevice *device, const ALCchar *funcname ); + +ALC_API ALCenum ALC_APIENTRY alcGetEnumValue( ALCdevice *device, const ALCchar *enumname ); + + +/* + * Query functions + */ +ALC_API const ALCchar * ALC_APIENTRY alcGetString( ALCdevice *device, ALCenum param ); + +ALC_API void ALC_APIENTRY alcGetIntegerv( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *data ); + + +/* + * Capture functions + */ +ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice( const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize ); + +ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice( ALCdevice *device ); + +ALC_API void ALC_APIENTRY alcCaptureStart( ALCdevice *device ); + +ALC_API void ALC_APIENTRY alcCaptureStop( ALCdevice *device ); + +ALC_API void ALC_APIENTRY alcCaptureSamples( ALCdevice *device, ALCvoid *buffer, ALCsizei samples ); + +/* + * Pointer-to-function types, useful for dynamically getting ALC entry points. + */ +typedef ALCcontext * (ALC_APIENTRY *LPALCCREATECONTEXT) (ALCdevice *device, const ALCint *attrlist); +typedef ALCboolean (ALC_APIENTRY *LPALCMAKECONTEXTCURRENT)( ALCcontext *context ); +typedef void (ALC_APIENTRY *LPALCPROCESSCONTEXT)( ALCcontext *context ); +typedef void (ALC_APIENTRY *LPALCSUSPENDCONTEXT)( ALCcontext *context ); +typedef void (ALC_APIENTRY *LPALCDESTROYCONTEXT)( ALCcontext *context ); +typedef ALCcontext * (ALC_APIENTRY *LPALCGETCURRENTCONTEXT)( void ); +typedef ALCdevice * (ALC_APIENTRY *LPALCGETCONTEXTSDEVICE)( ALCcontext *context ); +typedef ALCdevice * (ALC_APIENTRY *LPALCOPENDEVICE)( const ALCchar *devicename ); +typedef ALCboolean (ALC_APIENTRY *LPALCCLOSEDEVICE)( ALCdevice *device ); +typedef ALCenum (ALC_APIENTRY *LPALCGETERROR)( ALCdevice *device ); +typedef ALCboolean (ALC_APIENTRY *LPALCISEXTENSIONPRESENT)( ALCdevice *device, const ALCchar *extname ); +typedef void * (ALC_APIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname ); +typedef ALCenum (ALC_APIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname ); +typedef const ALCchar* (ALC_APIENTRY *LPALCGETSTRING)( ALCdevice *device, ALCenum param ); +typedef void (ALC_APIENTRY *LPALCGETINTEGERV)( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *dest ); +typedef ALCdevice * (ALC_APIENTRY *LPALCCAPTUREOPENDEVICE)( const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize ); +typedef ALCboolean (ALC_APIENTRY *LPALCCAPTURECLOSEDEVICE)( ALCdevice *device ); +typedef void (ALC_APIENTRY *LPALCCAPTURESTART)( ALCdevice *device ); +typedef void (ALC_APIENTRY *LPALCCAPTURESTOP)( ALCdevice *device ); +typedef void (ALC_APIENTRY *LPALCCAPTURESAMPLES)( ALCdevice *device, ALCvoid *buffer, ALCsizei samples ); + +#if defined(TARGET_OS_MAC) && TARGET_OS_MAC + #pragma export off +#endif + +#if defined(__cplusplus) +} +#endif + +#endif /* AL_ALC_H */ diff --git a/jni/OpenAL/build.mk b/jni/OpenAL/build.mk new file mode 100644 index 0000000..6ffe5a2 --- /dev/null +++ b/jni/OpenAL/build.mk @@ -0,0 +1,54 @@ +MODULE = System/OpenAL + +CCFLAGS = \ + -I$(SYSDIR) \ + -I$(SYSDIR)/OpenAL/include \ + -I$(SYSDIR)/OpenAL/OpenAL32/Include \ + -DAL_BUILD_LIBRARY \ + -DAL_ALEXT_PROTOTYPES \ + -DVERDE_USE_REAL_FILE_IO \ + +OBJECTS = \ + +ifneq ($(OS), mac) +OBJECTS += \ + OpenAL32/alAuxEffectSlot.o \ + OpenAL32/alBuffer.o \ + OpenAL32/alDatabuffer.o \ + OpenAL32/alEffect.o \ + OpenAL32/alError.o \ + OpenAL32/alExtension.o \ + OpenAL32/alFilter.o \ + OpenAL32/alListener.o \ + OpenAL32/alSource.o \ + OpenAL32/alState.o \ + OpenAL32/alThunk.o \ + Alc/ALc.o \ + Alc/alcConfig.o \ + Alc/alcEcho.o \ + Alc/alcModulator.o \ + Alc/alcReverb.o \ + Alc/alcRing.o \ + Alc/alcThread.o \ + Alc/ALu.o \ + Alc/bs2b.o \ + Alc/null.o \ + Alc/panning.o \ + Alc/mixer.o \ + +endif + +ifeq ($(TARGET_OS), android) +OBJECTS += Alc/audiotrack.o +ifdef POST_FROYO +OBJECTS += Alc/opensles.o +endif +CCFLAGS += -I/Developer/AndroidNDK/platforms/android-9/arch-arm/usr/include +CCFLAGS += -DOPENAL_FIXED_POINT -DOPENAL_FIXED_POINT_SHIFT=16 +endif + +ifeq ($(OS), linux) +OBJECTS += Alc/oss.o +endif + +include $(ROOTDIR)/module.mk diff --git a/jni/OpenAL/include/AL/al.h b/jni/OpenAL/include/AL/al.h new file mode 100644 index 0000000..e3a1bc5 --- /dev/null +++ b/jni/OpenAL/include/AL/al.h @@ -0,0 +1,814 @@ +#ifndef AL_AL_H +#define AL_AL_H + +#ifdef ANDROID +#include +#ifndef LOGI +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,"OpenAL",__VA_ARGS__) +#endif +#ifndef LOGE +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"OpenAL",__VA_ARGS__) +#endif +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(AL_LIBTYPE_STATIC) + #define AL_API +#elif defined(_WIN32) && !defined(_XBOX) + #if defined(AL_BUILD_LIBRARY) + #define AL_API __declspec(dllexport) + #else + #define AL_API __declspec(dllimport) + #endif +#else + #if defined(AL_BUILD_LIBRARY) && defined(HAVE_GCC_VISIBILITY) + #define AL_API __attribute__((visibility("protected"))) + #else + #define AL_API extern + #endif +#endif + +#if defined(_WIN32) + #define AL_APIENTRY __cdecl +#else + #define AL_APIENTRY +#endif + +#if defined(TARGET_OS_MAC) && TARGET_OS_MAC + #pragma export on +#endif + +/* + * The OPENAL, ALAPI, ALAPIENTRY, AL_INVALID, AL_ILLEGAL_ENUM, and + * AL_ILLEGAL_COMMAND macros are deprecated, but are included for + * applications porting code from AL 1.0 + */ +#define OPENAL +#define ALAPI AL_API +#define ALAPIENTRY AL_APIENTRY +#define AL_INVALID (-1) +#define AL_ILLEGAL_ENUM AL_INVALID_ENUM +#define AL_ILLEGAL_COMMAND AL_INVALID_OPERATION + +#define AL_VERSION_1_0 +#define AL_VERSION_1_1 + + +/** 8-bit boolean */ +typedef char ALboolean; + +/** character */ +typedef char ALchar; + +/** signed 8-bit 2's complement integer */ +typedef signed char ALbyte; + +/** unsigned 8-bit integer */ +typedef unsigned char ALubyte; + +/** signed 16-bit 2's complement integer */ +typedef short ALshort; + +/** unsigned 16-bit integer */ +typedef unsigned short ALushort; + +/** signed 32-bit 2's complement integer */ +typedef int ALint; + +/** unsigned 32-bit integer */ +typedef unsigned int ALuint; + +/** non-negative 32-bit binary integer size */ +typedef int ALsizei; + +/** enumerated 32-bit value */ +typedef int ALenum; + +/** 32-bit IEEE754 floating-point */ +typedef float ALfloat; + +/** 64-bit IEEE754 floating-point */ +typedef double ALdouble; + +#ifdef OPENAL_FIXED_POINT +/** Types and Macros for fixed-point math */ +#ifndef INT64_MAX +typedef long long int64_t; +#define INT64_MAX 9223372036854775807LL + +#endif +#ifndef INT32_MAX +typedef int int32_t; +#define INT32_MAX 2147483647 +#endif + +// FIXME(apportable) make this int32_t +typedef int64_t ALfp; +typedef int64_t ALdfp; + +#define ONE (1<=0 ? 0.5 : -0.5))) +#define ALfp2float(x) ((float)(x) / (1<=0 ? 0.5 : -0.5))) +#define ALdfp2double(x) ((double)(x) / (1<> OPENAL_FIXED_POINT_SHIFT)) + +#define int2ALdfp(x) ((ALdfp)(x) << OPENAL_FIXED_POINT_SHIFT) +#define ALdfp2int(x) ((ALint)((x) >> OPENAL_FIXED_POINT_SHIFT)) + +#define ALfpMult(x,y) ((ALfp)((((int64_t)(x))*((int64_t)(y)))>>OPENAL_FIXED_POINT_SHIFT)) +#define ALfpDiv(x,y) ((ALfp)(((int64_t)(x) << OPENAL_FIXED_POINT_SHIFT) / (y))) + +#define ALdfpMult(x,y) ALfpMult(x,y) +#define ALdfpDiv(x,y) ALfpDiv(x,y) + +#define __isnan(x) (0) +#define __cos(x) (float2ALfp(cos(ALfp2float(x)))) +#define __sin(x) (float2ALfp(sin(ALfp2float(x)))) +#define __log10(x) (float2ALfp(log10(ALfp2float(x)))) +#define __atan(x) (float2ALfp(atan(ALfp2float(x)))) + +#define toALfpConst(x) ((x)*(1< Hz + */ +#define ALC_FREQUENCY 0x1007 + +/** + * followed by Hz + */ +#define ALC_REFRESH 0x1008 + +/** + * followed by AL_TRUE, AL_FALSE + */ +#define ALC_SYNC 0x1009 + +/** + * followed by Num of requested Mono (3D) Sources + */ +#define ALC_MONO_SOURCES 0x1010 + +/** + * followed by Num of requested Stereo Sources + */ +#define ALC_STEREO_SOURCES 0x1011 + +/** + * errors + */ + +/** + * No error + */ +#define ALC_NO_ERROR ALC_FALSE + +/** + * No device + */ +#define ALC_INVALID_DEVICE 0xA001 + +/** + * invalid context ID + */ +#define ALC_INVALID_CONTEXT 0xA002 + +/** + * bad enum + */ +#define ALC_INVALID_ENUM 0xA003 + +/** + * bad value + */ +#define ALC_INVALID_VALUE 0xA004 + +/** + * Out of memory. + */ +#define ALC_OUT_OF_MEMORY 0xA005 + + +/** + * The Specifier string for default device + */ +#define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 +#define ALC_DEVICE_SPECIFIER 0x1005 +#define ALC_EXTENSIONS 0x1006 + +#define ALC_MAJOR_VERSION 0x1000 +#define ALC_MINOR_VERSION 0x1001 + +#define ALC_ATTRIBUTES_SIZE 0x1002 +#define ALC_ALL_ATTRIBUTES 0x1003 + + +/** + * Capture extension + */ +#define ALC_CAPTURE_DEVICE_SPECIFIER 0x310 +#define ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER 0x311 +#define ALC_CAPTURE_SAMPLES 0x312 + + +/* + * Context Management + */ +ALC_API ALCcontext * ALC_APIENTRY alcCreateContext( ALCdevice *device, const ALCint* attrlist ); + +ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent( ALCcontext *context ); + +ALC_API void ALC_APIENTRY alcProcessContext( ALCcontext *context ); + +ALC_API void ALC_APIENTRY alcSuspendContext( ALCcontext *context ); + +ALC_API void ALC_APIENTRY alcDestroyContext( ALCcontext *context ); + +ALC_API ALCcontext * ALC_APIENTRY alcGetCurrentContext( void ); + +ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice( ALCcontext *context ); + + +/* + * Device Management + */ +ALC_API ALCdevice * ALC_APIENTRY alcOpenDevice( const ALCchar *devicename ); + +ALC_API ALCboolean ALC_APIENTRY alcCloseDevice( ALCdevice *device ); + + +/* + * Error support. + * Obtain the most recent Context error + */ +ALC_API ALCenum ALC_APIENTRY alcGetError( ALCdevice *device ); + + +/* + * Extension support. + * Query for the presence of an extension, and obtain any appropriate + * function pointers and enum values. + */ +ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent( ALCdevice *device, const ALCchar *extname ); + +ALC_API void * ALC_APIENTRY alcGetProcAddress( ALCdevice *device, const ALCchar *funcname ); + +ALC_API ALCenum ALC_APIENTRY alcGetEnumValue( ALCdevice *device, const ALCchar *enumname ); + + +/* + * Query functions + */ +ALC_API const ALCchar * ALC_APIENTRY alcGetString( ALCdevice *device, ALCenum param ); + +ALC_API void ALC_APIENTRY alcGetIntegerv( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *data ); + + +/* + * Capture functions + */ +ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice( const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize ); + +ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice( ALCdevice *device ); + +ALC_API void ALC_APIENTRY alcCaptureStart( ALCdevice *device ); + +ALC_API void ALC_APIENTRY alcCaptureStop( ALCdevice *device ); + +ALC_API void ALC_APIENTRY alcCaptureSamples( ALCdevice *device, ALCvoid *buffer, ALCsizei samples ); + +/* + * Pointer-to-function types, useful for dynamically getting ALC entry points. + */ +typedef ALCcontext * (ALC_APIENTRY *LPALCCREATECONTEXT) (ALCdevice *device, const ALCint *attrlist); +typedef ALCboolean (ALC_APIENTRY *LPALCMAKECONTEXTCURRENT)( ALCcontext *context ); +typedef void (ALC_APIENTRY *LPALCPROCESSCONTEXT)( ALCcontext *context ); +typedef void (ALC_APIENTRY *LPALCSUSPENDCONTEXT)( ALCcontext *context ); +typedef void (ALC_APIENTRY *LPALCDESTROYCONTEXT)( ALCcontext *context ); +typedef ALCcontext * (ALC_APIENTRY *LPALCGETCURRENTCONTEXT)( void ); +typedef ALCdevice * (ALC_APIENTRY *LPALCGETCONTEXTSDEVICE)( ALCcontext *context ); +typedef ALCdevice * (ALC_APIENTRY *LPALCOPENDEVICE)( const ALCchar *devicename ); +typedef ALCboolean (ALC_APIENTRY *LPALCCLOSEDEVICE)( ALCdevice *device ); +typedef ALCenum (ALC_APIENTRY *LPALCGETERROR)( ALCdevice *device ); +typedef ALCboolean (ALC_APIENTRY *LPALCISEXTENSIONPRESENT)( ALCdevice *device, const ALCchar *extname ); +typedef void * (ALC_APIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname ); +typedef ALCenum (ALC_APIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname ); +typedef const ALCchar* (ALC_APIENTRY *LPALCGETSTRING)( ALCdevice *device, ALCenum param ); +typedef void (ALC_APIENTRY *LPALCGETINTEGERV)( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *dest ); +typedef ALCdevice * (ALC_APIENTRY *LPALCCAPTUREOPENDEVICE)( const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize ); +typedef ALCboolean (ALC_APIENTRY *LPALCCAPTURECLOSEDEVICE)( ALCdevice *device ); +typedef void (ALC_APIENTRY *LPALCCAPTURESTART)( ALCdevice *device ); +typedef void (ALC_APIENTRY *LPALCCAPTURESTOP)( ALCdevice *device ); +typedef void (ALC_APIENTRY *LPALCCAPTURESAMPLES)( ALCdevice *device, ALCvoid *buffer, ALCsizei samples ); + +#if defined(TARGET_OS_MAC) && TARGET_OS_MAC + #pragma export off +#endif + +#if defined(__cplusplus) +} +#endif + +#endif /* AL_ALC_H */ diff --git a/jni/OpenAL/include/AL/alext.h b/jni/OpenAL/include/AL/alext.h new file mode 100644 index 0000000..f3c7bca --- /dev/null +++ b/jni/OpenAL/include/AL/alext.h @@ -0,0 +1,165 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2008 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef AL_ALEXT_H +#define AL_ALEXT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef AL_LOKI_IMA_ADPCM_format +#define AL_LOKI_IMA_ADPCM_format 1 +#define AL_FORMAT_IMA_ADPCM_MONO16_EXT 0x10000 +#define AL_FORMAT_IMA_ADPCM_STEREO16_EXT 0x10001 +#endif + +#ifndef AL_LOKI_WAVE_format +#define AL_LOKI_WAVE_format 1 +#define AL_FORMAT_WAVE_EXT 0x10002 +#endif + +#ifndef AL_EXT_vorbis +#define AL_EXT_vorbis 1 +#define AL_FORMAT_VORBIS_EXT 0x10003 +#endif + +#ifndef AL_LOKI_quadriphonic +#define AL_LOKI_quadriphonic 1 +#define AL_FORMAT_QUAD8_LOKI 0x10004 +#define AL_FORMAT_QUAD16_LOKI 0x10005 +#endif + +#ifndef AL_EXT_float32 +#define AL_EXT_float32 1 +#define AL_FORMAT_MONO_FLOAT32 0x10010 +#define AL_FORMAT_STEREO_FLOAT32 0x10011 +#endif + +#ifndef AL_EXT_double +#define AL_EXT_double 1 +#define AL_FORMAT_MONO_DOUBLE_EXT 0x10012 +#define AL_FORMAT_STEREO_DOUBLE_EXT 0x10013 +#endif + +#ifndef ALC_LOKI_audio_channel +#define ALC_LOKI_audio_channel 1 +#define ALC_CHAN_MAIN_LOKI 0x500001 +#define ALC_CHAN_PCM_LOKI 0x500002 +#define ALC_CHAN_CD_LOKI 0x500003 +#endif + +#ifndef ALC_ENUMERATE_ALL_EXT +#define ALC_ENUMERATE_ALL_EXT 1 +#define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012 +#define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#endif + +#ifndef AL_EXT_MCFORMATS +#define AL_EXT_MCFORMATS 1 +#define AL_FORMAT_QUAD8 0x1204 +#define AL_FORMAT_QUAD16 0x1205 +#define AL_FORMAT_QUAD32 0x1206 +#define AL_FORMAT_REAR8 0x1207 +#define AL_FORMAT_REAR16 0x1208 +#define AL_FORMAT_REAR32 0x1209 +#define AL_FORMAT_51CHN8 0x120A +#define AL_FORMAT_51CHN16 0x120B +#define AL_FORMAT_51CHN32 0x120C +#define AL_FORMAT_61CHN8 0x120D +#define AL_FORMAT_61CHN16 0x120E +#define AL_FORMAT_61CHN32 0x120F +#define AL_FORMAT_71CHN8 0x1210 +#define AL_FORMAT_71CHN16 0x1211 +#define AL_FORMAT_71CHN32 0x1212 +#endif + +#ifndef AL_EXT_MULAW_MCFORMATS +#define AL_EXT_MULAW_MCFORMATS 1 +#define AL_FORMAT_MONO_MULAW 0x10014 +#define AL_FORMAT_STEREO_MULAW 0x10015 +#define AL_FORMAT_QUAD_MULAW 0x10021 +#define AL_FORMAT_REAR_MULAW 0x10022 +#define AL_FORMAT_51CHN_MULAW 0x10023 +#define AL_FORMAT_61CHN_MULAW 0x10024 +#define AL_FORMAT_71CHN_MULAW 0x10025 +#endif + +#ifndef AL_EXT_IMA4 +#define AL_EXT_IMA4 1 +#define AL_FORMAT_MONO_IMA4 0x1300 +#define AL_FORMAT_STEREO_IMA4 0x1301 +#endif + +#ifndef AL_EXT_STATIC_BUFFER +#define AL_EXT_STATIC_BUFFER 1 +typedef ALvoid (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALint,ALenum,ALvoid*,ALsizei,ALsizei); +#ifdef AL_ALEXT_PROTOTYPES +AL_API ALvoid AL_APIENTRY alBufferDataStatic(const ALint buffer, ALenum format, ALvoid *data, ALsizei len, ALsizei freq); +#endif +#endif + +#ifndef ALC_EXT_EFX +#define ALC_EXT_EFX 1 +#include "efx.h" +#endif + +#ifndef ALC_EXT_disconnect +#define ALC_EXT_disconnect 1 +#define ALC_CONNECTED 0x313 +#endif + +#ifndef ALC_EXT_thread_local_context +#define ALC_EXT_thread_local_context 1 +typedef ALCboolean (ALC_APIENTRY*PFNALCSETTHREADCONTEXTPROC)(ALCcontext *context); +typedef ALCcontext* (ALC_APIENTRY*PFNALCGETTHREADCONTEXTPROC)(void); +#ifdef AL_ALEXT_PROTOTYPES +ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context); +ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); +#endif +#endif + +#ifndef AL_EXT_source_distance_model +#define AL_EXT_source_distance_model 1 +#define AL_SOURCE_DISTANCE_MODEL 0x200 +#endif + +#ifndef AL_SOFT_buffer_sub_data +#define AL_SOFT_buffer_sub_data 1 +#define AL_BYTE_RW_OFFSETS_SOFT 0x1031 +#define AL_SAMPLE_RW_OFFSETS_SOFT 0x1032 +typedef ALvoid (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei); +#ifdef AL_ALEXT_PROTOTYPES +AL_API ALvoid AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length); +#endif +#endif + +#ifndef AL_SOFT_loop_points +#define AL_SOFT_loop_points 1 +#define AL_LOOP_POINTS_SOFT 0x2015 +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/jni/OpenAL/include/AL/efx-creative.h b/jni/OpenAL/include/AL/efx-creative.h new file mode 100644 index 0000000..0a04c98 --- /dev/null +++ b/jni/OpenAL/include/AL/efx-creative.h @@ -0,0 +1,3 @@ +/* The tokens that would be defined here are already defined in efx.h. This + * empty file is here to provide compatibility with Windows-based projects + * that would include it. */ diff --git a/jni/OpenAL/include/AL/efx.h b/jni/OpenAL/include/AL/efx.h new file mode 100644 index 0000000..0ccef95 --- /dev/null +++ b/jni/OpenAL/include/AL/efx.h @@ -0,0 +1,758 @@ +#ifndef AL_EFX_H +#define AL_EFX_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#define ALC_EXT_EFX_NAME "ALC_EXT_EFX" + +#define ALC_EFX_MAJOR_VERSION 0x20001 +#define ALC_EFX_MINOR_VERSION 0x20002 +#define ALC_MAX_AUXILIARY_SENDS 0x20003 + + +/* Listener properties. */ +#define AL_METERS_PER_UNIT 0x20004 + +/* Source properties. */ +#define AL_DIRECT_FILTER 0x20005 +#define AL_AUXILIARY_SEND_FILTER 0x20006 +#define AL_AIR_ABSORPTION_FACTOR 0x20007 +#define AL_ROOM_ROLLOFF_FACTOR 0x20008 +#define AL_CONE_OUTER_GAINHF 0x20009 +#define AL_DIRECT_FILTER_GAINHF_AUTO 0x2000A +#define AL_AUXILIARY_SEND_FILTER_GAIN_AUTO 0x2000B +#define AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO 0x2000C + + +/* Effect properties. */ + +/* Reverb effect parameters */ +#define AL_REVERB_DENSITY 0x0001 +#define AL_REVERB_DIFFUSION 0x0002 +#define AL_REVERB_GAIN 0x0003 +#define AL_REVERB_GAINHF 0x0004 +#define AL_REVERB_DECAY_TIME 0x0005 +#define AL_REVERB_DECAY_HFRATIO 0x0006 +#define AL_REVERB_REFLECTIONS_GAIN 0x0007 +#define AL_REVERB_REFLECTIONS_DELAY 0x0008 +#define AL_REVERB_LATE_REVERB_GAIN 0x0009 +#define AL_REVERB_LATE_REVERB_DELAY 0x000A +#define AL_REVERB_AIR_ABSORPTION_GAINHF 0x000B +#define AL_REVERB_ROOM_ROLLOFF_FACTOR 0x000C +#define AL_REVERB_DECAY_HFLIMIT 0x000D + +/* EAX Reverb effect parameters */ +#define AL_EAXREVERB_DENSITY 0x0001 +#define AL_EAXREVERB_DIFFUSION 0x0002 +#define AL_EAXREVERB_GAIN 0x0003 +#define AL_EAXREVERB_GAINHF 0x0004 +#define AL_EAXREVERB_GAINLF 0x0005 +#define AL_EAXREVERB_DECAY_TIME 0x0006 +#define AL_EAXREVERB_DECAY_HFRATIO 0x0007 +#define AL_EAXREVERB_DECAY_LFRATIO 0x0008 +#define AL_EAXREVERB_REFLECTIONS_GAIN 0x0009 +#define AL_EAXREVERB_REFLECTIONS_DELAY 0x000A +#define AL_EAXREVERB_REFLECTIONS_PAN 0x000B +#define AL_EAXREVERB_LATE_REVERB_GAIN 0x000C +#define AL_EAXREVERB_LATE_REVERB_DELAY 0x000D +#define AL_EAXREVERB_LATE_REVERB_PAN 0x000E +#define AL_EAXREVERB_ECHO_TIME 0x000F +#define AL_EAXREVERB_ECHO_DEPTH 0x0010 +#define AL_EAXREVERB_MODULATION_TIME 0x0011 +#define AL_EAXREVERB_MODULATION_DEPTH 0x0012 +#define AL_EAXREVERB_AIR_ABSORPTION_GAINHF 0x0013 +#define AL_EAXREVERB_HFREFERENCE 0x0014 +#define AL_EAXREVERB_LFREFERENCE 0x0015 +#define AL_EAXREVERB_ROOM_ROLLOFF_FACTOR 0x0016 +#define AL_EAXREVERB_DECAY_HFLIMIT 0x0017 + +/* Chorus effect parameters */ +#define AL_CHORUS_WAVEFORM 0x0001 +#define AL_CHORUS_PHASE 0x0002 +#define AL_CHORUS_RATE 0x0003 +#define AL_CHORUS_DEPTH 0x0004 +#define AL_CHORUS_FEEDBACK 0x0005 +#define AL_CHORUS_DELAY 0x0006 + +/* Distortion effect parameters */ +#define AL_DISTORTION_EDGE 0x0001 +#define AL_DISTORTION_GAIN 0x0002 +#define AL_DISTORTION_LOWPASS_CUTOFF 0x0003 +#define AL_DISTORTION_EQCENTER 0x0004 +#define AL_DISTORTION_EQBANDWIDTH 0x0005 + +/* Echo effect parameters */ +#define AL_ECHO_DELAY 0x0001 +#define AL_ECHO_LRDELAY 0x0002 +#define AL_ECHO_DAMPING 0x0003 +#define AL_ECHO_FEEDBACK 0x0004 +#define AL_ECHO_SPREAD 0x0005 + +/* Flanger effect parameters */ +#define AL_FLANGER_WAVEFORM 0x0001 +#define AL_FLANGER_PHASE 0x0002 +#define AL_FLANGER_RATE 0x0003 +#define AL_FLANGER_DEPTH 0x0004 +#define AL_FLANGER_FEEDBACK 0x0005 +#define AL_FLANGER_DELAY 0x0006 + +/* Frequency shifter effect parameters */ +#define AL_FREQUENCY_SHIFTER_FREQUENCY 0x0001 +#define AL_FREQUENCY_SHIFTER_LEFT_DIRECTION 0x0002 +#define AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION 0x0003 + +/* Vocal morpher effect parameters */ +#define AL_VOCAL_MORPHER_PHONEMEA 0x0001 +#define AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING 0x0002 +#define AL_VOCAL_MORPHER_PHONEMEB 0x0003 +#define AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING 0x0004 +#define AL_VOCAL_MORPHER_WAVEFORM 0x0005 +#define AL_VOCAL_MORPHER_RATE 0x0006 + +/* Pitchshifter effect parameters */ +#define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001 +#define AL_PITCH_SHIFTER_FINE_TUNE 0x0002 + +/* Ringmodulator effect parameters */ +#define AL_RING_MODULATOR_FREQUENCY 0x0001 +#define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002 +#define AL_RING_MODULATOR_WAVEFORM 0x0003 + +/* Autowah effect parameters */ +#define AL_AUTOWAH_ATTACK_TIME 0x0001 +#define AL_AUTOWAH_RELEASE_TIME 0x0002 +#define AL_AUTOWAH_RESONANCE 0x0003 +#define AL_AUTOWAH_PEAK_GAIN 0x0004 + +/* Compressor effect parameters */ +#define AL_COMPRESSOR_ONOFF 0x0001 + +/* Equalizer effect parameters */ +#define AL_EQUALIZER_LOW_GAIN 0x0001 +#define AL_EQUALIZER_LOW_CUTOFF 0x0002 +#define AL_EQUALIZER_MID1_GAIN 0x0003 +#define AL_EQUALIZER_MID1_CENTER 0x0004 +#define AL_EQUALIZER_MID1_WIDTH 0x0005 +#define AL_EQUALIZER_MID2_GAIN 0x0006 +#define AL_EQUALIZER_MID2_CENTER 0x0007 +#define AL_EQUALIZER_MID2_WIDTH 0x0008 +#define AL_EQUALIZER_HIGH_GAIN 0x0009 +#define AL_EQUALIZER_HIGH_CUTOFF 0x000A + +/* Effect type */ +#define AL_EFFECT_FIRST_PARAMETER 0x0000 +#define AL_EFFECT_LAST_PARAMETER 0x8000 +#define AL_EFFECT_TYPE 0x8001 + +/* Effect types, used with the AL_EFFECT_TYPE property */ +#define AL_EFFECT_NULL 0x0000 +#define AL_EFFECT_REVERB 0x0001 +#define AL_EFFECT_CHORUS 0x0002 +#define AL_EFFECT_DISTORTION 0x0003 +#define AL_EFFECT_ECHO 0x0004 +#define AL_EFFECT_FLANGER 0x0005 +#define AL_EFFECT_FREQUENCY_SHIFTER 0x0006 +#define AL_EFFECT_VOCAL_MORPHER 0x0007 +#define AL_EFFECT_PITCH_SHIFTER 0x0008 +#define AL_EFFECT_RING_MODULATOR 0x0009 +#define AL_EFFECT_AUTOWAH 0x000A +#define AL_EFFECT_COMPRESSOR 0x000B +#define AL_EFFECT_EQUALIZER 0x000C +#define AL_EFFECT_EAXREVERB 0x8000 + +/* Auxiliary Effect Slot properties. */ +#define AL_EFFECTSLOT_EFFECT 0x0001 +#define AL_EFFECTSLOT_GAIN 0x0002 +#define AL_EFFECTSLOT_AUXILIARY_SEND_AUTO 0x0003 + +/* NULL Auxiliary Slot ID to disable a source send. */ +#define AL_EFFECTSLOT_NULL 0x0000 + + +/* Filter properties. */ + +/* Lowpass filter parameters */ +#define AL_LOWPASS_GAIN 0x0001 +#define AL_LOWPASS_GAINHF 0x0002 + +/* Highpass filter parameters */ +#define AL_HIGHPASS_GAIN 0x0001 +#define AL_HIGHPASS_GAINLF 0x0002 + +/* Bandpass filter parameters */ +#define AL_BANDPASS_GAIN 0x0001 +#define AL_BANDPASS_GAINLF 0x0002 +#define AL_BANDPASS_GAINHF 0x0003 + +/* Filter type */ +#define AL_FILTER_FIRST_PARAMETER 0x0000 +#define AL_FILTER_LAST_PARAMETER 0x8000 +#define AL_FILTER_TYPE 0x8001 + +/* Filter types, used with the AL_FILTER_TYPE property */ +#define AL_FILTER_NULL 0x0000 +#define AL_FILTER_LOWPASS 0x0001 +#define AL_FILTER_HIGHPASS 0x0002 +#define AL_FILTER_BANDPASS 0x0003 + + +/* Effect object function types. */ +typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei, ALuint*); +typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei, ALuint*); +typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint); +typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint, ALenum, ALint); +typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint, ALenum, ALfloat); +typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*); + +/* Filter object function types. */ +typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei, ALuint*); +typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei, ALuint*); +typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint); +typedef void (AL_APIENTRY *LPALFILTERI)(ALuint, ALenum, ALint); +typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALFILTERF)(ALuint, ALenum, ALfloat); +typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint, ALenum, ALfloat*); + +/* Auxiliary Effect Slot object function types. */ +typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*); +typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*); +typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); + +#ifdef AL_ALEXT_PROTOTYPES +AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects); +AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, ALuint *effects); +AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect); +AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue); +AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue); +AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); +AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue); +AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue); +AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); + +AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters); +AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, ALuint *filters); +AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter); +AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue); +AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue); +AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); +AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue); +AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue); +AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); + +AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); +AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); +AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); +#endif + +/* Filter ranges and defaults. */ + +/* Lowpass filter */ +#define LOWPASS_MIN_GAIN (0.0f) +#define LOWPASS_MAX_GAIN (1.0f) +#define LOWPASS_DEFAULT_GAIN (1.0f) + +#define LOWPASS_MIN_GAINHF (0.0f) +#define LOWPASS_MAX_GAINHF (1.0f) +#define LOWPASS_DEFAULT_GAINHF (1.0f) + +/* Highpass filter */ +#define HIGHPASS_MIN_GAIN (0.0f) +#define HIGHPASS_MAX_GAIN (1.0f) +#define HIGHPASS_DEFAULT_GAIN (1.0f) + +#define HIGHPASS_MIN_GAINLF (0.0f) +#define HIGHPASS_MAX_GAINLF (1.0f) +#define HIGHPASS_DEFAULT_GAINLF (1.0f) + +/* Bandpass filter */ +#define BANDPASS_MIN_GAIN (0.0f) +#define BANDPASS_MAX_GAIN (1.0f) +#define BANDPASS_DEFAULT_GAIN (1.0f) + +#define BANDPASS_MIN_GAINHF (0.0f) +#define BANDPASS_MAX_GAINHF (1.0f) +#define BANDPASS_DEFAULT_GAINHF (1.0f) + +#define BANDPASS_MIN_GAINLF (0.0f) +#define BANDPASS_MAX_GAINLF (1.0f) +#define BANDPASS_DEFAULT_GAINLF (1.0f) + + +/* Effect parameter ranges and defaults. */ + +/* Standard reverb effect */ +#define AL_REVERB_MIN_DENSITY (0.0f) +#define AL_REVERB_MAX_DENSITY (1.0f) +#define AL_REVERB_DEFAULT_DENSITY (1.0f) + +#define AL_REVERB_MIN_DIFFUSION (0.0f) +#define AL_REVERB_MAX_DIFFUSION (1.0f) +#define AL_REVERB_DEFAULT_DIFFUSION (1.0f) + +#define AL_REVERB_MIN_GAIN (0.0f) +#define AL_REVERB_MAX_GAIN (1.0f) +#define AL_REVERB_DEFAULT_GAIN (0.32f) + +#define AL_REVERB_MIN_GAINHF (0.0f) +#define AL_REVERB_MAX_GAINHF (1.0f) +#define AL_REVERB_DEFAULT_GAINHF (0.89f) + +#define AL_REVERB_MIN_DECAY_TIME (0.1f) +#define AL_REVERB_MAX_DECAY_TIME (20.0f) +#define AL_REVERB_DEFAULT_DECAY_TIME (1.49f) + +#define AL_REVERB_MIN_DECAY_HFRATIO (0.1f) +#define AL_REVERB_MAX_DECAY_HFRATIO (2.0f) +#define AL_REVERB_DEFAULT_DECAY_HFRATIO (0.83f) + +#define AL_REVERB_MIN_REFLECTIONS_GAIN (0.0f) +#define AL_REVERB_MAX_REFLECTIONS_GAIN (3.16f) +#define AL_REVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) + +#define AL_REVERB_MIN_REFLECTIONS_DELAY (0.0f) +#define AL_REVERB_MAX_REFLECTIONS_DELAY (0.3f) +#define AL_REVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) + +#define AL_REVERB_MIN_LATE_REVERB_GAIN (0.0f) +#define AL_REVERB_MAX_LATE_REVERB_GAIN (10.0f) +#define AL_REVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) + +#define AL_REVERB_MIN_LATE_REVERB_DELAY (0.0f) +#define AL_REVERB_MAX_LATE_REVERB_DELAY (0.1f) +#define AL_REVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) + +#define AL_REVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) +#define AL_REVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) +#define AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) + +#define AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) + +#define AL_REVERB_MIN_DECAY_HFLIMIT AL_FALSE +#define AL_REVERB_MAX_DECAY_HFLIMIT AL_TRUE +#define AL_REVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE + +/* EAX reverb effect */ +#define AL_EAXREVERB_MIN_DENSITY (0.0f) +#define AL_EAXREVERB_MAX_DENSITY (1.0f) +#define AL_EAXREVERB_DEFAULT_DENSITY (1.0f) + +#define AL_EAXREVERB_MIN_DIFFUSION (0.0f) +#define AL_EAXREVERB_MAX_DIFFUSION (1.0f) +#define AL_EAXREVERB_DEFAULT_DIFFUSION (1.0f) + +#define AL_EAXREVERB_MIN_GAIN (0.0f) +#define AL_EAXREVERB_MAX_GAIN (1.0f) +#define AL_EAXREVERB_DEFAULT_GAIN (0.32f) + +#define AL_EAXREVERB_MIN_GAINHF (0.0f) +#define AL_EAXREVERB_MAX_GAINHF (1.0f) +#define AL_EAXREVERB_DEFAULT_GAINHF (0.89f) + +#define AL_EAXREVERB_MIN_GAINLF (0.0f) +#define AL_EAXREVERB_MAX_GAINLF (1.0f) +#define AL_EAXREVERB_DEFAULT_GAINLF (1.0f) + +#define AL_EAXREVERB_MIN_DECAY_TIME (0.1f) +#define AL_EAXREVERB_MAX_DECAY_TIME (20.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_TIME (1.49f) + +#define AL_EAXREVERB_MIN_DECAY_HFRATIO (0.1f) +#define AL_EAXREVERB_MAX_DECAY_HFRATIO (2.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_HFRATIO (0.83f) + +#define AL_EAXREVERB_MIN_DECAY_LFRATIO (0.1f) +#define AL_EAXREVERB_MAX_DECAY_LFRATIO (2.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_LFRATIO (1.0f) + +#define AL_EAXREVERB_MIN_REFLECTIONS_GAIN (0.0f) +#define AL_EAXREVERB_MAX_REFLECTIONS_GAIN (3.16f) +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) + +#define AL_EAXREVERB_MIN_REFLECTIONS_DELAY (0.0f) +#define AL_EAXREVERB_MAX_REFLECTIONS_DELAY (0.3f) +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) + +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ (0.0f) + +#define AL_EAXREVERB_MIN_LATE_REVERB_GAIN (0.0f) +#define AL_EAXREVERB_MAX_LATE_REVERB_GAIN (10.0f) +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) + +#define AL_EAXREVERB_MIN_LATE_REVERB_DELAY (0.0f) +#define AL_EAXREVERB_MAX_LATE_REVERB_DELAY (0.1f) +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) + +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ (0.0f) + +#define AL_EAXREVERB_MIN_ECHO_TIME (0.075f) +#define AL_EAXREVERB_MAX_ECHO_TIME (0.25f) +#define AL_EAXREVERB_DEFAULT_ECHO_TIME (0.25f) + +#define AL_EAXREVERB_MIN_ECHO_DEPTH (0.0f) +#define AL_EAXREVERB_MAX_ECHO_DEPTH (1.0f) +#define AL_EAXREVERB_DEFAULT_ECHO_DEPTH (0.0f) + +#define AL_EAXREVERB_MIN_MODULATION_TIME (0.04f) +#define AL_EAXREVERB_MAX_MODULATION_TIME (4.0f) +#define AL_EAXREVERB_DEFAULT_MODULATION_TIME (0.25f) + +#define AL_EAXREVERB_MIN_MODULATION_DEPTH (0.0f) +#define AL_EAXREVERB_MAX_MODULATION_DEPTH (1.0f) +#define AL_EAXREVERB_DEFAULT_MODULATION_DEPTH (0.0f) + +#define AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) +#define AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) +#define AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) + +#define AL_EAXREVERB_MIN_HFREFERENCE (1000.0f) +#define AL_EAXREVERB_MAX_HFREFERENCE (20000.0f) +#define AL_EAXREVERB_DEFAULT_HFREFERENCE (5000.0f) + +#define AL_EAXREVERB_MIN_LFREFERENCE (20.0f) +#define AL_EAXREVERB_MAX_LFREFERENCE (1000.0f) +#define AL_EAXREVERB_DEFAULT_LFREFERENCE (250.0f) + +#define AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) + +#define AL_EAXREVERB_MIN_DECAY_HFLIMIT AL_FALSE +#define AL_EAXREVERB_MAX_DECAY_HFLIMIT AL_TRUE +#define AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE + +/* Chorus effect */ +#define AL_CHORUS_WAVEFORM_SINUSOID (0) +#define AL_CHORUS_WAVEFORM_TRIANGLE (1) + +#define AL_CHORUS_MIN_WAVEFORM (0) +#define AL_CHORUS_MAX_WAVEFORM (1) +#define AL_CHORUS_DEFAULT_WAVEFORM (1) + +#define AL_CHORUS_MIN_PHASE (-180) +#define AL_CHORUS_MAX_PHASE (180) +#define AL_CHORUS_DEFAULT_PHASE (90) + +#define AL_CHORUS_MIN_RATE (0.0f) +#define AL_CHORUS_MAX_RATE (10.0f) +#define AL_CHORUS_DEFAULT_RATE (1.1f) + +#define AL_CHORUS_MIN_DEPTH (0.0f) +#define AL_CHORUS_MAX_DEPTH (1.0f) +#define AL_CHORUS_DEFAULT_DEPTH (0.1f) + +#define AL_CHORUS_MIN_FEEDBACK (-1.0f) +#define AL_CHORUS_MAX_FEEDBACK (1.0f) +#define AL_CHORUS_DEFAULT_FEEDBACK (0.25f) + +#define AL_CHORUS_MIN_DELAY (0.0f) +#define AL_CHORUS_MAX_DELAY (0.016f) +#define AL_CHORUS_DEFAULT_DELAY (0.016f) + +/* Distortion effect */ +#define AL_DISTORTION_MIN_EDGE (0.0f) +#define AL_DISTORTION_MAX_EDGE (1.0f) +#define AL_DISTORTION_DEFAULT_EDGE (0.2f) + +#define AL_DISTORTION_MIN_GAIN (0.01f) +#define AL_DISTORTION_MAX_GAIN (1.0f) +#define AL_DISTORTION_DEFAULT_GAIN (0.05f) + +#define AL_DISTORTION_MIN_LOWPASS_CUTOFF (80.0f) +#define AL_DISTORTION_MAX_LOWPASS_CUTOFF (24000.0f) +#define AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF (8000.0f) + +#define AL_DISTORTION_MIN_EQCENTER (80.0f) +#define AL_DISTORTION_MAX_EQCENTER (24000.0f) +#define AL_DISTORTION_DEFAULT_EQCENTER (3600.0f) + +#define AL_DISTORTION_MIN_EQBANDWIDTH (80.0f) +#define AL_DISTORTION_MAX_EQBANDWIDTH (24000.0f) +#define AL_DISTORTION_DEFAULT_EQBANDWIDTH (3600.0f) + +/* Echo effect */ +#define AL_ECHO_MIN_DELAY (0.0f) +#define AL_ECHO_MAX_DELAY (0.207f) +#define AL_ECHO_DEFAULT_DELAY (0.1f) + +#define AL_ECHO_MIN_LRDELAY (0.0f) +#define AL_ECHO_MAX_LRDELAY (0.404f) +#define AL_ECHO_DEFAULT_LRDELAY (0.1f) + +#define AL_ECHO_MIN_DAMPING (0.0f) +#define AL_ECHO_MAX_DAMPING (0.99f) +#define AL_ECHO_DEFAULT_DAMPING (0.5f) + +#define AL_ECHO_MIN_FEEDBACK (0.0f) +#define AL_ECHO_MAX_FEEDBACK (1.0f) +#define AL_ECHO_DEFAULT_FEEDBACK (0.5f) + +#define AL_ECHO_MIN_SPREAD (-1.0f) +#define AL_ECHO_MAX_SPREAD (1.0f) +#define AL_ECHO_DEFAULT_SPREAD (-1.0f) + +/* Flanger effect */ +#define AL_FLANGER_WAVEFORM_SINUSOID (0) +#define AL_FLANGER_WAVEFORM_TRIANGLE (1) + +#define AL_FLANGER_MIN_WAVEFORM (0) +#define AL_FLANGER_MAX_WAVEFORM (1) +#define AL_FLANGER_DEFAULT_WAVEFORM (1) + +#define AL_FLANGER_MIN_PHASE (-180) +#define AL_FLANGER_MAX_PHASE (180) +#define AL_FLANGER_DEFAULT_PHASE (0) + +#define AL_FLANGER_MIN_RATE (0.0f) +#define AL_FLANGER_MAX_RATE (10.0f) +#define AL_FLANGER_DEFAULT_RATE (0.27f) + +#define AL_FLANGER_MIN_DEPTH (0.0f) +#define AL_FLANGER_MAX_DEPTH (1.0f) +#define AL_FLANGER_DEFAULT_DEPTH (1.0f) + +#define AL_FLANGER_MIN_FEEDBACK (-1.0f) +#define AL_FLANGER_MAX_FEEDBACK (1.0f) +#define AL_FLANGER_DEFAULT_FEEDBACK (-0.5f) + +#define AL_FLANGER_MIN_DELAY (0.0f) +#define AL_FLANGER_MAX_DELAY (0.004f) +#define AL_FLANGER_DEFAULT_DELAY (0.002f) + +/* Frequency shifter effect */ +#define AL_FREQUENCY_SHIFTER_MIN_FREQUENCY (0.0f) +#define AL_FREQUENCY_SHIFTER_MAX_FREQUENCY (24000.0f) +#define AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY (0.0f) + +#define AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION (0) +#define AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION (2) +#define AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION (0) + +#define AL_FREQUENCY_SHIFTER_DIRECTION_DOWN (0) +#define AL_FREQUENCY_SHIFTER_DIRECTION_UP (1) +#define AL_FREQUENCY_SHIFTER_DIRECTION_OFF (2) + +#define AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION (0) +#define AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION (2) +#define AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION (0) + +/* Vocal morpher effect */ +#define AL_VOCAL_MORPHER_MIN_PHONEMEA (0) +#define AL_VOCAL_MORPHER_MAX_PHONEMEA (29) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA (0) + +#define AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING (-24) +#define AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING (24) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING (0) + +#define AL_VOCAL_MORPHER_MIN_PHONEMEB (0) +#define AL_VOCAL_MORPHER_MAX_PHONEMEB (29) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB (10) + +#define AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING (-24) +#define AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING (24) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING (0) + +#define AL_VOCAL_MORPHER_PHONEME_A (0) +#define AL_VOCAL_MORPHER_PHONEME_E (1) +#define AL_VOCAL_MORPHER_PHONEME_I (2) +#define AL_VOCAL_MORPHER_PHONEME_O (3) +#define AL_VOCAL_MORPHER_PHONEME_U (4) +#define AL_VOCAL_MORPHER_PHONEME_AA (5) +#define AL_VOCAL_MORPHER_PHONEME_AE (6) +#define AL_VOCAL_MORPHER_PHONEME_AH (7) +#define AL_VOCAL_MORPHER_PHONEME_AO (8) +#define AL_VOCAL_MORPHER_PHONEME_EH (9) +#define AL_VOCAL_MORPHER_PHONEME_ER (10) +#define AL_VOCAL_MORPHER_PHONEME_IH (11) +#define AL_VOCAL_MORPHER_PHONEME_IY (12) +#define AL_VOCAL_MORPHER_PHONEME_UH (13) +#define AL_VOCAL_MORPHER_PHONEME_UW (14) +#define AL_VOCAL_MORPHER_PHONEME_B (15) +#define AL_VOCAL_MORPHER_PHONEME_D (16) +#define AL_VOCAL_MORPHER_PHONEME_F (17) +#define AL_VOCAL_MORPHER_PHONEME_G (18) +#define AL_VOCAL_MORPHER_PHONEME_J (19) +#define AL_VOCAL_MORPHER_PHONEME_K (20) +#define AL_VOCAL_MORPHER_PHONEME_L (21) +#define AL_VOCAL_MORPHER_PHONEME_M (22) +#define AL_VOCAL_MORPHER_PHONEME_N (23) +#define AL_VOCAL_MORPHER_PHONEME_P (24) +#define AL_VOCAL_MORPHER_PHONEME_R (25) +#define AL_VOCAL_MORPHER_PHONEME_S (26) +#define AL_VOCAL_MORPHER_PHONEME_T (27) +#define AL_VOCAL_MORPHER_PHONEME_V (28) +#define AL_VOCAL_MORPHER_PHONEME_Z (29) + +#define AL_VOCAL_MORPHER_WAVEFORM_SINUSOID (0) +#define AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE (1) +#define AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH (2) + +#define AL_VOCAL_MORPHER_MIN_WAVEFORM (0) +#define AL_VOCAL_MORPHER_MAX_WAVEFORM (2) +#define AL_VOCAL_MORPHER_DEFAULT_WAVEFORM (0) + +#define AL_VOCAL_MORPHER_MIN_RATE (0.0f) +#define AL_VOCAL_MORPHER_MAX_RATE (10.0f) +#define AL_VOCAL_MORPHER_DEFAULT_RATE (1.41f) + +/* Pitch shifter effect */ +#define AL_PITCH_SHIFTER_MIN_COARSE_TUNE (-12) +#define AL_PITCH_SHIFTER_MAX_COARSE_TUNE (12) +#define AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE (12) + +#define AL_PITCH_SHIFTER_MIN_FINE_TUNE (-50) +#define AL_PITCH_SHIFTER_MAX_FINE_TUNE (50) +#define AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE (0) + +/* Ring modulator effect */ +#define AL_RING_MODULATOR_MIN_FREQUENCY (0.0f) +#define AL_RING_MODULATOR_MAX_FREQUENCY (8000.0f) +#define AL_RING_MODULATOR_DEFAULT_FREQUENCY (440.0f) + +#define AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF (0.0f) +#define AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF (24000.0f) +#define AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF (800.0f) + +#define AL_RING_MODULATOR_SINUSOID (0) +#define AL_RING_MODULATOR_SAWTOOTH (1) +#define AL_RING_MODULATOR_SQUARE (2) + +#define AL_RING_MODULATOR_MIN_WAVEFORM (0) +#define AL_RING_MODULATOR_MAX_WAVEFORM (2) +#define AL_RING_MODULATOR_DEFAULT_WAVEFORM (0) + +/* Autowah effect */ +#define AL_AUTOWAH_MIN_ATTACK_TIME (0.0001f) +#define AL_AUTOWAH_MAX_ATTACK_TIME (1.0f) +#define AL_AUTOWAH_DEFAULT_ATTACK_TIME (0.06f) + +#define AL_AUTOWAH_MIN_RELEASE_TIME (0.0001f) +#define AL_AUTOWAH_MAX_RELEASE_TIME (1.0f) +#define AL_AUTOWAH_DEFAULT_RELEASE_TIME (0.06f) + +#define AL_AUTOWAH_MIN_RESONANCE (2.0f) +#define AL_AUTOWAH_MAX_RESONANCE (1000.0f) +#define AL_AUTOWAH_DEFAULT_RESONANCE (1000.0f) + +#define AL_AUTOWAH_MIN_PEAK_GAIN (0.00003f) +#define AL_AUTOWAH_MAX_PEAK_GAIN (31621.0f) +#define AL_AUTOWAH_DEFAULT_PEAK_GAIN (11.22f) + +/* Compressor effect */ +#define AL_COMPRESSOR_MIN_ONOFF (0) +#define AL_COMPRESSOR_MAX_ONOFF (1) +#define AL_COMPRESSOR_DEFAULT_ONOFF (1) + +/* Equalizer effect */ +#define AL_EQUALIZER_MIN_LOW_GAIN (0.126f) +#define AL_EQUALIZER_MAX_LOW_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_LOW_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_LOW_CUTOFF (50.0f) +#define AL_EQUALIZER_MAX_LOW_CUTOFF (800.0f) +#define AL_EQUALIZER_DEFAULT_LOW_CUTOFF (200.0f) + +#define AL_EQUALIZER_MIN_MID1_GAIN (0.126f) +#define AL_EQUALIZER_MAX_MID1_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_MID1_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_MID1_CENTER (200.0f) +#define AL_EQUALIZER_MAX_MID1_CENTER (3000.0f) +#define AL_EQUALIZER_DEFAULT_MID1_CENTER (500.0f) + +#define AL_EQUALIZER_MIN_MID1_WIDTH (0.01f) +#define AL_EQUALIZER_MAX_MID1_WIDTH (1.0f) +#define AL_EQUALIZER_DEFAULT_MID1_WIDTH (1.0f) + +#define AL_EQUALIZER_MIN_MID2_GAIN (0.126f) +#define AL_EQUALIZER_MAX_MID2_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_MID2_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_MID2_CENTER (1000.0f) +#define AL_EQUALIZER_MAX_MID2_CENTER (8000.0f) +#define AL_EQUALIZER_DEFAULT_MID2_CENTER (3000.0f) + +#define AL_EQUALIZER_MIN_MID2_WIDTH (0.01f) +#define AL_EQUALIZER_MAX_MID2_WIDTH (1.0f) +#define AL_EQUALIZER_DEFAULT_MID2_WIDTH (1.0f) + +#define AL_EQUALIZER_MIN_HIGH_GAIN (0.126f) +#define AL_EQUALIZER_MAX_HIGH_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_HIGH_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_HIGH_CUTOFF (4000.0f) +#define AL_EQUALIZER_MAX_HIGH_CUTOFF (16000.0f) +#define AL_EQUALIZER_DEFAULT_HIGH_CUTOFF (6000.0f) + + +/* Source parameter value ranges and defaults. */ +#define AL_MIN_AIR_ABSORPTION_FACTOR (0.0f) +#define AL_MAX_AIR_ABSORPTION_FACTOR (10.0f) +#define AL_DEFAULT_AIR_ABSORPTION_FACTOR (0.0f) + +#define AL_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) + +#define AL_MIN_CONE_OUTER_GAINHF (0.0f) +#define AL_MAX_CONE_OUTER_GAINHF (1.0f) +#define AL_DEFAULT_CONE_OUTER_GAINHF (1.0f) + +#define AL_MIN_DIRECT_FILTER_GAINHF_AUTO AL_FALSE +#define AL_MAX_DIRECT_FILTER_GAINHF_AUTO AL_TRUE +#define AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO AL_TRUE + +#define AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_FALSE +#define AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE +#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE + +#define AL_MIN_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_FALSE +#define AL_MAX_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE +#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE + + +/* Listener parameter value ranges and defaults. */ +#define AL_MIN_METERS_PER_UNIT FLT_MIN +#define AL_MAX_METERS_PER_UNIT FLT_MAX +#define AL_DEFAULT_METERS_PER_UNIT (1.0f) + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AL_EFX_H */ diff --git a/jni/OpenAL/include/config.h b/jni/OpenAL/include/config.h new file mode 100755 index 0000000..83acafa --- /dev/null +++ b/jni/OpenAL/include/config.h @@ -0,0 +1,110 @@ +#ifndef CONFIG_H +#define CONFIG_H + +/* Define to the library version */ +#define ALSOFT_VERSION "1.12.854" + +/* Define if we have the Android backend */ +#if defined(ANDROID) +#if defined(POST_FROYO) +#define HAVE_OPENSLES 1 +#endif + +#define HAVE_AUDIOTRACK 1 + +// For throttling AlSource.c +#define MAX_SOURCES_LOW 8 +#define MAX_SOURCES_HIGH 64 +#endif + +/* Define if we have the ALSA backend */ +/* #cmakedefine HAVE_ALSA */ + +/* Define if we have the OSS backend */ +/* #cmakedefine HAVE_OSS */ + +/* Define if we have the Solaris backend */ +/* #cmakedefine HAVE_SOLARIS */ + +/* Define if we have the DSound backend */ +/* #cmakedefine HAVE_DSOUND */ + +/* Define if we have the Wave Writer backend */ +/* #cmakedefine HAVE_WAVE */ + +/* Define if we have the Windows Multimedia backend */ +/* #cmakedefine HAVE_WINMM */ + +/* Define if we have the PortAudio backend */ +/* #cmakedefine HAVE_PORTAUDIO */ + +/* Define if we have the PulseAudio backend */ +/* #cmakedefine HAVE_PULSEAUDIO */ + +/* Define if we have dlfcn.h */ +#define HAVE_DLFCN_H 1 + +/* Define if we have the stat function */ +#define HAVE_STAT 1 + +/* Define if we have the powf function */ +#define HAVE_POWF 1 + +/* Define if we have the sqrtf function */ +#define HAVE_SQRTF 1 + +/* Define if we have the acosf function */ +#define HAVE_ACOSF 1 + +/* Define if we have the atanf function */ +#define HAVE_ATANF 1 + +/* Define if we have the fabsf function */ +#define HAVE_FABSF 1 + +/* Define if we have the strtof function */ +#define HAVE_STRTOF 1 + +/* Define if we have stdint.h */ +#define HAVE_STDINT_H 1 + +/* Define if we have the __int64 type */ +/* #cmakedefine HAVE___INT64 */ + +/* Define to the size of a long int type */ +#define SIZEOF_LONG 4 + +/* Define to the size of a long long int type */ +#define SIZEOF_LONG_LONG 8 + +/* Define to the size of an unsigned int type */ +#define SIZEOF_UINT 4 + +/* Define to the size of a void pointer type */ +#define SIZEOF_VOIDP 4 + +/* Define if we have GCC's destructor attribute */ +#define HAVE_GCC_DESTRUCTOR 1 + +/* Define if we have GCC's format attribute */ +#define HAVE_GCC_FORMAT 1 + +/* Define if we have pthread_np.h */ +/* #cmakedefine HAVE_PTHREAD_NP_H */ + +/* Define if we have float.h */ +/* #cmakedefine HAVE_FLOAT_H */ + +/* Define if we have fenv.h */ +#define HAVE_FENV_H 1 + +/* Define if we have fesetround() */ +/* #cmakedefine HAVE_FESETROUND */ + +/* Define if we have _controlfp() */ +/* #cmakedefine HAVE__CONTROLFP */ + +/* Define if we have pthread_setschedparam() */ +#define HAVE_PTHREAD_SETSCHEDPARAM 1 + +#endif diff --git a/jni/OpenAL/oalStaticBufferExtension.h b/jni/OpenAL/oalStaticBufferExtension.h new file mode 100644 index 0000000..e69de29 diff --git a/libs/armeabi/libopenal.so b/libs/armeabi/libopenal.so new file mode 100755 index 0000000000000000000000000000000000000000..f63f3596a7695baa1b4b66b1b3ed7bccdf93327a GIT binary patch literal 389156 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 eNAcqRP|%dl%uJ@EuS;-2Vfdxyxt( literal 0 HcmV?d00001 diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..f93af5b --- /dev/null +++ b/local.properties @@ -0,0 +1,10 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked in Version Control Systems, +# as it contains information specific to your local configuration. + +# location of the SDK. This is only used by Ant +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=/Developer/AndroidSDK diff --git a/proguard.cfg b/proguard.cfg new file mode 100644 index 0000000..b1cdf17 --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,40 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8074c4c571b8cd19e27f4ee5545df367420686d7 GIT binary patch literal 4147 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 literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1095584ec21f71cd0afc9e0993aa2209671b590c GIT binary patch literal 1723 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 + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..127a314 --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,4 @@ + + + openal_soft + diff --git a/src/com/apportable/openal_soft/openal_soft.java b/src/com/apportable/openal_soft/openal_soft.java new file mode 100644 index 0000000..e3662e4 --- /dev/null +++ b/src/com/apportable/openal_soft/openal_soft.java @@ -0,0 +1,15 @@ +package com.apportable.openal_soft; + +import android.app.Activity; +import android.os.Bundle; + +public class openal_soft extends Activity +{ + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } +}