From 55c790c9ff28acad5e5da365bee8a6f693d21796 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 12 Apr 2009 16:01:10 -0700 Subject: [PATCH] Add the Echo effect --- Alc/ALc.c | 1 + Alc/ALu.c | 2 + Alc/alcEcho.c | 194 +++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 1 + OpenAL32/Include/alAuxEffectSlot.h | 2 + OpenAL32/Include/alEcho.h | 24 +++++ OpenAL32/Include/alEffect.h | 34 +++++++ OpenAL32/alAuxEffectSlot.c | 20 ++++ OpenAL32/alEffect.c | 153 ++++++++++++++++++++++++++++- alsoftrc.sample | 2 +- 10 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 Alc/alcEcho.c create mode 100644 OpenAL32/Include/alEcho.h diff --git a/Alc/ALc.c b/Alc/ALc.c index 943201f6..3c0b1e62 100644 --- a/Alc/ALc.c +++ b/Alc/ALc.c @@ -322,6 +322,7 @@ static void InitAL(void) int type; } EffectList[] = { { "reverb", REVERB }, + { "echo", ECHO }, { NULL, 0 } }; int n; diff --git a/Alc/ALu.c b/Alc/ALu.c index ea2f6526..babccb8e 100644 --- a/Alc/ALu.c +++ b/Alc/ALu.c @@ -1383,6 +1383,8 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma { if(ALEffectSlot->effect.type == AL_EFFECT_REVERB) VerbProcess(ALEffectSlot->ReverbState, SamplesToDo, ALEffectSlot->WetBuffer, DryBuffer); + else if(ALEffectSlot->effect.type == AL_EFFECT_ECHO) + EchoProcess(ALEffectSlot->EchoState, SamplesToDo, ALEffectSlot->WetBuffer, DryBuffer); for(i = 0;i < SamplesToDo;i++) ALEffectSlot->WetBuffer[i] = 0.0f; diff --git a/Alc/alcEcho.c b/Alc/alcEcho.c new file mode 100644 index 00000000..6640d9af --- /dev/null +++ b/Alc/alcEcho.c @@ -0,0 +1,194 @@ +/** + * 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 "AL/al.h" +#include "alFilter.h" +#include "alAuxEffectSlot.h" +#include "alEcho.h" + +#ifdef HAVE_SQRTF +#define aluSqrt(x) ((ALfloat)sqrtf((float)(x))) +#else +#define aluSqrt(x) ((ALfloat)sqrt((double)(x))) +#endif + +struct ALechoState { + ALfloat *SampleBuffer; + ALuint BufferLength; + + // The echo is two tap. The third tap is the offset to sample from for + // feedback + struct { + ALuint offset; + } Tap[3]; + // The LR gains for the first tap. The second tap uses the reverse + ALfloat GainL; + ALfloat GainR; + + ALfloat FeedGain; + FILTER iirFilter; +}; + +// Find the next power of 2. Actually, this will return the input value if +// it is already a power of 2. +static ALuint NextPowerOf2(ALuint value) +{ + ALuint powerOf2 = 1; + + if(value) + { + value--; + while(value) + { + value >>= 1; + powerOf2 <<= 1; + } + } + return powerOf2; +} + +ALechoState *EchoCreate(ALCcontext *Context) +{ + ALechoState *state; + ALuint i, maxlen; + + state = malloc(sizeof(*state)); + if(!state) + return NULL; + + maxlen = (ALuint)(AL_ECHO_MAX_DELAY * Context->Frequency); + maxlen += (ALuint)(AL_ECHO_MAX_LRDELAY * Context->Frequency); + + // Use the next power of 2 for the buffer length, so the tap offsets can be + // wrapped using a mask instead of a modulo + state->BufferLength = NextPowerOf2(maxlen+1); + state->SampleBuffer = malloc(state->BufferLength * sizeof(ALfloat)); + if(!state->SampleBuffer) + { + free(state); + return NULL; + } + + for(i = 0;i < state->BufferLength;i++) + state->SampleBuffer[i] = 0.0f; + + state->Tap[0].offset = 0; + state->Tap[1].offset = 0; + state->Tap[2].offset = 0; + state->GainL = 0.0f; + state->GainR = 0.0f; + + for(i = 0;i < sizeof(state->iirFilter.history)/sizeof(state->iirFilter.history[0]);i++) + state->iirFilter.history[i] = 0.0f; + state->iirFilter.coeff = 0.0f; + + return state; +} + +ALvoid EchoDestroy(ALechoState *state) +{ + if(state) + { + free(state->SampleBuffer); + state->SampleBuffer = NULL; + free(state); + } +} + +ALvoid EchoUpdate(ALCcontext *Context, struct ALeffectslot *Slot, ALeffect *Effect) +{ + ALechoState *state = Slot->EchoState; + ALuint newdelay1, newdelay2; + ALfloat lrpan, cw, a, g; + + newdelay1 = (ALuint)(Effect->Echo.Delay * Context->Frequency); + newdelay2 = (ALuint)(Effect->Echo.LRDelay * Context->Frequency); + + state->Tap[0].offset = (state->BufferLength - newdelay1 - 1 + + state->Tap[2].offset)%state->BufferLength; + state->Tap[1].offset = (state->BufferLength - newdelay1 - newdelay2 - 1 + + state->Tap[2].offset)%state->BufferLength; + + lrpan = Effect->Echo.Spread*0.5f + 0.5f; + state->GainL = aluSqrt( lrpan); + state->GainR = aluSqrt(1.0f-lrpan); + + state->FeedGain = Effect->Echo.Feedback; + + cw = cos(2.0*M_PI * LOWPASSFREQCUTOFF / Context->Frequency); + g = 1.0f - Effect->Echo.Damping; + a = 0.0f; + if(g < 0.9999f) // 1-epsilon + a = (1 - g*cw - aluSqrt(2*g*(1-cw) - g*g*(1 - cw*cw))) / (1 - g); + state->iirFilter.coeff = a; +} + +ALvoid EchoProcess(ALechoState *state, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[OUTPUTCHANNELS]) +{ + ALfloat *history = state->iirFilter.history; + const ALfloat a = state->iirFilter.coeff; + const ALuint delay = state->BufferLength-1; + ALuint tap1off = state->Tap[0].offset; + ALuint tap2off = state->Tap[1].offset; + ALuint fboff = state->Tap[2].offset; + ALfloat samp[2]; + ALuint i; + + for(i = 0;i < SamplesToDo;i++) + { + // Apply damping + samp[0] = state->SampleBuffer[tap2off] + SamplesIn[i]; + + samp[0] += (history[0]-samp[0]) * a; + history[0] = samp[0]; + samp[0] += (history[1]-samp[0]) * a; + history[1] = samp[0]; + + // Apply feedback gain and mix in the new sample + state->SampleBuffer[fboff] = samp[0] * state->FeedGain; + + tap1off = (tap1off+1) & delay; + tap2off = (tap2off+1) & delay; + fboff = (fboff+1) & delay; + + // Sample first tap + samp[0] = state->SampleBuffer[tap1off]*state->GainL; + samp[1] = state->SampleBuffer[tap1off]*state->GainR; + // Sample second tap. Reverse LR panning + samp[0] += state->SampleBuffer[tap2off]*state->GainR; + samp[1] += state->SampleBuffer[tap2off]*state->GainL; + + SamplesOut[i][FRONT_LEFT] += samp[0]; + SamplesOut[i][FRONT_RIGHT] += samp[1]; + SamplesOut[i][SIDE_LEFT] += samp[0]; + SamplesOut[i][SIDE_RIGHT] += samp[1]; + SamplesOut[i][BACK_LEFT] += samp[0]; + SamplesOut[i][BACK_RIGHT] += samp[1]; + } + + state->Tap[0].offset = tap1off; + state->Tap[1].offset = tap2off; + state->Tap[2].offset = fboff; +} diff --git a/CMakeLists.txt b/CMakeLists.txt index bf2cd4dd..59c8c78d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,6 +238,7 @@ SET(OPENAL_OBJS OpenAL32/alAuxEffectSlot.c SET(ALC_OBJS Alc/ALc.c Alc/ALu.c Alc/alcConfig.c + Alc/alcEcho.c Alc/alcReverb.c Alc/alcRing.c Alc/alcThread.c diff --git a/OpenAL32/Include/alAuxEffectSlot.h b/OpenAL32/Include/alAuxEffectSlot.h index a818bb47..8dfa9418 100644 --- a/OpenAL32/Include/alAuxEffectSlot.h +++ b/OpenAL32/Include/alAuxEffectSlot.h @@ -5,6 +5,7 @@ #include "alEffect.h" #include "alFilter.h" #include "alReverb.h" +#include "alEcho.h" #ifdef __cplusplus extern "C" { @@ -24,6 +25,7 @@ typedef struct ALeffectslot ALboolean AuxSendAuto; ALverbState *ReverbState; + ALechoState *EchoState; ALfloat WetBuffer[BUFFERSIZE]; diff --git a/OpenAL32/Include/alEcho.h b/OpenAL32/Include/alEcho.h new file mode 100644 index 00000000..34896ea7 --- /dev/null +++ b/OpenAL32/Include/alEcho.h @@ -0,0 +1,24 @@ +#ifndef AL_ECHO_H +#define AL_ECHO_H + +#include "AL/al.h" +#include "AL/alc.h" +#include "alMain.h" +#include "alEffect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ALechoState ALechoState; + +ALechoState *EchoCreate(ALCcontext *Context); +ALvoid EchoDestroy(ALechoState *State); +ALvoid EchoUpdate(ALCcontext *Context, struct ALeffectslot *Slot, ALeffect *Effect); +ALvoid EchoProcess(ALechoState *State, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[OUTPUTCHANNELS]); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/OpenAL32/Include/alEffect.h b/OpenAL32/Include/alEffect.h index 5f6723bc..6dd1b766 100644 --- a/OpenAL32/Include/alEffect.h +++ b/OpenAL32/Include/alEffect.h @@ -37,9 +37,33 @@ extern "C" { #define AL_REVERB_ROOM_ROLLOFF_FACTOR 0x000C #define AL_REVERB_DECAY_HFLIMIT 0x000D +#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 + +#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) + enum { REVERB = 0, + ECHO, + MAX_EFFECTS }; extern ALboolean DisabledEffects[MAX_EFFECTS]; @@ -67,6 +91,16 @@ typedef struct ALeffect_struct ALboolean DecayHFLimit; } Reverb; + struct { + ALfloat Delay; + ALfloat LRDelay; + + ALfloat Damping; + ALfloat Feedback; + + ALfloat Spread; + } Echo; + // Index to itself ALuint effect; diff --git a/OpenAL32/alAuxEffectSlot.c b/OpenAL32/alAuxEffectSlot.c index 85a7c6ee..810b380e 100644 --- a/OpenAL32/alAuxEffectSlot.c +++ b/OpenAL32/alAuxEffectSlot.c @@ -151,6 +151,7 @@ ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) ALTHUNK_REMOVEENTRY(ALAuxiliaryEffectSlot->effectslot); VerbDestroy(ALAuxiliaryEffectSlot->ReverbState); + EchoDestroy(ALAuxiliaryEffectSlot->EchoState); memset(ALAuxiliaryEffectSlot, 0, sizeof(ALeffectslot)); free(ALAuxiliaryEffectSlot); @@ -474,14 +475,32 @@ static ALvoid InitializeEffect(ALCcontext *Context, ALeffectslot *ALEffectSlot, memset(&ALEffectSlot->effect, 0, sizeof(ALEffectSlot->effect)); VerbDestroy(ALEffectSlot->ReverbState); ALEffectSlot->ReverbState = NULL; + EchoDestroy(ALEffectSlot->EchoState); + ALEffectSlot->EchoState = NULL; return; } if(effect->type == AL_EFFECT_REVERB) { + if(ALEffectSlot->EchoState) + { + EchoDestroy(ALEffectSlot->EchoState); + ALEffectSlot->EchoState = NULL; + } if(!ALEffectSlot->ReverbState) ALEffectSlot->ReverbState = VerbCreate(Context); VerbUpdate(Context, ALEffectSlot, effect); } + else if(effect->type == AL_EFFECT_ECHO) + { + if(ALEffectSlot->ReverbState) + { + VerbDestroy(ALEffectSlot->ReverbState); + ALEffectSlot->ReverbState = NULL; + } + if(!ALEffectSlot->EchoState) + ALEffectSlot->EchoState = EchoCreate(Context); + EchoUpdate(Context, ALEffectSlot, effect); + } memcpy(&ALEffectSlot->effect, effect, sizeof(*effect)); } @@ -500,6 +519,7 @@ ALvoid ReleaseALAuxiliaryEffectSlots(ALCcontext *Context) // Release effectslot structure VerbDestroy(temp->ReverbState); + EchoDestroy(temp->EchoState); ALTHUNK_REMOVEENTRY(temp->effectslot); memset(temp, 0, sizeof(ALeffectslot)); diff --git a/OpenAL32/alEffect.c b/OpenAL32/alEffect.c index 939b663f..4455a230 100644 --- a/OpenAL32/alEffect.c +++ b/OpenAL32/alEffect.c @@ -170,7 +170,8 @@ ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue) if(param == AL_EFFECT_TYPE) { ALboolean isOk = (iValue == AL_EFFECT_NULL || - (iValue == AL_EFFECT_REVERB && !DisabledEffects[REVERB])); + (iValue == AL_EFFECT_REVERB && !DisabledEffects[REVERB]) || + (iValue == AL_EFFECT_ECHO && !DisabledEffects[ECHO])); if(isOk) InitEffectParams(ALEffect, iValue); @@ -193,6 +194,15 @@ ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue) break; } } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + default: + alSetError(AL_INVALID_ENUM); + break; + } + } else alSetError(AL_INVALID_ENUM); } @@ -230,6 +240,15 @@ ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, ALint *piValues) break; } } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + default: + alSetError(AL_INVALID_ENUM); + break; + } + } else alSetError(AL_INVALID_ENUM); } @@ -343,6 +362,50 @@ ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue) break; } } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + case AL_ECHO_DELAY: + if(flValue >= AL_ECHO_MIN_DELAY && flValue <= AL_ECHO_MAX_DELAY) + ALEffect->Echo.Delay = flValue; + else + alSetError(AL_INVALID_VALUE); + break; + + case AL_ECHO_LRDELAY: + if(flValue >= AL_ECHO_MIN_LRDELAY && flValue <= AL_ECHO_MAX_LRDELAY) + ALEffect->Echo.LRDelay = flValue; + else + alSetError(AL_INVALID_VALUE); + break; + + case AL_ECHO_DAMPING: + if(flValue >= AL_ECHO_MIN_DAMPING && flValue <= AL_ECHO_MAX_DAMPING) + ALEffect->Echo.Damping = flValue; + else + alSetError(AL_INVALID_VALUE); + break; + + case AL_ECHO_FEEDBACK: + if(flValue >= AL_ECHO_MIN_FEEDBACK && flValue <= AL_ECHO_MAX_FEEDBACK) + ALEffect->Echo.Feedback = flValue; + else + alSetError(AL_INVALID_VALUE); + break; + + case AL_ECHO_SPREAD: + if(flValue >= AL_ECHO_MIN_SPREAD && flValue <= AL_ECHO_MAX_SPREAD) + ALEffect->Echo.Spread = flValue; + else + alSetError(AL_INVALID_VALUE); + break; + + default: + alSetError(AL_INVALID_ENUM); + break; + } + } else alSetError(AL_INVALID_ENUM); } @@ -387,6 +450,23 @@ ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, ALfloat *pflValues) 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(AL_INVALID_ENUM); + break; + } + } else alSetError(AL_INVALID_ENUM); } @@ -424,6 +504,15 @@ ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue) break; } } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + default: + alSetError(AL_INVALID_ENUM); + break; + } + } else alSetError(AL_INVALID_ENUM); } @@ -461,6 +550,15 @@ ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues) break; } } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + default: + alSetError(AL_INVALID_ENUM); + break; + } + } else alSetError(AL_INVALID_ENUM); } @@ -538,6 +636,35 @@ ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue) break; } } + else if(ALEffect->type == AL_EFFECT_ECHO) + { + switch(param) + { + case AL_ECHO_DELAY: + *pflValue = ALEffect->Echo.Delay; + break; + + case AL_ECHO_LRDELAY: + *pflValue = ALEffect->Echo.LRDelay; + break; + + case AL_ECHO_DAMPING: + *pflValue = ALEffect->Echo.Damping; + break; + + case AL_ECHO_FEEDBACK: + *pflValue = ALEffect->Echo.Feedback; + break; + + case AL_ECHO_SPREAD: + *pflValue = ALEffect->Echo.Spread; + break; + + default: + alSetError(AL_INVALID_ENUM); + break; + } + } else alSetError(AL_INVALID_ENUM); } @@ -582,6 +709,23 @@ ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues 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(AL_INVALID_ENUM); + break; + } + } else alSetError(AL_INVALID_ENUM); } @@ -632,5 +776,12 @@ static void InitEffectParams(ALeffect *effect, ALenum type) effect->Reverb.RoomRolloffFactor = 0.0f; effect->Reverb.DecayHFLimit = AL_TRUE; break; + case AL_EFFECT_ECHO: + effect->Echo.Delay = AL_ECHO_DEFAULT_DELAY; + effect->Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY; + effect->Echo.Damping = AL_ECHO_DEFAULT_DAMPING; + effect->Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK; + effect->Echo.Spread = AL_ECHO_DEFAULT_SPREAD; + break; } } diff --git a/alsoftrc.sample b/alsoftrc.sample index 8408ab78..855b862e 100644 --- a/alsoftrc.sample +++ b/alsoftrc.sample @@ -64,7 +64,7 @@ drivers = # Sets the backend driver list order, comma-seperated. Unknown excludefx = # Sets which effects to exclude, preventing apps from using them. # This can help for apps that try to use effects which are too CPU # intensive for the system to handle. Available effects are: - # reverb + # reverb,echo # Default is empty (all available effects enabled) slots = 4 # Sets the maximum number of Auxiliary Effect Slots an app can -- 2.11.4.GIT