aboutsummaryrefslogtreecommitdiffstats
path: root/Alc
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2009-01-24 10:38:04 -0800
committerChris Robinson <[email protected]>2009-01-24 10:38:04 -0800
commit778b74cae10e8798e85cffb58a452c7677175778 (patch)
tree73a451bec2de951ab1024daac6f582a40ef34333 /Alc
parent43ee1edd972d528233475f5239690cd2c3efb04c (diff)
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.
Diffstat (limited to 'Alc')
-rw-r--r--Alc/ALc.c3
-rw-r--r--Alc/ALu.c257
2 files changed, 197 insertions, 63 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 <math.h>
+#include <stdlib.h>
+#include <string.h>
#include "alMain.h"
#include "AL/al.h"
#include "AL/alc.h"
@@ -39,6 +41,11 @@
#include <float.h>
#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 <stdint.h>
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);