rlm@0: /** rlm@0: * OpenAL cross platform audio library rlm@0: * Copyright (C) 2009 by Chris Robinson. rlm@0: * This library is free software; you can redistribute it and/or rlm@0: * modify it under the terms of the GNU Library General Public rlm@0: * License as published by the Free Software Foundation; either rlm@0: * version 2 of the License, or (at your option) any later version. rlm@0: * rlm@0: * This library is distributed in the hope that it will be useful, rlm@0: * but WITHOUT ANY WARRANTY; without even the implied warranty of rlm@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU rlm@0: * Library General Public License for more details. rlm@0: * rlm@0: * You should have received a copy of the GNU Library General Public rlm@0: * License along with this library; if not, write to the rlm@0: * Free Software Foundation, Inc., 59 Temple Place - Suite 330, rlm@0: * Boston, MA 02111-1307, USA. rlm@0: * Or go to http://www.gnu.org/copyleft/lgpl.html rlm@0: */ rlm@0: rlm@0: #include "config.h" rlm@0: rlm@0: #include rlm@0: #include rlm@0: rlm@0: #include "alMain.h" rlm@0: #include "alFilter.h" rlm@0: #include "alAuxEffectSlot.h" rlm@0: #include "alError.h" rlm@0: #include "alu.h" rlm@0: rlm@0: rlm@0: typedef struct ALechoState { rlm@0: // Must be first in all effects! rlm@0: ALeffectState state; rlm@0: rlm@0: ALfloat *SampleBuffer; rlm@0: ALuint BufferLength; rlm@0: rlm@0: // The echo is two tap. The delay is the number of samples from before the rlm@0: // current offset rlm@0: struct { rlm@0: ALuint delay; rlm@0: } Tap[2]; rlm@0: ALuint Offset; rlm@0: // The LR gains for the first tap. The second tap uses the reverse rlm@0: ALfloat GainL; rlm@0: ALfloat GainR; rlm@0: rlm@0: ALfloat FeedGain; rlm@0: rlm@0: ALfloat Gain[MAXCHANNELS]; rlm@0: rlm@0: FILTER iirFilter; rlm@0: ALfloat history[2]; rlm@0: } ALechoState; rlm@0: rlm@0: static ALvoid EchoDestroy(ALeffectState *effect) rlm@0: { rlm@0: ALechoState *state = (ALechoState*)effect; rlm@0: if(state) rlm@0: { rlm@0: free(state->SampleBuffer); rlm@0: state->SampleBuffer = NULL; rlm@0: free(state); rlm@0: } rlm@0: } rlm@0: rlm@0: static ALboolean EchoDeviceUpdate(ALeffectState *effect, ALCdevice *Device) rlm@0: { rlm@0: ALechoState *state = (ALechoState*)effect; rlm@0: ALuint maxlen, i; rlm@0: rlm@0: // Use the next power of 2 for the buffer length, so the tap offsets can be rlm@0: // wrapped using a mask instead of a modulo rlm@0: maxlen = (ALuint)(AL_ECHO_MAX_DELAY * Device->Frequency) + 1; rlm@0: maxlen += (ALuint)(AL_ECHO_MAX_LRDELAY * Device->Frequency) + 1; rlm@0: maxlen = NextPowerOf2(maxlen); rlm@0: rlm@0: if(maxlen != state->BufferLength) rlm@0: { rlm@0: void *temp; rlm@0: rlm@0: temp = realloc(state->SampleBuffer, maxlen * sizeof(ALfloat)); rlm@0: if(!temp) rlm@0: return AL_FALSE; rlm@0: state->SampleBuffer = temp; rlm@0: state->BufferLength = maxlen; rlm@0: } rlm@0: for(i = 0;i < state->BufferLength;i++) rlm@0: state->SampleBuffer[i] = 0.0f; rlm@0: rlm@0: return AL_TRUE; rlm@0: } rlm@0: rlm@0: static ALvoid EchoUpdate(ALeffectState *effect, ALCcontext *Context, const ALeffectslot *Slot) rlm@0: { rlm@0: ALechoState *state = (ALechoState*)effect; rlm@0: ALCdevice *Device = Context->Device; rlm@0: ALuint frequency = Device->Frequency; rlm@0: ALfloat lrpan, cw, g, gain; rlm@0: ALuint i; rlm@0: rlm@0: state->Tap[0].delay = (ALuint)(Slot->effect.Params.Echo.Delay * frequency) + 1; rlm@0: state->Tap[1].delay = (ALuint)(Slot->effect.Params.Echo.LRDelay * frequency); rlm@0: state->Tap[1].delay += state->Tap[0].delay; rlm@0: rlm@0: lrpan = Slot->effect.Params.Echo.Spread*0.5f + 0.5f; rlm@0: state->GainL = aluSqrt( lrpan); rlm@0: state->GainR = aluSqrt(1.0f-lrpan); rlm@0: rlm@0: state->FeedGain = Slot->effect.Params.Echo.Feedback; rlm@0: rlm@0: cw = cos(2.0*M_PI * LOWPASSFREQCUTOFF / frequency); rlm@0: g = 1.0f - Slot->effect.Params.Echo.Damping; rlm@0: state->iirFilter.coeff = lpCoeffCalc(g, cw); rlm@0: rlm@0: gain = Slot->Gain; rlm@0: for(i = 0;i < MAXCHANNELS;i++) rlm@0: state->Gain[i] = 0.0f; rlm@0: for(i = 0;i < Device->NumChan;i++) rlm@0: { rlm@0: enum Channel chan = Device->Speaker2Chan[i]; rlm@0: state->Gain[chan] = gain; rlm@0: } rlm@0: } rlm@0: rlm@0: static ALvoid EchoProcess(ALeffectState *effect, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[MAXCHANNELS]) rlm@0: { rlm@0: ALechoState *state = (ALechoState*)effect; rlm@0: const ALuint mask = state->BufferLength-1; rlm@0: const ALuint tap1 = state->Tap[0].delay; rlm@0: const ALuint tap2 = state->Tap[1].delay; rlm@0: ALuint offset = state->Offset; rlm@0: ALfloat samp[2], smp; rlm@0: ALuint i; rlm@0: (void)Slot; rlm@0: rlm@0: for(i = 0;i < SamplesToDo;i++,offset++) rlm@0: { rlm@0: // Sample first tap rlm@0: smp = state->SampleBuffer[(offset-tap1) & mask]; rlm@0: samp[0] = smp * state->GainL; rlm@0: samp[1] = smp * state->GainR; rlm@0: // Sample second tap. Reverse LR panning rlm@0: smp = state->SampleBuffer[(offset-tap2) & mask]; rlm@0: samp[0] += smp * state->GainR; rlm@0: samp[1] += smp * state->GainL; rlm@0: rlm@0: // Apply damping and feedback gain to the second tap, and mix in the rlm@0: // new sample rlm@0: smp = lpFilter2P(&state->iirFilter, 0, smp+SamplesIn[i]); rlm@0: state->SampleBuffer[offset&mask] = smp * state->FeedGain; rlm@0: rlm@0: SamplesOut[i][FRONT_LEFT] += state->Gain[FRONT_LEFT] * samp[0]; rlm@0: SamplesOut[i][FRONT_RIGHT] += state->Gain[FRONT_RIGHT] * samp[1]; rlm@0: SamplesOut[i][SIDE_LEFT] += state->Gain[SIDE_LEFT] * samp[0]; rlm@0: SamplesOut[i][SIDE_RIGHT] += state->Gain[SIDE_RIGHT] * samp[1]; rlm@0: SamplesOut[i][BACK_LEFT] += state->Gain[BACK_LEFT] * samp[0]; rlm@0: SamplesOut[i][BACK_RIGHT] += state->Gain[BACK_RIGHT] * samp[1]; rlm@0: } rlm@0: state->Offset = offset; rlm@0: } rlm@0: rlm@0: ALeffectState *EchoCreate(void) rlm@0: { rlm@0: ALechoState *state; rlm@0: rlm@0: state = malloc(sizeof(*state)); rlm@0: if(!state) rlm@0: return NULL; rlm@0: rlm@0: state->state.Destroy = EchoDestroy; rlm@0: state->state.DeviceUpdate = EchoDeviceUpdate; rlm@0: state->state.Update = EchoUpdate; rlm@0: state->state.Process = EchoProcess; rlm@0: rlm@0: state->BufferLength = 0; rlm@0: state->SampleBuffer = NULL; rlm@0: rlm@0: state->Tap[0].delay = 0; rlm@0: state->Tap[1].delay = 0; rlm@0: state->Offset = 0; rlm@0: state->GainL = 0.0f; rlm@0: state->GainR = 0.0f; rlm@0: rlm@0: state->iirFilter.coeff = 0.0f; rlm@0: state->iirFilter.history[0] = 0.0f; rlm@0: state->iirFilter.history[1] = 0.0f; rlm@0: rlm@0: return &state->state; rlm@0: }