From 778b74cae10e8798e85cffb58a452c7677175778 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 24 Jan 2009 10:38:04 -0800 Subject: [PATCH] Reimplement panning using lookup tables, based on a patch by Christian Borss This allows speaker positions to be specified by discrete angles around the listener, providing more flexibility and configurability in placement. Additional patches to take advantage of this are forthcoming. --- Alc/ALc.c | 3 + Alc/ALu.c | 257 ++++++++++++++++++++++++++++++++++------------ CMakeLists.txt | 4 +- OpenAL32/Include/alMain.h | 6 ++ OpenAL32/Include/alu.h | 1 + config.h.in | 6 ++ 6 files changed, 213 insertions(+), 64 deletions(-) diff --git a/Alc/ALc.c b/Alc/ALc.c index 7f393c7c..72902292 100644 --- a/Alc/ALc.c +++ b/Alc/ALc.c @@ -37,6 +37,7 @@ #include "alExtension.h" #include "alAuxEffectSlot.h" #include "bs2b.h" +#include "alu.h" /////////////////////////////////////////////////////// // DEBUG INFORMATION @@ -487,6 +488,8 @@ static ALvoid InitContext(ALCcontext *pContext) bs2b_set_srate(pContext->bs2b, pContext->Frequency); bs2b_set_level(pContext->bs2b, level); } + + aluInitPanning(pContext); } diff --git a/Alc/ALu.c b/Alc/ALu.c index a6be6ae6..032ec958 100644 --- a/Alc/ALu.c +++ b/Alc/ALu.c @@ -23,6 +23,8 @@ #include "config.h" #include +#include +#include #include "alMain.h" #include "AL/al.h" #include "AL/alc.h" @@ -39,6 +41,11 @@ #include #endif +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif + #if defined(HAVE_STDINT_H) #include typedef int64_t ALint64; @@ -62,6 +69,18 @@ typedef long long ALint64; #define aluAcos(x) ((ALfloat)acos((double)(x))) #endif +#ifdef HAVE_ATANF +#define aluAtan(x) ((ALfloat)atanf((float)(x))) +#else +#define aluAtan(x) ((ALfloat)atan((double)(x))) +#endif + +#ifdef HAVE_FABSF +#define aluFabs(x) ((ALfloat)fabsf((float)(x))) +#else +#define aluFabs(x) ((ALfloat)fabs((double)(x))) +#endif + // fixes for mingw32. #if defined(max) && !defined(__max) #define __max max @@ -240,17 +259,170 @@ static __inline ALvoid aluMatrixVector(ALfloat *vector,ALfloat matrix[3][3]) memcpy(vector, result, sizeof(result)); } +static __inline ALfloat aluLUTpos2Angle(ALint pos) +{ + if(pos < QUADRANT_NUM) + return aluAtan((ALfloat)pos / (ALfloat)(QUADRANT_NUM - pos)); + if(pos < 2 * QUADRANT_NUM) + return M_PI_2 + aluAtan((ALfloat)(pos - QUADRANT_NUM) / (ALfloat)(2 * QUADRANT_NUM - pos)); + if(pos < 3 * QUADRANT_NUM) + return aluAtan((ALfloat)(pos - 2 * QUADRANT_NUM) / (ALfloat)(3 * QUADRANT_NUM - pos)) - M_PI; + return aluAtan((ALfloat)(pos - 3 * QUADRANT_NUM) / (ALfloat)(4 * QUADRANT_NUM - pos)) - M_PI_2; +} + +ALvoid aluInitPanning(ALCcontext *Context) +{ + ALint pos, offset, s; + ALfloat Alpha, Theta; + ALfloat SpeakerAngle[OUTPUTCHANNELS]; + ALint Speaker2Chan[OUTPUTCHANNELS]; + + switch(Context->Device->Format) + { + /* Mono is rendered as stereo, then downmixed during post-process */ + case AL_FORMAT_MONO8: + case AL_FORMAT_MONO16: + case AL_FORMAT_MONO_FLOAT32: + case AL_FORMAT_STEREO8: + case AL_FORMAT_STEREO16: + case AL_FORMAT_STEREO_FLOAT32: + Context->NumChan = 2; + Speaker2Chan[0] = FRONT_LEFT; + Speaker2Chan[1] = FRONT_RIGHT; + SpeakerAngle[0] = -90.0f * M_PI/180.0f; + SpeakerAngle[1] = 90.0f * M_PI/180.0f; + + case AL_FORMAT_QUAD8: + case AL_FORMAT_QUAD16: + case AL_FORMAT_QUAD32: + Context->NumChan = 4; + Speaker2Chan[0] = BACK_LEFT; + Speaker2Chan[1] = FRONT_LEFT; + Speaker2Chan[2] = FRONT_RIGHT; + Speaker2Chan[3] = BACK_RIGHT; + SpeakerAngle[0] = -135.0f * M_PI/180.0f; + SpeakerAngle[1] = -45.0f * M_PI/180.0f; + SpeakerAngle[2] = 45.0f * M_PI/180.0f; + SpeakerAngle[3] = 135.0f * M_PI/180.0f; + break; + + case AL_FORMAT_51CHN8: + case AL_FORMAT_51CHN16: + case AL_FORMAT_51CHN32: + Context->NumChan = 5; + Speaker2Chan[0] = BACK_LEFT; + Speaker2Chan[1] = FRONT_LEFT; + Speaker2Chan[2] = CENTER; + Speaker2Chan[3] = FRONT_RIGHT; + Speaker2Chan[4] = BACK_RIGHT; + SpeakerAngle[0] = -110.0f * M_PI/180.0f; + SpeakerAngle[1] = -30.0f * M_PI/180.0f; + SpeakerAngle[2] = 0.0f * M_PI/180.0f; + SpeakerAngle[3] = 30.0f * M_PI/180.0f; + SpeakerAngle[4] = 110.0f * M_PI/180.0f; + break; + + case AL_FORMAT_61CHN8: + case AL_FORMAT_61CHN16: + case AL_FORMAT_61CHN32: + Context->NumChan = 6; + Speaker2Chan[0] = BACK_LEFT; + Speaker2Chan[1] = SIDE_LEFT; + Speaker2Chan[2] = FRONT_LEFT; + Speaker2Chan[3] = FRONT_RIGHT; + Speaker2Chan[4] = SIDE_RIGHT; + Speaker2Chan[5] = BACK_RIGHT; + SpeakerAngle[0] = -150.0f * M_PI/180.0f; + SpeakerAngle[1] = -90.0f * M_PI/180.0f; + SpeakerAngle[2] = -30.0f * M_PI/180.0f; + SpeakerAngle[3] = 30.0f * M_PI/180.0f; + SpeakerAngle[4] = 90.0f * M_PI/180.0f; + SpeakerAngle[5] = 150.0f * M_PI/180.0f; + break; + + case AL_FORMAT_71CHN8: + case AL_FORMAT_71CHN16: + case AL_FORMAT_71CHN32: + Context->NumChan = 7; + Speaker2Chan[0] = BACK_LEFT; + Speaker2Chan[1] = SIDE_LEFT; + Speaker2Chan[2] = FRONT_LEFT; + Speaker2Chan[3] = CENTER; + Speaker2Chan[4] = FRONT_RIGHT; + Speaker2Chan[5] = SIDE_RIGHT; + Speaker2Chan[6] = BACK_RIGHT; + SpeakerAngle[0] = -150.0f * M_PI/180.0f; + SpeakerAngle[1] = -90.0f * M_PI/180.0f; + SpeakerAngle[2] = -30.0f * M_PI/180.0f; + SpeakerAngle[3] = 0.0f * M_PI/180.0f; + SpeakerAngle[4] = 30.0f * M_PI/180.0f; + SpeakerAngle[5] = 90.0f * M_PI/180.0f; + SpeakerAngle[6] = 150.0f * M_PI/180.0f; + break; + + default: + assert(0); + } + + for(pos = 0; pos < LUT_NUM; pos++) + { + /* source angle */ + Theta = aluLUTpos2Angle(pos); + + /* clear all values */ + offset = OUTPUTCHANNELS * pos; + for(s = 0; s < OUTPUTCHANNELS; s++) + Context->PanningLUT[offset+s] = 0.0f; + + /* set panning values */ + for(s = 0; s < Context->NumChan - 1; s++) + { + if(Theta >= SpeakerAngle[s] && Theta < SpeakerAngle[s+1]) + { + /* source between speaker s and speaker s+1 */ + Alpha = M_PI_2 * (Theta-SpeakerAngle[s]) / + (SpeakerAngle[s+1]-SpeakerAngle[s]); + Context->PanningLUT[offset + Speaker2Chan[s]] = cos(Alpha); + Context->PanningLUT[offset + Speaker2Chan[s+1]] = sin(Alpha); + break; + } + } + if(s == Context->NumChan - 1) + { + /* source between last and first speaker */ + if(Theta < SpeakerAngle[0]) + Theta += 2.0f * M_PI; + Alpha = M_PI_2 * (Theta-SpeakerAngle[s]) / + (2.0f * M_PI + SpeakerAngle[0]-SpeakerAngle[s]); + Context->PanningLUT[offset + Speaker2Chan[s]] = cos(Alpha); + Context->PanningLUT[offset + Speaker2Chan[0]] = sin(Alpha); + } + } +} + +static __inline ALint aluCart2LUTpos(ALfloat re, ALfloat im) +{ + ALint pos = 0; + ALfloat denom = aluFabs(re) + aluFabs(im); + if(denom > 0.0f) + pos = (ALint)(QUADRANT_NUM*aluFabs(im) / denom + 0.5); + + if(re < 0.0) + pos = 2 * QUADRANT_NUM - pos; + if(im < 0.0) + pos = LUT_NUM - pos; + return pos%LUT_NUM; +} static ALvoid CalcSourceParams(ALCcontext *ALContext, ALsource *ALSource, - ALenum isMono, ALenum OutputFormat, - ALfloat *drysend, ALfloat *wetsend, - ALfloat *pitch, ALfloat *drygainhf, - ALfloat *wetgainhf) + ALenum isMono, ALfloat *drysend, + ALfloat *wetsend, ALfloat *pitch, + ALfloat *drygainhf, ALfloat *wetgainhf) { ALfloat InnerAngle,OuterAngle,Angle,Distance,DryMix,WetMix=0.0f; ALfloat Direction[3],Position[3],SourceToListener[3]; ALfloat MinVolume,MaxVolume,MinDist,MaxDist,Rolloff,OuterGainHF; - ALfloat ConeVolume,SourceVolume,PanningFB,PanningLR,ListenerGain; + ALfloat ConeVolume,SourceVolume,ListenerGain; ALfloat U[3],V[3],N[3]; ALfloat DopplerFactor, DopplerVelocity, flSpeedOfSound, flMaxVelocity; ALfloat Matrix[3][3]; @@ -260,6 +432,9 @@ static ALvoid CalcSourceParams(ALCcontext *ALContext, ALsource *ALSource, ALfloat RoomRolloff; ALfloat DryGainHF = 1.0f; ALfloat WetGainHF = 1.0f; + ALfloat DirGain, AmbientGain; + const ALfloat *SpeakerGain; + ALint pos, s; ALfloat cw, a, g; //Get context properties @@ -519,65 +694,20 @@ static ALvoid CalcSourceParams(ALCcontext *ALContext, ALsource *ALSource, DryMix *= ListenerGain; WetMix *= ListenerGain; - //6. Convert normalized position into pannings, then into channel volumes + // Use energy-preserving panning algorithm for multi-speaker playback aluNormalize(Position); - switch(aluChannelsFromFormat(OutputFormat)) + + pos = aluCart2LUTpos(-Position[2], Position[0]); + SpeakerGain = &ALContext->PanningLUT[OUTPUTCHANNELS * pos]; + + DirGain = aluSqrt(Position[0]*Position[0] + Position[2]*Position[2]); + // elevation adjustment for directional gain. this sucks, but + // has low complexity + AmbientGain = 1.0/aluSqrt(ALContext->NumChan) * (1.0-DirGain); + for(s = 0; s < OUTPUTCHANNELS; s++) { - case 1: - case 2: - PanningLR = 0.5f + 0.5f*Position[0]; - drysend[FRONT_LEFT] = DryMix * aluSqrt(1.0f-PanningLR); //L Direct - drysend[FRONT_RIGHT] = DryMix * aluSqrt( PanningLR); //R Direct - drysend[BACK_LEFT] = 0.0f; - drysend[BACK_RIGHT] = 0.0f; - drysend[SIDE_LEFT] = 0.0f; - drysend[SIDE_RIGHT] = 0.0f; - break; - case 4: - /* TODO: Add center/lfe channel in spatial calculations? */ - case 6: - // Apply a scalar so each individual speaker has more weight - PanningLR = 0.5f + (0.5f*Position[0]*1.41421356f); - PanningLR = __min(1.0f, PanningLR); - PanningLR = __max(0.0f, PanningLR); - PanningFB = 0.5f + (0.5f*Position[2]*1.41421356f); - PanningFB = __min(1.0f, PanningFB); - PanningFB = __max(0.0f, PanningFB); - drysend[FRONT_LEFT] = DryMix * aluSqrt((1.0f-PanningLR)*(1.0f-PanningFB)); - drysend[FRONT_RIGHT] = DryMix * aluSqrt(( PanningLR)*(1.0f-PanningFB)); - drysend[BACK_LEFT] = DryMix * aluSqrt((1.0f-PanningLR)*( PanningFB)); - drysend[BACK_RIGHT] = DryMix * aluSqrt(( PanningLR)*( PanningFB)); - drysend[SIDE_LEFT] = 0.0f; - drysend[SIDE_RIGHT] = 0.0f; - break; - case 7: - case 8: - PanningFB = 1.0f - fabs(Position[2]*1.15470054f); - PanningFB = __min(1.0f, PanningFB); - PanningFB = __max(0.0f, PanningFB); - PanningLR = 0.5f + (0.5*Position[0]*((1.0f-PanningFB)*2.0f)); - PanningLR = __min(1.0f, PanningLR); - PanningLR = __max(0.0f, PanningLR); - if(Position[2] > 0.0f) - { - drysend[BACK_LEFT] = DryMix * aluSqrt((1.0f-PanningLR)*(1.0f-PanningFB)); - drysend[BACK_RIGHT] = DryMix * aluSqrt(( PanningLR)*(1.0f-PanningFB)); - drysend[SIDE_LEFT] = DryMix * aluSqrt((1.0f-PanningLR)*( PanningFB)); - drysend[SIDE_RIGHT] = DryMix * aluSqrt(( PanningLR)*( PanningFB)); - drysend[FRONT_LEFT] = 0.0f; - drysend[FRONT_RIGHT] = 0.0f; - } - else - { - drysend[FRONT_LEFT] = DryMix * aluSqrt((1.0f-PanningLR)*(1.0f-PanningFB)); - drysend[FRONT_RIGHT] = DryMix * aluSqrt(( PanningLR)*(1.0f-PanningFB)); - drysend[SIDE_LEFT] = DryMix * aluSqrt((1.0f-PanningLR)*( PanningFB)); - drysend[SIDE_RIGHT] = DryMix * aluSqrt(( PanningLR)*( PanningFB)); - drysend[BACK_LEFT] = 0.0f; - drysend[BACK_RIGHT] = 0.0f; - } - default: - break; + ALfloat gain = SpeakerGain[s]*DirGain + AmbientGain; + drysend[s] = DryMix * gain; } *wetsend = WetMix; @@ -748,7 +878,7 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma CalcSourceParams(ALContext, ALSource, (Channels==1) ? AL_TRUE : AL_FALSE, - format, newDrySend, &newWetSend, &Pitch, + newDrySend, &newWetSend, &Pitch, &DryGainHF, &WetGainHF); Pitch = (Pitch*Frequency) / ALContext->Frequency; @@ -847,6 +977,7 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma DryBuffer[j][SIDE_RIGHT] += outsamp*DrySend[SIDE_RIGHT]; DryBuffer[j][BACK_LEFT] += outsamp*DrySend[BACK_LEFT]; DryBuffer[j][BACK_RIGHT] += outsamp*DrySend[BACK_RIGHT]; + DryBuffer[j][CENTER] += outsamp*DrySend[CENTER]; //Room path final mix buffer and panning outsamp = lpFilter(WetFilter, sample); WetBuffer[j] += outsamp*(*WetSend); diff --git a/CMakeLists.txt b/CMakeLists.txt index 28622177..845442c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,10 +122,12 @@ CHECK_INCLUDE_FILE(float.h HAVE_FLOAT_H) CHECK_LIBRARY_EXISTS(m sqrtf "" HAVE_SQRTF) CHECK_LIBRARY_EXISTS(m acosf "" HAVE_ACOSF) +CHECK_LIBRARY_EXISTS(m atanf "" HAVE_ATANF) +CHECK_LIBRARY_EXISTS(m fabsf "" HAVE_FABSF) IF(HAVE_FENV_H) CHECK_LIBRARY_EXISTS(m fesetround "" HAVE_FESETROUND) ENDIF() -IF(HAVE_SQRTF OR HAVE_ACOSF OR HAVE_FESETROUND) +IF(HAVE_SQRTF OR HAVE_ACOSF OR HAVE_ATANF OR HAVE_FABSF OR HAVE_FESETROUND) SET(EXTRA_LIBS m ${EXTRA_LIBS}) ENDIF() CHECK_FUNCTION_EXISTS(strtof HAVE_STRTOF) diff --git a/OpenAL32/Include/alMain.h b/OpenAL32/Include/alMain.h index 042ab779..de53b072 100644 --- a/OpenAL32/Include/alMain.h +++ b/OpenAL32/Include/alMain.h @@ -129,6 +129,9 @@ extern char _alDebug[256]; #define LOWPASSFREQCUTOFF (5000) +#define QUADRANT_NUM 128 +#define LUT_NUM (4 * QUADRANT_NUM) + typedef struct { ALCboolean (*OpenPlayback)(ALCdevice*, const ALCchar*); @@ -206,6 +209,9 @@ struct ALCcontext_struct ALint lNumMonoSources; ALint lNumStereoSources; + ALfloat PanningLUT[OUTPUTCHANNELS * LUT_NUM]; + ALint NumChan; + ALCdevice *Device; const ALCchar *ExtensionList; diff --git a/OpenAL32/Include/alu.h b/OpenAL32/Include/alu.h index b14180b2..f307ea01 100644 --- a/OpenAL32/Include/alu.h +++ b/OpenAL32/Include/alu.h @@ -25,6 +25,7 @@ extern ALboolean DuplicateStereo; __inline ALuint aluBytesFromFormat(ALenum format); __inline ALuint aluChannelsFromFormat(ALenum format); +ALvoid aluInitPanning(ALCcontext *Context); ALvoid aluMixData(ALCcontext *context,ALvoid *buffer,ALsizei size,ALenum format); #ifdef __cplusplus diff --git a/config.h.in b/config.h.in index 45df5157..0c8ef40d 100644 --- a/config.h.in +++ b/config.h.in @@ -31,6 +31,12 @@ /* Define if we have the acosf function */ #cmakedefine HAVE_ACOSF +/* Define if we have the atanf function */ +#cmakedefine HAVE_ATANF + +/* Define if we have the fabsf function */ +#cmakedefine HAVE_FABSF + /* Define if we have the strtof function */ #cmakedefine HAVE_STRTOF -- 2.11.4.GIT