aboutsummaryrefslogtreecommitdiffstats
path: root/Alc/effects/echo.c
diff options
context:
space:
mode:
Diffstat (limited to 'Alc/effects/echo.c')
-rw-r--r--Alc/effects/echo.c243
1 files changed, 129 insertions, 114 deletions
diff --git a/Alc/effects/echo.c b/Alc/effects/echo.c
index f5a53c36..4570fcb1 100644
--- a/Alc/effects/echo.c
+++ b/Alc/effects/echo.c
@@ -28,186 +28,207 @@
#include "alAuxEffectSlot.h"
#include "alError.h"
#include "alu.h"
+#include "filters/defs.h"
typedef struct ALechoState {
DERIVE_FROM_TYPE(ALeffectState);
ALfloat *SampleBuffer;
- ALuint BufferLength;
+ ALsizei BufferLength;
// The echo is two tap. The delay is the number of samples from before the
// current offset
struct {
- ALuint delay;
+ ALsizei delay;
} Tap[2];
- ALuint Offset;
+ ALsizei Offset;
+
/* The panning gains for the two taps */
- ALfloat Gain[2][MAX_OUTPUT_CHANNELS];
+ struct {
+ ALfloat Current[MAX_OUTPUT_CHANNELS];
+ ALfloat Target[MAX_OUTPUT_CHANNELS];
+ } Gains[2];
ALfloat FeedGain;
- ALfilterState Filter;
+ BiquadFilter Filter;
} ALechoState;
+static ALvoid ALechoState_Destruct(ALechoState *state);
+static ALboolean ALechoState_deviceUpdate(ALechoState *state, ALCdevice *Device);
+static ALvoid ALechoState_update(ALechoState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
+static ALvoid ALechoState_process(ALechoState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
+DECLARE_DEFAULT_ALLOCATORS(ALechoState)
+
+DEFINE_ALEFFECTSTATE_VTABLE(ALechoState);
+
+
+static void ALechoState_Construct(ALechoState *state)
+{
+ ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
+ SET_VTABLE2(ALechoState, ALeffectState, state);
+
+ state->BufferLength = 0;
+ state->SampleBuffer = NULL;
+
+ state->Tap[0].delay = 0;
+ state->Tap[1].delay = 0;
+ state->Offset = 0;
+
+ BiquadFilter_clear(&state->Filter);
+}
+
static ALvoid ALechoState_Destruct(ALechoState *state)
{
- free(state->SampleBuffer);
+ al_free(state->SampleBuffer);
state->SampleBuffer = NULL;
+ ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
}
static ALboolean ALechoState_deviceUpdate(ALechoState *state, ALCdevice *Device)
{
- ALuint maxlen, i;
+ ALsizei maxlen;
// 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 = fastf2u(AL_ECHO_MAX_DELAY * Device->Frequency) + 1;
- maxlen += fastf2u(AL_ECHO_MAX_LRDELAY * Device->Frequency) + 1;
- maxlen = NextPowerOf2(maxlen);
+ maxlen = float2int(AL_ECHO_MAX_DELAY*Device->Frequency + 0.5f) +
+ float2int(AL_ECHO_MAX_LRDELAY*Device->Frequency + 0.5f);
+ maxlen = NextPowerOf2(maxlen);
+ if(maxlen <= 0) return AL_FALSE;
if(maxlen != state->BufferLength)
{
- void *temp;
-
- temp = realloc(state->SampleBuffer, maxlen * sizeof(ALfloat));
+ void *temp = al_calloc(16, maxlen * sizeof(ALfloat));
if(!temp) return AL_FALSE;
+
+ al_free(state->SampleBuffer);
state->SampleBuffer = temp;
state->BufferLength = maxlen;
}
- for(i = 0;i < state->BufferLength;i++)
- state->SampleBuffer[i] = 0.0f;
+
+ memset(state->SampleBuffer, 0, state->BufferLength*sizeof(ALfloat));
+ memset(state->Gains, 0, sizeof(state->Gains));
return AL_TRUE;
}
-static ALvoid ALechoState_update(ALechoState *state, ALCdevice *Device, const ALeffectslot *Slot)
+static ALvoid ALechoState_update(ALechoState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
{
- ALfloat pandir[3] = { 0.0f, 0.0f, 0.0f };
- ALuint frequency = Device->Frequency;
- ALfloat gain, lrpan;
+ const ALCdevice *device = context->Device;
+ ALuint frequency = device->Frequency;
+ ALfloat coeffs[MAX_AMBI_COEFFS];
+ ALfloat gainhf, lrpan, spread;
- state->Tap[0].delay = fastf2u(Slot->EffectProps.Echo.Delay * frequency) + 1;
- state->Tap[1].delay = fastf2u(Slot->EffectProps.Echo.LRDelay * frequency);
+ state->Tap[0].delay = maxi(float2int(props->Echo.Delay*frequency + 0.5f), 1);
+ state->Tap[1].delay = float2int(props->Echo.LRDelay*frequency + 0.5f);
state->Tap[1].delay += state->Tap[0].delay;
- lrpan = Slot->EffectProps.Echo.Spread;
+ spread = props->Echo.Spread;
+ if(spread < 0.0f) lrpan = -1.0f;
+ else lrpan = 1.0f;
+ /* Convert echo spread (where 0 = omni, +/-1 = directional) to coverage
+ * spread (where 0 = point, tau = omni).
+ */
+ spread = asinf(1.0f - fabsf(spread))*4.0f;
- state->FeedGain = Slot->EffectProps.Echo.Feedback;
+ state->FeedGain = props->Echo.Feedback;
- gain = minf(1.0f - Slot->EffectProps.Echo.Damping, 0.01f);
- ALfilterState_setParams(&state->Filter, ALfilterType_HighShelf,
- gain, LOWPASSFREQREF/frequency,
- calc_rcpQ_from_slope(gain, 0.75f));
-
- gain = Slot->Gain;
+ gainhf = maxf(1.0f - props->Echo.Damping, 0.0625f); /* Limit -24dB */
+ BiquadFilter_setParams(&state->Filter, BiquadType_HighShelf,
+ gainhf, LOWPASSFREQREF/frequency, calc_rcpQ_from_slope(gainhf, 1.0f)
+ );
/* First tap panning */
- pandir[0] = -lrpan;
- ComputeDirectionalGains(Device, pandir, gain, state->Gain[0]);
+ CalcAngleCoeffs(-F_PI_2*lrpan, 0.0f, spread, coeffs);
+ ComputePanGains(&device->Dry, coeffs, slot->Params.Gain, state->Gains[0].Target);
/* Second tap panning */
- pandir[0] = +lrpan;
- ComputeDirectionalGains(Device, pandir, gain, state->Gain[1]);
+ CalcAngleCoeffs( F_PI_2*lrpan, 0.0f, spread, coeffs);
+ ComputePanGains(&device->Dry, coeffs, slot->Params.Gain, state->Gains[1].Target);
}
-static ALvoid ALechoState_process(ALechoState *state, ALuint SamplesToDo, const ALfloat *restrict SamplesIn, ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels)
+static ALvoid ALechoState_process(ALechoState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
{
- const ALuint mask = state->BufferLength-1;
- const ALuint tap1 = state->Tap[0].delay;
- const ALuint tap2 = state->Tap[1].delay;
- ALuint offset = state->Offset;
- ALfloat smp;
- ALuint base;
- ALuint i, k;
-
+ const ALsizei mask = state->BufferLength-1;
+ const ALsizei tap1 = state->Tap[0].delay;
+ const ALsizei tap2 = state->Tap[1].delay;
+ ALfloat *restrict delaybuf = state->SampleBuffer;
+ ALsizei offset = state->Offset;
+ ALfloat z1, z2, in, out;
+ ALsizei base;
+ ALsizei c, i;
+
+ z1 = state->Filter.z1;
+ z2 = state->Filter.z2;
for(base = 0;base < SamplesToDo;)
{
- ALfloat temps[128][2];
- ALuint td = minu(128, SamplesToDo-base);
+ alignas(16) ALfloat temps[2][128];
+ ALsizei td = mini(128, SamplesToDo-base);
for(i = 0;i < td;i++)
{
+ /* Feed the delay buffer's input first. */
+ delaybuf[offset&mask] = SamplesIn[0][i+base];
+
/* First tap */
- temps[i][0] = state->SampleBuffer[(offset-tap1) & mask];
+ temps[0][i] = delaybuf[(offset-tap1) & mask];
/* Second tap */
- temps[i][1] = state->SampleBuffer[(offset-tap2) & mask];
+ temps[1][i] = delaybuf[(offset-tap2) & mask];
- // Apply damping and feedback gain to the second tap, and mix in the
- // new sample
- smp = ALfilterState_processSingle(&state->Filter, temps[i][1]+SamplesIn[i+base]);
- state->SampleBuffer[offset&mask] = smp * state->FeedGain;
+ /* Apply damping to the second tap, then add it to the buffer with
+ * feedback attenuation.
+ */
+ in = temps[1][i];
+ out = in*state->Filter.b0 + z1;
+ z1 = in*state->Filter.b1 - out*state->Filter.a1 + z2;
+ z2 = in*state->Filter.b2 - out*state->Filter.a2;
+
+ delaybuf[offset&mask] += out * state->FeedGain;
offset++;
}
- for(k = 0;k < NumChannels;k++)
- {
- ALfloat gain = state->Gain[0][k];
- if(fabsf(gain) > GAIN_SILENCE_THRESHOLD)
- {
- for(i = 0;i < td;i++)
- SamplesOut[k][i+base] += temps[i][0] * gain;
- }
-
- gain = state->Gain[1][k];
- if(fabsf(gain) > GAIN_SILENCE_THRESHOLD)
- {
- for(i = 0;i < td;i++)
- SamplesOut[k][i+base] += temps[i][1] * gain;
- }
- }
+ for(c = 0;c < 2;c++)
+ MixSamples(temps[c], NumChannels, SamplesOut, state->Gains[c].Current,
+ state->Gains[c].Target, SamplesToDo-base, base, td);
base += td;
}
+ state->Filter.z1 = z1;
+ state->Filter.z2 = z2;
state->Offset = offset;
}
-DECLARE_DEFAULT_ALLOCATORS(ALechoState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALechoState);
-
-typedef struct ALechoStateFactory {
- DERIVE_FROM_TYPE(ALeffectStateFactory);
-} ALechoStateFactory;
+typedef struct EchoStateFactory {
+ DERIVE_FROM_TYPE(EffectStateFactory);
+} EchoStateFactory;
-ALeffectState *ALechoStateFactory_create(ALechoStateFactory *UNUSED(factory))
+ALeffectState *EchoStateFactory_create(EchoStateFactory *UNUSED(factory))
{
ALechoState *state;
- state = ALechoState_New(sizeof(*state));
+ NEW_OBJ0(state, ALechoState)();
if(!state) return NULL;
- SET_VTABLE2(ALechoState, ALeffectState, state);
-
- state->BufferLength = 0;
- state->SampleBuffer = NULL;
-
- state->Tap[0].delay = 0;
- state->Tap[1].delay = 0;
- state->Offset = 0;
-
- ALfilterState_clear(&state->Filter);
return STATIC_CAST(ALeffectState, state);
}
-DEFINE_ALEFFECTSTATEFACTORY_VTABLE(ALechoStateFactory);
+DEFINE_EFFECTSTATEFACTORY_VTABLE(EchoStateFactory);
-ALeffectStateFactory *ALechoStateFactory_getFactory(void)
+EffectStateFactory *EchoStateFactory_getFactory(void)
{
- static ALechoStateFactory EchoFactory = { { GET_VTABLE2(ALechoStateFactory, ALeffectStateFactory) } };
+ static EchoStateFactory EchoFactory = { { GET_VTABLE2(EchoStateFactory, EffectStateFactory) } };
- return STATIC_CAST(ALeffectStateFactory, &EchoFactory);
+ return STATIC_CAST(EffectStateFactory, &EchoFactory);
}
-void ALecho_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum UNUSED(param), ALint UNUSED(val))
-{ SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); }
-void ALecho_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
-{
- ALecho_setParami(effect, context, param, vals[0]);
-}
+void ALecho_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
+{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
+void ALecho_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
+{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
void ALecho_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
{
ALeffectProps *props = &effect->Props;
@@ -215,49 +236,45 @@ void ALecho_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALflo
{
case AL_ECHO_DELAY:
if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
- SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE);
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range");
props->Echo.Delay = val;
break;
case AL_ECHO_LRDELAY:
if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY))
- SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE);
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range");
props->Echo.LRDelay = val;
break;
case AL_ECHO_DAMPING:
if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING))
- SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE);
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range");
props->Echo.Damping = val;
break;
case AL_ECHO_FEEDBACK:
if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK))
- SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE);
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range");
props->Echo.Feedback = val;
break;
case AL_ECHO_SPREAD:
if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD))
- SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE);
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range");
props->Echo.Spread = val;
break;
default:
- SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM);
+ alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
}
}
void ALecho_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{
- ALecho_setParamf(effect, context, param, vals[0]);
-}
+{ ALecho_setParamf(effect, context, param, vals[0]); }
-void ALecho_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum UNUSED(param), ALint *UNUSED(val))
-{ SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); }
-void ALecho_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
-{
- ALecho_getParami(effect, context, param, vals);
-}
+void ALecho_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
+{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
+void ALecho_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
+{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
void ALecho_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
{
const ALeffectProps *props = &effect->Props;
@@ -284,12 +301,10 @@ void ALecho_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param,
break;
default:
- SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM);
+ alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
}
}
void ALecho_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{
- ALecho_getParamf(effect, context, param, vals);
-}
+{ ALecho_getParamf(effect, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(ALecho);