aboutsummaryrefslogtreecommitdiffstats
path: root/Alc/effects/autowah.c
diff options
context:
space:
mode:
Diffstat (limited to 'Alc/effects/autowah.c')
-rw-r--r--Alc/effects/autowah.c321
1 files changed, 186 insertions, 135 deletions
diff --git a/Alc/effects/autowah.c b/Alc/effects/autowah.c
index 6770f719..ba1180ef 100644
--- a/Alc/effects/autowah.c
+++ b/Alc/effects/autowah.c
@@ -1,6 +1,6 @@
/**
* OpenAL cross platform audio library
- * Copyright (C) 2013 by Anis A. Hireche, Nasca Octavian Paul
+ * Copyright (C) 2018 by Raul Herraiz.
* 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
@@ -18,181 +18,215 @@
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
+#include "config.h"
+
+#include <math.h>
#include <stdlib.h>
-#include "config.h"
-#include "alu.h"
-#include "alFilter.h"
-#include "alError.h"
#include "alMain.h"
#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "alu.h"
+#include "filters/defs.h"
-
-/* Auto-wah is simply a low-pass filter with a cutoff frequency that shifts up
- * or down depending on the input signal, and a resonant peak at the cutoff.
- *
- * Currently, we assume a cutoff frequency range of 20hz (no amplitude) to
- * 20khz (peak gain). Peak gain is assumed to be in normalized scale.
- */
+#define MIN_FREQ 20.0f
+#define MAX_FREQ 2500.0f
+#define Q_FACTOR 5.0f
typedef struct ALautowahState {
DERIVE_FROM_TYPE(ALeffectState);
- /* Effect gains for each channel */
- ALfloat Gain[MAX_OUTPUT_CHANNELS];
-
/* Effect parameters */
ALfloat AttackRate;
ALfloat ReleaseRate;
- ALfloat Resonance;
+ ALfloat ResonanceGain;
ALfloat PeakGain;
- ALfloat GainCtrl;
- ALfloat Frequency;
-
- /* Samples processing */
- ALfilterState LowPass;
+ ALfloat FreqMinNorm;
+ ALfloat BandwidthNorm;
+ ALfloat env_delay;
+
+ /* Filter components derived from the envelope. */
+ struct {
+ ALfloat cos_w0;
+ ALfloat alpha;
+ } Env[BUFFERSIZE];
+
+ struct {
+ /* Effect filters' history. */
+ struct {
+ ALfloat z1, z2;
+ } Filter;
+
+ /* Effect gains for each output channel */
+ ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
+ ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
+ } Chans[MAX_EFFECT_CHANNELS];
+
+ /* Effects buffers */
+ alignas(16) ALfloat BufferOut[BUFFERSIZE];
} ALautowahState;
-static ALvoid ALautowahState_Destruct(ALautowahState *UNUSED(state))
+static ALvoid ALautowahState_Destruct(ALautowahState *state);
+static ALboolean ALautowahState_deviceUpdate(ALautowahState *state, ALCdevice *device);
+static ALvoid ALautowahState_update(ALautowahState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
+static ALvoid ALautowahState_process(ALautowahState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
+DECLARE_DEFAULT_ALLOCATORS(ALautowahState)
+
+DEFINE_ALEFFECTSTATE_VTABLE(ALautowahState);
+
+static void ALautowahState_Construct(ALautowahState *state)
{
+ ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
+ SET_VTABLE2(ALautowahState, ALeffectState, state);
}
-static ALboolean ALautowahState_deviceUpdate(ALautowahState *state, ALCdevice *device)
+static ALvoid ALautowahState_Destruct(ALautowahState *state)
{
- state->Frequency = (ALfloat)device->Frequency;
- return AL_TRUE;
+ ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
}
-static ALvoid ALautowahState_update(ALautowahState *state, ALCdevice *device, const ALeffectslot *slot)
+static ALboolean ALautowahState_deviceUpdate(ALautowahState *state, ALCdevice *UNUSED(device))
{
- ALfloat attackTime, releaseTime;
+ /* (Re-)initializing parameters and clear the buffers. */
+ ALsizei i, j;
- attackTime = slot->EffectProps.Autowah.AttackTime * state->Frequency;
- releaseTime = slot->EffectProps.Autowah.ReleaseTime * state->Frequency;
+ state->AttackRate = 1.0f;
+ state->ReleaseRate = 1.0f;
+ state->ResonanceGain = 10.0f;
+ state->PeakGain = 4.5f;
+ state->FreqMinNorm = 4.5e-4f;
+ state->BandwidthNorm = 0.05f;
+ state->env_delay = 0.0f;
- state->AttackRate = powf(1.0f/GAIN_SILENCE_THRESHOLD, 1.0f/attackTime);
- state->ReleaseRate = powf(GAIN_SILENCE_THRESHOLD/1.0f, 1.0f/releaseTime);
- state->PeakGain = slot->EffectProps.Autowah.PeakGain;
- state->Resonance = slot->EffectProps.Autowah.Resonance;
+ memset(state->Env, 0, sizeof(state->Env));
+
+ for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
+ {
+ for(j = 0;j < MAX_OUTPUT_CHANNELS;j++)
+ state->Chans[i].CurrentGains[j] = 0.0f;
+ state->Chans[i].Filter.z1 = 0.0f;
+ state->Chans[i].Filter.z2 = 0.0f;
+ }
- ComputeAmbientGains(device, slot->Gain, state->Gain);
+ return AL_TRUE;
}
-static ALvoid ALautowahState_process(ALautowahState *state, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[BUFFERSIZE], ALuint NumChannels)
+static ALvoid ALautowahState_update(ALautowahState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
{
- ALuint it, kt;
- ALuint base;
+ const ALCdevice *device = context->Device;
+ ALfloat ReleaseTime;
+ ALsizei i;
+
+ ReleaseTime = clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f);
+
+ state->AttackRate = expf(-1.0f / (props->Autowah.AttackTime*device->Frequency));
+ state->ReleaseRate = expf(-1.0f / (ReleaseTime*device->Frequency));
+ /* 0-20dB Resonance Peak gain */
+ state->ResonanceGain = sqrtf(log10f(props->Autowah.Resonance)*10.0f / 3.0f);
+ state->PeakGain = 1.0f - log10f(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN);
+ state->FreqMinNorm = MIN_FREQ / device->Frequency;
+ state->BandwidthNorm = (MAX_FREQ-MIN_FREQ) / device->Frequency;
+
+ STATIC_CAST(ALeffectState,state)->OutBuffer = device->FOAOut.Buffer;
+ STATIC_CAST(ALeffectState,state)->OutChannels = device->FOAOut.NumChannels;
+ for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
+ ComputePanGains(&device->FOAOut, IdentityMatrixf.m[i], slot->Params.Gain,
+ state->Chans[i].TargetGains);
+}
- for(base = 0;base < SamplesToDo;)
+static ALvoid ALautowahState_process(ALautowahState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
+{
+ const ALfloat attack_rate = state->AttackRate;
+ const ALfloat release_rate = state->ReleaseRate;
+ const ALfloat res_gain = state->ResonanceGain;
+ const ALfloat peak_gain = state->PeakGain;
+ const ALfloat freq_min = state->FreqMinNorm;
+ const ALfloat bandwidth = state->BandwidthNorm;
+ ALfloat env_delay;
+ ALsizei c, i;
+
+ env_delay = state->env_delay;
+ for(i = 0;i < SamplesToDo;i++)
{
- ALfloat temps[256];
- ALuint td = minu(256, SamplesToDo-base);
- ALfloat gain = state->GainCtrl;
-
- for(it = 0;it < td;it++)
- {
- ALfloat smp = SamplesIn[it+base];
- ALfloat alpha, w0;
- ALfloat amplitude;
- ALfloat cutoff;
-
- /* Similar to compressor, we get the current amplitude of the
- * incoming signal, and attack or release to reach it. */
- amplitude = fabsf(smp);
- if(amplitude > gain)
- gain = minf(gain*state->AttackRate, amplitude);
- else if(amplitude < gain)
- gain = maxf(gain*state->ReleaseRate, amplitude);
- gain = maxf(gain, GAIN_SILENCE_THRESHOLD);
-
- /* FIXME: What range does the filter cover? */
- cutoff = lerp(20.0f, 20000.0f, minf(gain/state->PeakGain, 1.0f));
-
- /* The code below is like calling ALfilterState_setParams with
- * ALfilterType_LowPass. However, instead of passing a bandwidth,
- * we use the resonance property for Q. This also inlines the call.
- */
- w0 = F_TAU * cutoff / state->Frequency;
-
- /* FIXME: Resonance controls the resonant peak, or Q. How? Not sure
- * that Q = resonance*0.1. */
- alpha = sinf(w0) / (2.0f * state->Resonance*0.1f);
- state->LowPass.b[0] = (1.0f - cosf(w0)) / 2.0f;
- state->LowPass.b[1] = 1.0f - cosf(w0);
- state->LowPass.b[2] = (1.0f - cosf(w0)) / 2.0f;
- state->LowPass.a[0] = 1.0f + alpha;
- state->LowPass.a[1] = -2.0f * cosf(w0);
- state->LowPass.a[2] = 1.0f - alpha;
-
- state->LowPass.b[2] /= state->LowPass.a[0];
- state->LowPass.b[1] /= state->LowPass.a[0];
- state->LowPass.b[0] /= state->LowPass.a[0];
- state->LowPass.a[2] /= state->LowPass.a[0];
- state->LowPass.a[1] /= state->LowPass.a[0];
- state->LowPass.a[0] /= state->LowPass.a[0];
-
- temps[it] = ALfilterState_processSingle(&state->LowPass, smp);
- }
- state->GainCtrl = gain;
+ ALfloat w0, sample, a;
+
+ /* Envelope follower described on the book: Audio Effects, Theory,
+ * Implementation and Application.
+ */
+ sample = peak_gain * fabsf(SamplesIn[0][i]);
+ a = (sample > env_delay) ? attack_rate : release_rate;
+ env_delay = lerp(sample, env_delay, a);
+
+ /* Calculate the cos and alpha components for this sample's filter. */
+ w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * F_TAU;
+ state->Env[i].cos_w0 = cosf(w0);
+ state->Env[i].alpha = sinf(w0)/(2.0f * Q_FACTOR);
+ }
+ state->env_delay = env_delay;
- for(kt = 0;kt < NumChannels;kt++)
+ for(c = 0;c < MAX_EFFECT_CHANNELS; c++)
+ {
+ /* This effectively inlines BiquadFilter_setParams for a peaking
+ * filter and BiquadFilter_processC. The alpha and cosine components
+ * for the filter coefficients were previously calculated with the
+ * envelope. Because the filter changes for each sample, the
+ * coefficients are transient and don't need to be held.
+ */
+ ALfloat z1 = state->Chans[c].Filter.z1;
+ ALfloat z2 = state->Chans[c].Filter.z2;
+
+ for(i = 0;i < SamplesToDo;i++)
{
- ALfloat gain = state->Gain[kt];
- if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
- continue;
-
- for(it = 0;it < td;it++)
- SamplesOut[kt][base+it] += gain * temps[it];
+ const ALfloat alpha = state->Env[i].alpha;
+ const ALfloat cos_w0 = state->Env[i].cos_w0;
+ ALfloat input, output;
+ ALfloat a[3], b[3];
+
+ b[0] = 1.0f + alpha*res_gain;
+ b[1] = -2.0f * cos_w0;
+ b[2] = 1.0f - alpha*res_gain;
+ a[0] = 1.0f + alpha/res_gain;
+ a[1] = -2.0f * cos_w0;
+ a[2] = 1.0f - alpha/res_gain;
+
+ input = SamplesIn[c][i];
+ output = input*(b[0]/a[0]) + z1;
+ z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2;
+ z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
+ state->BufferOut[i] = output;
}
+ state->Chans[c].Filter.z1 = z1;
+ state->Chans[c].Filter.z2 = z2;
- base += td;
+ /* Now, mix the processed sound data to the output. */
+ MixSamples(state->BufferOut, NumChannels, SamplesOut, state->Chans[c].CurrentGains,
+ state->Chans[c].TargetGains, SamplesToDo, 0, SamplesToDo);
}
}
-DECLARE_DEFAULT_ALLOCATORS(ALautowahState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALautowahState);
-
+typedef struct AutowahStateFactory {
+ DERIVE_FROM_TYPE(EffectStateFactory);
+} AutowahStateFactory;
-typedef struct ALautowahStateFactory {
- DERIVE_FROM_TYPE(ALeffectStateFactory);
-} ALautowahStateFactory;
-
-static ALeffectState *ALautowahStateFactory_create(ALautowahStateFactory *UNUSED(factory))
+static ALeffectState *AutowahStateFactory_create(AutowahStateFactory *UNUSED(factory))
{
ALautowahState *state;
- state = ALautowahState_New(sizeof(*state));
+ NEW_OBJ0(state, ALautowahState)();
if(!state) return NULL;
- SET_VTABLE2(ALautowahState, ALeffectState, state);
-
- state->AttackRate = 1.0f;
- state->ReleaseRate = 1.0f;
- state->Resonance = 2.0f;
- state->PeakGain = 1.0f;
- state->GainCtrl = 1.0f;
-
- ALfilterState_clear(&state->LowPass);
return STATIC_CAST(ALeffectState, state);
}
-DEFINE_ALEFFECTSTATEFACTORY_VTABLE(ALautowahStateFactory);
+DEFINE_EFFECTSTATEFACTORY_VTABLE(AutowahStateFactory);
-ALeffectStateFactory *ALautowahStateFactory_getFactory(void)
+EffectStateFactory *AutowahStateFactory_getFactory(void)
{
- static ALautowahStateFactory AutowahFactory = { { GET_VTABLE2(ALautowahStateFactory, ALeffectStateFactory) } };
+ static AutowahStateFactory AutowahFactory = { { GET_VTABLE2(AutowahStateFactory, EffectStateFactory) } };
- return STATIC_CAST(ALeffectStateFactory, &AutowahFactory);
+ return STATIC_CAST(EffectStateFactory, &AutowahFactory);
}
-
-void ALautowah_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum UNUSED(param), ALint UNUSED(val))
-{ SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); }
-void ALautowah_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
-{
- ALautowah_setParami(effect, context, param, vals[0]);
-}
void ALautowah_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
{
ALeffectProps *props = &effect->Props;
@@ -200,45 +234,60 @@ void ALautowah_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, AL
{
case AL_AUTOWAH_ATTACK_TIME:
if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
- SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE);
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range");
props->Autowah.AttackTime = val;
break;
case AL_AUTOWAH_RELEASE_TIME:
if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME))
- SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE);
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range");
props->Autowah.ReleaseTime = val;
break;
case AL_AUTOWAH_RESONANCE:
if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
- SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE);
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range");
props->Autowah.Resonance = val;
break;
case AL_AUTOWAH_PEAK_GAIN:
if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN))
- SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE);
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range");
props->Autowah.PeakGain = val;
break;
default:
- SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM);
+ alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
}
}
+
void ALautowah_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
{
ALautowah_setParamf(effect, context, param, vals[0]);
}
-void ALautowah_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum UNUSED(param), ALint *UNUSED(val))
-{ SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); }
-void ALautowah_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
+void ALautowah_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
+{
+ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param);
+}
+
+void ALautowah_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
+{
+ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param);
+}
+
+void ALautowah_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
{
- ALautowah_getParami(effect, context, param, vals);
+ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param);
}
+void ALautowah_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
+{
+ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param);
+}
+
void ALautowah_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
{
+
const ALeffectProps *props = &effect->Props;
switch(param)
{
@@ -259,9 +308,11 @@ void ALautowah_getParamf(const ALeffect *effect, ALCcontext *context, ALenum par
break;
default:
- SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM);
+ alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
}
+
}
+
void ALautowah_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
{
ALautowah_getParamf(effect, context, param, vals);